it-swarm.com.ru

Получить последний не пустой столбец и индекс строки из Excel, используя Interop

Я пытаюсь удалить все лишние пустые строки и столбцы из файла Excel с помощью Interop Library.

Я следовал за этим вопросом Самый быстрый метод удаления пустых строк и столбцов из файлов Excel с помощью Interop и я считаю его полезным. 

Но у меня есть файлы Excel, которые содержат небольшой набор данных, но много пустых строк и столбцов (от последней непустой строки (или столбца) до конца рабочего листа)

Я попытался перебрать строки и столбцы, но цикл занимает часы.

Я пытаюсь получить последний непустой индекс строки и столбца, чтобы я мог удалить весь пустой диапазон в одной строке 

XlWks.Range("...").EntireRow.Delete(xlShiftUp)

 enter image description here

Примечание: я пытаюсь получить последнюю строку, содержащую данные, чтобы удалить все лишние пробелы (после этой строки или столбца)

Какие-либо предложения?

29
Yahfoufi

Обновление 1

Если ваша цель состоит в том, чтобы импортировать данные Excel с помощью c #, предполагая, что вы указали самый высокий использованный индекс в своей рабочей таблице (на изображении, которое вы разместили, это Col = 10, Row = 16), вы можете преобразовать Максимальное количество используемых индексов для буквы, так что это будет J16 и выберите только используемый диапазон, используя и OLEDBCommand

SELECT * FROM [Sheet1$A1:J16]

Иначе, я не думаю, что легко найти более быстрый метод.

Вы можете обратиться к этой статье, чтобы преобразовать индексы в алфавит и подключиться к Excel, используя OLEDB:


Начальный ответ

Как вы сказали, вы начали со следующего вопроса:

И вы пытаетесь "получить последнюю строку, содержащую данные, чтобы удалить все лишние пробелы (после этой строки или столбца)"

Предполагая, что вы работаете с ответом на принятие (предоставленным @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();
        }
    }
12
Hadi

Несколько лет назад я создал образец кода 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. Выберите имя листа в списке и получите последнюю строку и столбец этого листа в действительном адресе ячейки.

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

На первый взгляд при открытии решения по ссылке выше вы заметите, что кода много. Код является оптимальным и немедленно освободит все объекты.

8
Karen Payne

Я использую ClosedXml, который имеет полезные методы LastUsedRow и LastUsedColumn.

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();

Этот простой цикл удалил 5000 из 10000 строк за 38 секунд. Не быстро, но намного лучше, чем «часы». Это зависит от того, сколько строк/столбцов вы имеете дело с, конечно, вы не говорите . Однако, после дальнейших тестов с 25000 пустых строк из 50000, это займет около 30 минут, чтобы удалить пустые строки в петля. Очевидно, что удаление строк не является эффективным процессом.

Лучшее решение - создать новый лист, а затем скопировать строки, которые вы хотите сохранить.

Шаг 1 - создайте лист с 50000 строками и 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 - это будет делать ту же операцию для столбцов.

7
Phil
  • Для получения последнего непустого индекса столбца/строки можно использовать функцию Excel Find. Смотрите GetLastIndexOfNonEmptyCell.
  • Затем функция Excel WorksheetCountA используется для определения того, являются ли ячейки пустыми, и union целых строк/столбцов в одном диапазоне строк/столбцов. 
  • Эти диапазоны удаляются окончательно сразу.

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 вопрос .

Правка

Попробуйте просто очистить содержимое всех ячеек, которые находятся после последней использованной ячейки. 

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);

    // Clear the columns
    sh.Range(sh.Cells(1, lastCol + 1), sh.Cells(1, Columns.Count)).EntireColumn.Clear();

    // Clear the remaining cells
    sh.Range(sh.Cells(lastRow + 1, 1), sh.Cells(Rows.Count, lastCol)).Clear();

}
7
dee

Допустим, последняя угловая ячейка с данными - это J16, поэтому в столбцах K и далее в строках 17 нет данных. Почему вы на самом деле удаляете их? Каков сценарий и чего вы пытаетесь достичь? Это очищает наше форматирование? Является ли очистка наших формул, которые показывают пустую строку? 

В любом случае, циклы не так. 

Приведенный ниже код демонстрирует способ использования метода Clear () объекта Range для очистки всего содержимого, формул и форматирования из диапазона. В качестве альтернативы, если вы действительно хотите удалить их, вы можете использовать метод 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
4
MacroMarc

Вы должны быть в состоянии найти последние непустые строки и столбцы с чем-то похожим на это:

with m_XlWrkSheet
lastRow = .UsedRange.Rows.Count
lastCol = .UsedRange.Columns.Count
end with

Это VB.NET, но он должен работать более или менее. Это вернет строку 16 и столбец 10 (на основе вашей фотографии выше). Затем вы можете использовать это, чтобы найти диапазон, который вы хотите удалить все в одной строке.

2
garroad_ran

Кажется, что ваша проблема была решена Microsoft. Взгляните на свойство Range.CurrentRegion , которое возвращает диапазон, ограниченный любой комбинацией пустых строк и пустых столбцов. Есть одно неудобство: это свойство нельзя использовать на защищенном листе.

Дополнительные сведения см. В разделе: Как найти текущий регион, используемый диапазон, последнюю строку и последний столбец в Excel с помощью макроса VBA

Некоторые члены SO упоминали о свойстве UsedRange , что также может быть полезно, но отличие от CurrentRegion в том, что UsedRange возвращает диапазон, включающий любую ячейку, которая когда-либо использовалась.
Итак, если вы хотите, чтобы LAST(row) и LAST(column) были заняты данными, вы должны использовать Завершить свойство с XlDirection: xlToLeft и/или xlUp

Примечание № 1:
Если ваши данные представлены в табличном формате, вы можете просто найти последнюю ячейку, используя:

lastCell = yourWorkseet.UsedRange.End(xlUp)
firstEmtyRow = lastCell.Offset(RowOffset:=1).EntireRow

Заметка 2:
Если ваши данные не в табличном формате, вам нужно перебрать коллекцию строк и столбцов, чтобы найти последнюю непустую ячейку.

Удачи!

1
Maciej Los