web-dev-qa-db-fra.com

Obtenir le dernier index de colonne et de ligne non vide d'Excel à l'aide d'Interop

J'essaie de supprimer toutes les lignes et colonnes vides supplémentaires d'un fichier Excel à l'aide d'Interop Library.

J'ai suivi cette question La méthode la plus rapide pour supprimer les lignes vides et les colonnes des fichiers Excel à l'aide d'Interop et je la trouve utile. 

Mais j'ai des fichiers Excel qui contiennent un petit ensemble de données mais beaucoup de lignes vides et de colonnes (de la dernière ligne non vide (ou colonne) à la fin de la feuille de calcul)

J'ai essayé de boucler sur des lignes et des colonnes, mais la boucle prend des heures.

J'essaie d'obtenir le dernier index de ligne et de colonne non vide afin de pouvoir supprimer toute la plage vide d'une ligne. 

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

 enter image description here

Remarque: j'essaie d'obtenir la dernière ligne contenant des données pour supprimer tous les espaces supplémentaires (après cette ligne ou cette colonne)

Aucune suggestion?

29
Yahfoufi

Mise à jour 1

Si votre objectif est d'importer les données Excel à l'aide de c #, en supposant que vous ayez identifié l'index utilisé le plus élevé dans votre feuille de calcul (dans l'image que vous avez publiée, il s'agit de Col = 10, Row = 16), vous pouvez convertir le Le nombre maximal d'index utilisés par lettre sera donc J16 et ne sélectionnera que la plage utilisée à l'aide de et OLEDBCommand

SELECT * FROM [Sheet1$A1:J16]

Sinon, je ne pense pas qu'il soit facile de trouver une méthode plus rapide.

Vous pouvez vous référer à ces articles pour convertir des index en alphabet et pour vous connecter à Excel à l'aide d'OLEDB:


Réponse initiale

Comme vous l'avez dit, vous êtes parti de la question suivante:

Et vous essayez de "obtenir la dernière ligne contenant des données pour supprimer tous les blancs supplémentaires (après cette ligne ou cette colonne)"

Donc, en supposant que vous travaillez avec la réponse d'acceptation (fournie par @JohnG ), vous pouvez donc ajouter une ligne de code pour obtenir la dernière ligne et colonne utilisées

Les lignes vides sont stockées dans une liste de nombres entiers rowsToDelete

Vous pouvez utiliser le code suivant pour obtenir les dernières lignes non vides avec un index plus petit que la dernière ligne vide.

List<int> NonEmptyRows = Enumerable.Range(1, rowsToDelete.Max()).ToList().Except(rowsToDelete).ToList();

Et si NonEmptyRows.Max() < rowsToDelete.Max() la dernière ligne non vide est NonEmptyRows.Max() Autrement, elle est worksheet.Rows.Count et il n'y a pas de lignes vides après la dernière utilisée.

La même chose peut être faite pour obtenir la dernière colonne non vide

Le code est édité dans les fonctions DeleteCols et 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

Il y a plusieurs années, j'ai créé un exemple de code MSDN qui permet à un développeur de récupérer les dernières lignes et colonnes utilisées dans une feuille de calcul. Je l'ai modifié, placé tout le code nécessaire dans une bibliothèque de classes avec un frontal Windows Form pour démontrer l'opération.

Le code sous-jacent utilise Microsoft.Office.Interop.Excel.

Emplacement sur un lecteur Microsoft https://1drv.ms/u/s!AtGAgKKpqdWjiEGdBzWDCSCZAMaM

Ici, je récupère la première feuille d'un fichier Excel, la dernière ligne et le dernier col utilisés et les présente comme adresse de cellule valide.

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

Dans le projet de démonstration, je récupère également toutes les feuilles d'un fichier Excel et les présente dans un contrôle ListBox. Sélectionnez un nom de feuille dans la zone de liste et obtenez la dernière ligne et la dernière colonne de cette feuille dans une adresse de cellule valide.

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

En ouvrant la solution à partir du lien ci-dessus, vous remarquerez qu'il y a beaucoup de code. Le code est optimal et libérera tous les objets immédiatement.

8
Karen Payne

J'utilise ClosedXml qui contient des méthodes utiles 'LastUsedRow' et '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();

Cette simple boucle a supprimé 5000 lignes sur 10 000 en 38 secondes. Pas vite, mais beaucoup mieux que «heures». Cela dépend bien sûr du nombre de lignes/colonnes que vous traitez et que vous ne dites pas… .. Cependant, après d'autres tests avec 25 000 lignes vides sur 50000, il faut environ 30 minutes pour supprimer les lignes vides dans boucle. Effacer clairement des lignes n'est pas un processus efficace.

Une meilleure solution consiste à créer une nouvelle feuille, puis à copier les lignes que vous souhaitez conserver.

Étape 1 - Créer une feuille avec 50000 lignes et 20 colonnes, une ligne sur deux et une colonne étant vide.

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

Étape 2 - Copiez les lignes avec les données dans une nouvelle feuille. Cela prend 10 secondes.

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

Étape 3 - Ce serait faire la même opération pour les colonnes.

7
Phil
  • Pour obtenir le dernier index de colonne/ligne non vide, la fonction Excel Find peut être utilisée. Voir GetLastIndexOfNonEmptyCell.
  • Ensuite, la fonction de feuille de calcul ExcelCountA est utilisée pour déterminer si les cellules sont vides et union les lignes/colonnes entières sur une plage de lignes/colonnes. 
  • Ces plages sont finalement supprimées en une fois.

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

Les deux fonctions permettant d’obtenir des plages vides de lignes/colonnes peuvent être remises à jour en une seule fonction, à peu près comme ceci:

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

Et puis appelez ça:

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

Remarque: pour plus d’informations, regardez par exemple. sur cette SO question .

Modifier

Essayez de simplement effacer le contenu de toutes les cellules qui sont après la dernière cellule utilisée. 

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

Supposons que la dernière cellule de coin avec les données est J16 - donc, aucune donnée dans les colonnes K ou supérieures ni dans les lignes 17 et suivantes. Pourquoi les supprimez-vous réellement? Quel est le scénario et qu'essayez-vous de réaliser? Est-ce que ça efface notre formatage? Est-ce que l'effacement de nos formules qui montrent une chaîne vide? 

Dans tous les cas, la boucle n'est pas la solution. 

Le code ci-dessous illustre l'utilisation de la méthode Clear () de l'objet Range pour effacer tout le contenu, les formules et le formatage d'une plage. Si vous souhaitez réellement les supprimer, vous pouvez également utiliser la méthode Delete () pour supprimer une plage rectangulaire entière en un seul résultat. Sera beaucoup plus rapide que la boucle ...

//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

Vous devriez pouvoir trouver la dernière ligne et colonne non vides avec quelque chose de similaire à ceci:

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

C'est VB.NET, mais ça devrait plus ou moins fonctionner. Cela renverra la ligne 16 et la colonne 10 (en fonction de votre image ci-dessus). Vous pouvez ensuite l'utiliser pour trouver la plage que vous souhaitez supprimer sur une seule ligne.

2
garroad_ran

Il semble que votre problème ait été résolu par Microsoft. Examinez la propriété Range.CurrentRegion , qui renvoie une plage délimitée par toute combinaison de lignes et de colonnes vides. Il y a un inconvénient: cette propriété ne peut pas être utilisée dans une feuille de calcul protégée}.

Pour plus de détails, veuillez consulter: Comment trouver la région actuelle, la plage utilisée, la dernière ligne et la dernière colonne dans Excel avec macro VBA

Certains membres de SO ont mentionné la propriété UsedRange , ce qui peut également être utile, mais la différence avec CurrentRegion est que UsedRange renvoie une plage incluant toutes les cellules déjà utilisées.
Ainsi, si vous souhaitez que LAST(row) et LAST(column) soient occupés par des données, vous devez utiliser End property with XlDirection: xlToLeft et/ou xlUp

Note 1:
Si vos données sont dans un format tabulaire, vous pouvez simplement trouver la dernière cellule en utilisant:

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

Note 2:
Si vos données ne sont pas dans un format tabulaire, vous devez parcourir la collection de lignes et de colonnes pour rechercher la dernière cellule non vide.

Bonne chance!

1
Maciej Los