2015-05-01 13 views
7

Ich möchte ein mehrdimensionales Array formatierter Zellenwerte aus Excel effizient abrufen können. Wenn ich formatierte Werte sage, meine ich, dass ich sie genau so erhalten möchte, wie sie in Excel erscheinen, mit allen Zellen NumberFormat angewendet.Formatierte Zellenwerte effizient abrufen

Die Eigenschaften Range.Value und Range.Value2 eignen sich hervorragend zum Abrufen der Zellenwerte einer großen Anzahl von Zellen in einem mehrdimensionalen Array. Aber das sind die tatsächlichen Zellenwerte (zumindest mit Range.Value2 ist, ich bin mir nicht ganz sicher, was Range.Value in Bezug auf einige der Werte tut).

Wenn ich den tatsächlichen Text abrufen möchte, der in den Zellen angezeigt wird, kann ich die Range.Text-Eigenschaft verwenden. Dies hat einige Vorbehalte. Zuerst müssen Sie die Zellen automatisch anpassen, sonst erhalten Sie etwas wie ####, wenn nicht der gesamte Text mit der aktuellen Zellenbreite sichtbar ist. Zweitens funktioniert Range.Text nicht für mehr als eine Zelle gleichzeitig, sodass Sie alle Zellen im Bereich durchlaufen müssen, was bei großen Datenmengen extrem langsam sein kann. Die andere Methode, die ich ausprobiert habe, ist, den Bereich in die Zwischenablage zu kopieren und dann den Zwischenablage-Text als tabulatorgetrennten Datenstrom zu analysieren und in ein mehrdimensionales Array zu übertragen. Das scheint gut zu funktionieren, obwohl es langsamer ist als Range.Value2 zu bekommen, ist es viel schneller für große Datenmengen als Range.Text. Die Idee, die System-Zwischenablage zu verwenden, gefällt mir jedoch nicht. Wenn dies ein sehr langer Vorgang war, der 60 Sekunden dauert und während dieser Vorgang ausgeführt wird, kann der Benutzer entscheiden, zu einer anderen Anwendung zu wechseln, und wäre sehr unglücklich zu finden, dass seine Zwischenablage entweder nicht funktioniert oder mysteriöse Daten enthält.

Gibt es eine Möglichkeit, die formatierten Zellenwerte effizient zu einem mehrdimensionalen Array abrufen zu können?

Ich habe einen Beispielcode hinzugefügt, der in einer VSTO-App über einige Multifunktionsleisten-Schaltflächen ausgeführt wird. Die erste setzt einige gute Testwerte und Zahlenformate und die zweite Schaltfläche zeigt, wie sie aussehen, wenn sie mit einer dieser Methoden in einer MessageBox abgerufen werden.

Die Beispielausgabe auf meinem System ist (es aufgrund von Ländereinstellungen auf Ihrem unterschiedlich sein könnte):

Output using Range.Value 
1/25/2008 3:19:32 PM 5.12345 
2008-01-25 15:19:32 0.456 

Output using Range.Value2 
39472.6385648148 5.12345 
2008-01-25 15:19:32 0.456 

Output using Clipboard Copy 
1/25/2008 15:19 5.12 
2008-01-25 15:19:32 45.60% 

Output using Range.Text and Autofit 
1/25/2008 15:19 5.12 
2008-01-25 15:19:32 45.60% 

Die Range.Text und Zwischenablage Methoden produzieren die richtige Ausgabe, aber wie oben erklärt haben sie beide Probleme: Range.Text ist langsam und Zwischenablage ist eine schlechte Übung.

private void SetSampleValues() 
    { 
     var sheet = (Microsoft.Office.Interop.Excel.Worksheet) Globals.ThisAddIn.Application.ActiveSheet; 

     sheet.Cells.ClearContents(); 
     sheet.Cells.ClearFormats(); 

     var range = sheet.Range["A1"]; 

     range.NumberFormat = "General"; 
     range.Value2 = "2008-01-25 15:19:32"; 

     range = sheet.Range["A2"]; 
     range.NumberFormat = "@"; 
     range.Value2 = "2008-01-25 15:19:32"; 

     range = sheet.Range["B1"]; 
     range.NumberFormat = "0.00"; 
     range.Value2 = "5.12345"; 

     range = sheet.Range["B2"]; 
     range.NumberFormat = "0.00%"; 
     range.Value2 = ".456"; 
    } 

    private string ArrayToString(ref object[,] vals) 
    { 

     int dim1Start = vals.GetLowerBound(0); //Excel Interop will return index-1 based arrays instead of index-0 based 
     int dim1End = vals.GetUpperBound(0); 
     int dim2Start = vals.GetLowerBound(1); 
     int dim2End = vals.GetUpperBound(1); 

     var sb = new StringBuilder(); 
     for (int i = dim1Start; i <= dim1End; i++) 
     { 
      for (int j = dim2Start; j <= dim2End; j++) 
      { 
       sb.Append(vals[i, j]); 
       if (j != dim2End) 
        sb.Append("\t"); 
      } 
      sb.Append("\n"); 
     } 
     return sb.ToString(); 
    } 

    private void GetCellValues() 
    { 
     var sheet = (Microsoft.Office.Interop.Excel.Worksheet)Globals.ThisAddIn.Application.ActiveSheet; 

     var usedRange = sheet.UsedRange; 

     var sb = new StringBuilder(); 

     sb.Append("Output using Range.Value\n"); 
     var vals = (object [,]) usedRange.Value; //1-based array 
     sb.Append(ArrayToString(ref vals)); 

     sb.Append("\nOutput using Range.Value2\n"); 
     vals = (object[,])usedRange.Value2; //1-based array 
     sb.Append(ArrayToString(ref vals)); 

     sb.Append("\nOutput using Clipboard Copy\n"); 
     string previousClipboardText = Clipboard.GetText(); 
     usedRange.Copy(); 
     string clipboardText = Clipboard.GetText(); 
     Clipboard.SetText(previousClipboardText); 
     vals = new object[usedRange.Rows.Count, usedRange.Columns.Count]; //0-based array 
     ParseClipboard(clipboardText,ref vals); 
     sb.Append(ArrayToString(ref vals)); 


     sb.Append("\nOutput using Range.Text and Autofit\n"); 
     //if you dont autofit, Range.Text may give you something like ##### 
     usedRange.Columns.AutoFit(); 
     usedRange.Rows.AutoFit(); 
     vals = new object[usedRange.Rows.Count, usedRange.Columns.Count]; 
     int startRow = usedRange.Row; 
     int endRow = usedRange.Row + usedRange.Rows.Count - 1; 
     int startCol = usedRange.Column; 
     int endCol = usedRange.Column + usedRange.Columns.Count - 1; 
     for (int r = startRow; r <= endRow; r++) 
     { 
      for (int c = startCol; c <= endCol; c++) 
      { 
       vals[r - startRow, c - startCol] = sheet.Cells[r, c].Text; 
      } 
     } 
     sb.Append(ArrayToString(ref vals)); 


     MessageBox.Show(sb.ToString()); 
    } 

    //requires reference to Microsoft.VisualBasic to get TextFieldParser 
    private void ParseClipboard(string text, ref object[,] vals) 
    { 
     using (var tabReader = new TextFieldParser(new StringReader(text))) 
     { 
      tabReader.SetDelimiters("\t"); 
      tabReader.HasFieldsEnclosedInQuotes = true; 

      int row = 0; 
      while (!tabReader.EndOfData) 
      { 
       var fields = tabReader.ReadFields(); 
       for (int i = 0; i < fields.Length; i++) 
        vals[row, i] = fields[i]; 
       row++; 
      } 
     } 
    } 


    private void button1_Click(object sender, RibbonControlEventArgs e) 
    { 
     SetSampleValues(); 
    } 

    private void button2_Click(object sender, RibbonControlEventArgs e) 
    { 
     GetCellValues(); 
    } 
+0

Versuchen Sie Folgendes: Exportieren Sie das formatierte Arbeitsblatt in eine CSV-Datei. Erstellen Sie ein neues Arbeitsblatt und ** Importieren ** (nicht öffnen) die CSV-Datei. Wenn Sie dies tun, wird der Textimport-Assistent geöffnet und Sie geben jede Spalte als Text an. Sie können dann den UsedRange dieses neuen Arbeitsblatts in einem einzigen Schritt in ein Varianten-Array einfügen. –

+1

danke, aber ich denke, dass diese Methode ziemlich langsam und fehleranfällig sein wird, da Excel nicht das robusteste Programm für CSVs ist.Ich würde Excel nie vertrauen, um einen csv mit Unicode-Zeichen, führenden Nullen, Daten, Trennzeichen innerhalb des Zellenwerts, Zeilenumbruchzeichen innerhalb des Zellenwerts, usw. – jjdem

+0

zu exportieren Realisierte nicht Ihre Anforderung für Unicode-Zeichen. Ich habe nur die von Ihnen bereitgestellten Beispieldaten verwendet, was gut funktioniert hat. –

Antwort

0

Ich habe eine Teillösung gefunden. Wenden Sie den NumberFormat-Wert auf das analysierte Double von Value2 an. Dies funktioniert nur für einzelne Zellen, da ein Array für NumberFormat mit verschiedenen Formaten im Array System.DBNull zurückgibt.

Die Daten funktionieren jedoch nicht mit diesem. Wenn Sie wissen, welche Spalten bestimmte Dinge enthalten, beispielsweise ein formatiertes Datum, können Sie DateTime.FromOADate für das Double und dann value.ToString (Format) mit NumberFormat verwenden. Der folgende Code wird geschlossen, ist jedoch nicht vollständig.

<snip> 
sb.Append("\nOutput using Range.Value2\n"); 
vals = (object[,])usedRange.Value2; //1-based array 
var format = GetFormat(usedRange); 
sb.Append(ArrayToString(ref vals, format)); 
</snip> 

private static object[,] GetFormat(Microsoft.Office.Interop.Excel.Range range) 
{ 
    var rows = range.Rows.Count; 
    var cols = range.Columns.Count; 
    object[,] vals = new object[rows, cols]; 
    for (int r = 1; r <= rows; ++r) 
    { 
     for (int c = 1; c <= cols; ++c) 
     { 
      vals[r-1, c-1] = range[r, c].NumberFormat; 
     } 
    } 
    return vals; 
} 

private static string ArrayToString(ref object[,] vals, object[,] numberformat = null) 
{ 
    int dim1Start = vals.GetLowerBound(0); //Excel Interop will return index-1 based arrays instead of index-0 based 
    int dim1End = vals.GetUpperBound(0); 
    int dim2Start = vals.GetLowerBound(1); 
    int dim2End = vals.GetUpperBound(1); 

    var sb = new StringBuilder(); 
    for (int i = dim1Start; i <= dim1End; i++) 
    { 
     for (int j = dim2Start; j <= dim2End; j++) 
     { 
      if (numberformat != null) 
      { 
       var format = numberformat[i-1, j-1].ToString(); 
       double v; 
       if (double.TryParse(vals[i, j].ToString(), out v)) 
       { 
        if (format.Contains(@"/") || format.Contains(":")) 
        {// parse a date 
         var date = DateTime.FromOADate(v); 
         sb.Append(date.ToString(format)); 
        } 
        else 
        { 
         sb.Append(v.ToString(format)); 
        } 
       } 
       else 
       { 
        sb.Append(vals[i, j].ToString()); 
       } 
      } 
      else 
      { 
       sb.Append(vals[i, j]); 
      } 
      if (j != dim2End) 
       sb.Append("\t"); 
     } 
     sb.Append("\n"); 
    } 
    return sb.ToString(); 
} 
+1

danke, aber wie Sie sagten, NumberFormat funktioniert nur, wenn der gesamte Bereich das gleiche Format hat, andernfalls müssen Sie die Zellen nacheinander betrachten. Dies könnte relativ effizient funktionieren, wenn Ihre Daten eine konsistente Zahlenformatierung in Bereichssegmenten aufweisen und Sie einen Algorithmus anwenden, um Teile davon zu erhalten (z. B. prüfen, ob eine ganze Spalte das gleiche NumberFormat aufweist), aber wenn die Daten relativ zufällig sind das NumberFormat für jede Zelle und Sie werden keine Leistungssteigerung gegenüber der Verwendung der Text-Eigenschaft sehen, in der Tat wird es langsamer. – jjdem

Verwandte Themen