使用Interop从Excel中获取最后一个非空列和行索引
我试图从使用Interop库的Excel文件中删除所有多余的空行和列。
我跟着这个问题最快的方法来删除空行和列从Excel文件使用互操作 ,我觉得很有帮助。
但我有Excel文件包含一小部分的数据,但很多空行和列(从最后一个非空行(或列)到工作表的结尾)
我试着循环遍历行和列,但循环需要几个小时。
我想获得最后一个非空的行和列索引,所以我可以删除一行中的整个空范围
XlWks.Range("...").EntireRow.Delete(xlShiftUp)
注意:我试图让包含数据的最后一行删除所有额外的空白(在这一行或列之后)
有什么build议么?
正如你所说,你从以下问题开始:
- 使用Interop从Excel文件中删除空行和列的最快速的方法
而你正试图“获取包含数据的最后一行来删除所有额外的空白(在这一行或列之后)”
所以假设你正在接受答案(由@JohnG提供),所以你可以添加一些代码行来获得最后使用的行和列
空行存储在整数rowsToDelete
列表中
您可以使用以下代码来获取索引小于最后一个空行的最后一个非空行
List<int> NonEmptyRows = Enumerable.Range(1, rowsToDelete.Max()).ToList().Except(rowsToDelete).ToList();
如果NonEmptyRows.Max() < rowsToDelete.Max()
最后一个非空行是NonEmptyRows.Max()
否则它是worksheet.Rows.Count
并且最后一个使用后没有空行。
获取最后一个非空列也可以做同样的事情
代码在DeleteCols
和DeleteRows
函数中被编辑:
private static void DeleteRows(List<int> rowsToDelete, Microsoft.Office.Interop.Excel.Worksheet worksheet) { // the rows are sorted high to low - so index's wont shift List<int> NonEmptyRows = Enumerable.Range(1, rowsToDelete.Max()).ToList().Except(rowsToDelete).ToList(); if (NonEmptyRows.Max() < rowsToDelete.Max()) { // there are empty rows after the last non empty row Microsoft.Office.Interop.Excel.Range cell1 = worksheet.Cells[NonEmptyRows.Max() + 1,1]; Microsoft.Office.Interop.Excel.Range cell2 = worksheet.Cells[rowsToDelete.Max(), 1]; //Delete all empty rows after the last used row worksheet.Range[cell1, cell2].EntireRow.Delete(Microsoft.Office.Interop.Excel.XlDeleteShiftDirection.xlShiftUp); } //else last non empty row = worksheet.Rows.Count foreach (int rowIndex in rowsToDelete.Where(x => x < NonEmptyRows.Max())) { worksheet.Rows[rowIndex].Delete(); } } private static void DeleteCols(List<int> colsToDelete, Microsoft.Office.Interop.Excel.Worksheet worksheet) { // the cols are sorted high to low - so index's wont shift //Get non Empty Cols List<int> NonEmptyCols = Enumerable.Range(1, colsToDelete.Max()).ToList().Except(colsToDelete).ToList(); if (NonEmptyCols.Max() < colsToDelete.Max()) { // there are empty rows after the last non empty row Microsoft.Office.Interop.Excel.Range cell1 = worksheet.Cells[1,NonEmptyCols.Max() + 1]; Microsoft.Office.Interop.Excel.Range cell2 = worksheet.Cells[1,NonEmptyCols.Max()]; //Delete all empty rows after the last used row worksheet.Range[cell1, cell2].EntireColumn.Delete(Microsoft.Office.Interop.Excel.XlDeleteShiftDirection.xlShiftToLeft); } //else last non empty column = worksheet.Columns.Count foreach (int colIndex in colsToDelete.Where(x => x < NonEmptyCols.Max())) { worksheet.Columns[colIndex].Delete(); } }
几年前,我创build了一个MSDN代码示例,允许开发人员从工作表中获取上次使用的行和列。 我修改了它,把所有需要的代码放到一个带有窗体前端的类库中来演示这个操作。
底层代码使用Microsoft.Office.Interop.Excel。
在Microsoft一个驱动器上的位置https://1drv.ms/u/s!AtGAgKKpqdWjiEGdBzWDCSCZAMaM
在这里,我得到Excel文件中的第一张纸,获取最后使用的行和列,并将其作为有效的单元格地址呈现。
Private Sub cmdAddress1_Click(sender As Object, e As EventArgs) Handles cmdAddress1.Click Dim ops As New GetExcelColumnLastRowInformation Dim info = New UsedInformation ExcelInformationData = info.UsedInformation(FileName, ops.GetSheets(FileName)) Dim SheetName As String = ExcelInformationData.FirstOrDefault.SheetName Dim cellAddress = ( From item In ExcelInformationData Where item.SheetName = ExcelInformationData.FirstOrDefault.SheetName Select item.LastCell).FirstOrDefault MessageBox.Show($"{SheetName} - {cellAddress}") End Sub
在演示项目中,我还获得了一个excel文件的所有表单,并将它们显示在ListBox中。 从列表框中select一个工作表名称,并获取该工作表的最后一行和一列的有效单元格地址。
Private Sub cmdAddress_Click(sender As Object, e As EventArgs) Handles cmdAddress.Click Dim cellAddress = ( From item In ExcelInformationData Where item.SheetName = ListBox1.Text Select item.LastCell).FirstOrDefault If cellAddress IsNot Nothing Then MessageBox.Show($"{ListBox1.Text} {cellAddress}") End If End Sub
从上面的链接打开解决scheme时,第一眼就会看到有很多代码。 该代码是最佳的,将立即释放所有对象。
我使用的是有用的'LastUsedRow'和'LastUsedColumn'方法的ClosedXml。
var wb = new XLWorkbook(@"<path>\test.xlsx", XLEventTracking.Disabled); var sheet = wb.Worksheet("Sheet1"); for (int i = sheet.LastRowUsed().RowNumber() - 1; i >= 1; i--) { var row = sheet.Row(i); if (row.IsEmpty()) { row.Delete(); } } wb.Save();
这个简单的循环在38秒内从10000行中删除了5000个。 不快,但比“小时”好很多。 这取决于你正在处理的行数/列数,当然你不会说。 但是,进一步testing后,在50000行中有25000个空行,大约需要30分钟来删除循环中的空行。 明确删除行不是一个有效的过程。
更好的解决scheme是创build一个新工作表,然后复制要保留的行。
第1步 – 创build50000行和20列的表,其他行和列是空的。
var wb = new XLWorkbook(@"C:\Users\passp\Documents\test.xlsx"); var sheet = wb.Worksheet("Sheet1"); sheet.Clear(); for (int i = 1; i < 50000; i+=2) { var row = sheet.Row(i); for (int j = 1; j < 20; j += 2) { row.Cell(j).Value = i * j; } }
第2步 – 将数据行复制到新的工作表。 这需要10秒钟。
var wb = new XLWorkbook(@"C:\Users\passp\Documents\test.xlsx", XLEventTracking.Disabled); var sheet = wb.Worksheet("Sheet1"); var sheet2 = wb.Worksheet("Sheet2"); sheet2.Clear(); sheet.RowsUsed() .Where(r => !r.IsEmpty()) .Select((r, index) => new { Row = r, Index = index + 1} ) .ForEach(r => { var newRow = sheet2.Row(r.Index); r.Row.CopyTo(newRow); } ); wb.Save();
第3步 – 这将是为列做相同的操作。
- 要获得最后一个非空列/行索引,可以使用Excel函数
Find
。 请参阅GetLastIndexOfNonEmptyCell
。 - 然后Excel工作表函数
CountA
用于确定单元格是否为空,并将整个行/列CountA
到一个行/列范围。 - 这个范围一下子被删除了。
public void Yahfoufi(string excelFile) { var exapp = new Microsoft.Office.Interop.Excel.Application {Visible = true}; var wrb = exapp.Workbooks.Open(excelFile); var sh = wrb.Sheets["Sheet1"]; var lastRow = GetLastIndexOfNonEmptyCell(exapp, sh, XlSearchOrder.xlByRows); var lastCol = GetLastIndexOfNonEmptyCell(exapp, sh, XlSearchOrder.xlByColumns); var target = sh.Range[sh.Range["A1"], sh.Cells[lastRow, lastCol]]; Range deleteRows = GetEmptyRows(exapp, target); Range deleteColumns = GetEmptyColumns(exapp, target); deleteColumns?.Delete(); deleteRows?.Delete(); } private static int GetLastIndexOfNonEmptyCell( Microsoft.Office.Interop.Excel.Application app, Worksheet sheet, XlSearchOrder searchOrder) { Range rng = sheet.Cells.Find( What: "*", After: sheet.Range["A1"], LookIn: XlFindLookIn.xlFormulas, LookAt: XlLookAt.xlPart, SearchOrder: searchOrder, SearchDirection: XlSearchDirection.xlPrevious, MatchCase: false); if (rng == null) return 1; return searchOrder == XlSearchOrder.xlByRows ? rng.Row : rng.Column; } private static Range GetEmptyRows( Microsoft.Office.Interop.Excel.Application app, Range target) { Range result = null; foreach (Range r in target.Rows) { if (app.WorksheetFunction.CountA(r.Cells) >= 1) continue; result = result == null ? r.EntireRow : app.Union(result, r.EntireRow); } return result; } private static Range GetEmptyColumns( Microsoft.Office.Interop.Excel.Application app, Range target) { Range result = null; foreach (Range c in target.Columns) { if (app.WorksheetFunction.CountA(c.Cells) >= 1) continue; result = result == null ? c.EntireColumn : app.Union(result, c.EntireColumn); } return result; }
获取行/列的空范围的两个函数可以重构为一个函数,如下所示:
private static Range GetEntireEmptyRowsOrColumns( Microsoft.Office.Interop.Excel.Application app, Range target, Func<Range, Range> rowsOrColumns, Func<Range, Range> entireRowOrColumn) { Range result = null; foreach (Range c in rowsOrColumns(target)) { if (app.WorksheetFunction.CountA(c.Cells) >= 1) continue; result = result == null ? entireRowOrColumn(c) : app.Union(result, entireRowOrColumn(c)); } return result; }
然后调用它:
Range deleteColumns = GetEntireEmptyRowsOrColumns(exapp, target, (Func<Range, Range>)(r1 => r1.Columns), (Func<Range, Range>)(r2 => r2.EntireColumn)); Range deleteRows = GetEntireEmptyRowsOrColumns(exapp, target, (Func<Range, Range>)(r1 => r1.Rows), (Func<Range, Range>)(r2 => r2.EntireRow)); deleteColumns?.Delete(); deleteRows?.Delete();
注意:有关更多信息,请看这个SO问题 。
假设数据的最后一个angular落单元格是J16,所以K列向前没有数据,或者17行向下。 为什么你真的删除它们? 什么情况,你想达到什么目的? 它是否清除我们的格式? 是清除显示空string的公式吗?
无论如何,循环不是方法。
下面的代码展示了一种使用Range对象的Clear()方法来清除范围内的所有内容和公式和格式的方法。 或者,如果你真的想删除它们,你可以使用Delete()方法在一个命中中删除整个矩形范围。 会比循环更快…
//code uses variables declared appropriately as Excel.Range & Excel.Worksheet Using Interop library int x; int y; // get the row of the last value content row-wise oRange = oSheet.Cells.Find(What: "*", After: oSheet.get_Range("A1"), LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlPart, SearchDirection: XlSearchDirection.xlPrevious, SearchOrder: XlSearchOrder.xlByRows); if (oRange == null) { return; } x = oRange.Row; // get the column of the last value content column-wise oRange = oSheet.Cells.Find(What: "*", After: oSheet.get_Range("A1"), LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlPart, SearchDirection: XlSearchDirection.xlPrevious, SearchOrder: XlSearchOrder.xlByColumns); y = oRange.Column; // now we have the corner (x, y), we can delete or clear all content to the right and below // say J16 is the cell, so x = 16, and j=10 Excel.Range clearRange; //set clearRange to ("K1:XFD1048576") clearRange = oSheet.Range[oSheet.Cells[1, y + 1], oSheet.Cells[oSheet.Rows.Count, oSheet.Columns.Count]]; clearRange.Clear(); //clears all content, formulas and formatting //clearRange.Delete(); if you REALLY want to hard delete the rows //set clearRange to ("A17:J1048576") clearRange = oSheet.Range[oSheet.Cells[x + 1, 1], oSheet.Cells[oSheet.Rows.Count, y]]; clearRange.Clear(); //clears all content, formulas and formatting //clearRange.Delete(); if you REALLY want to hard delete the columns
你应该可以find类似这样的最后一个非空行和列:
with m_XlWrkSheet lastRow = .UsedRange.Rows.Count lastCol = .UsedRange.Columns.Count end with
这是VB.NET,但它应该或多或less的工作。 这将返回行16和列10(根据您的照片上面)。 然后你可以使用它来查找你想要删除所有在一行中的范围。