web-dev-qa-db-ja.com

Interopを使用してExcelから最後の空でない列と行のインデックスを取得する

Interop Libraryを使用して、Excelファイルから余分な空白行と列をすべて削除しようとしています。

私はこの質問に答えました Interopを使用してExcelファイルから空の行と列を削除する最速の方法 と私はそれが役立つと思います。

しかし、私は小さなデータセットを含むExcelファイルがありますが、多くの空の行と列(最後の空でない行(または列)からワークシートの最後まで)

行と列をループしようとしましたが、ループに時間がかかりました。

私は1行で空の範囲全体を削除できるように、最後の空でない行と列のインデックスを取得しようとしています

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

enter image description here

注:データを含む最後の行を取得して、すべての余分な空白(この行、または列の後)を削除しようとしています

助言がありますか?


注:コードはSSISスクリプトタスク環境と互換性がある必要があります

29
Yahfoufi

アップデート1

ワークシートで最も使用されているインデックスを特定したと仮定して、目標がc#を使用してExcelデータをインポートする場合(投稿した画像ではCol = 10、Row = 16) 、最大使用インデックスを文字に変換して、_J16_になり、OLEDBCommandを使用して使用範囲のみを選択できます。

_SELECT * FROM [Sheet1$A1:J16]
_

そうでなければ、より速い方法を見つけるのは簡単だとは思わない。

これらの記事を参照して、インデックスをアルファベットに変換し、OLEDBを使用して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();
        }
    }
_
12
Hadi

数年前、開発者がワークシートから最後に使用した行と列を取得できるようにするMSDNコードサンプルを作成しました。それを修正し、必要なすべてのコードをフロントエンドのウィンドウを備えたクラスライブラリに配置して、操作をデモしました。

基礎となるコードはMicrosoft.Office.Interop.Excelを使用します。

Microsoftの1つのドライブ上の場所 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

便利な「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行を削除しました。速くはありませんが、「時間」よりもはるかに優れています。言うまでもなく、処理する行/列の数に依存します。ただし、50000のうち25000の空の行でさらにテストした後、ループ内の空の行を削除するには約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ワークシート関数CountAを使用して、セルが空かどうか、およびunion行/列全体から1行/列の範囲。
  • この範囲は最終的に一度に削除されます。

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

行/列の空の範囲を取得するための2つの関数は、次のような1つの関数にリファクタリングできます。

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

注:詳細については、たとえばon this SO question

編集

最後に使用したセルの後にあるすべてのセルの内容を単純にクリアしてみてください。

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の下のデータはありません。実際にそれらを削除するのはなぜですか?シナリオとは何ですか、また何を達成しようとしていますか?フォーマットをクリアしていますか?空の文字列を示す数式をクリアしていますか?

いずれにせよ、ループは方法ではありません。

以下のコードは、RangeオブジェクトのClear()メソッドを使用して、範囲からすべてのコンテンツと数式およびフォーマットをクリアする方法を示しています。あるいは、本当に削除したい場合は、Delete()メソッドを使用して、1回のヒットで長方形の範囲全体を削除できます。ループよりもはるかに高速になります...

//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が返されます(上の写真に基づいて)。次に、それを使用して、削除する範囲をすべて1行で検索できます。

2
garroad_ran

Rangeを使用してみてください。

        Application Excel = new Application();
        Workbook workBook=  Excel.Workbooks.Open("file.xlsx")
        Worksheet excelSheet = workBook.ActiveSheet;
        Range excelRange = excelSheet.UsedRange.Columns[1, Missing.Value] as Range;

        var lastNonEmptyRow = excelRange.Cells.Count;

上記のコードは私のために機能します。

1
Shajin Chandran

問題はマイクロソフトによって解決されたようです。 Range.CurrentRegion Property を見てください。空白行と空白列の任意の組み合わせで区切られた範囲を返します。不便な点が1つあります:このプロパティは保護されたワークシートでは使用できません

詳細については、以下を参照してください。 VBAマクロを使用してExcelで現在の地域、使用範囲、最終行、最終列を検索する方法

一部のSOメンバーは sedRangeプロパティ について言及していますが、これも有用かもしれませんが、CurrentRegionとの違いは、UsedRangeが範囲を返すことで、これまでにあったセルが含まれます中古。
したがって、データで占有されているLAST(row)およびLAST(column)を取得するには、XlDirectionEndプロパティ を使用する必要があります:xlToLeft and /またはxlUp

注#1:
データが表形式の場合、次を使用して最後のセルを簡単に見つけることができます。

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

注2:
データが表形式ではないでない場合、行と列のコレクションをループして最後の非-空白のセル。

幸運を!

1
Maciej Los