有效地获得格式化的单元格值

我希望能够有效地从Excel中检索格式化单元格值的multidimensional array。 当我说格式化的值,我的意思是我想要得到他们完全一样,他们出现在应用所有单元格格式的Excel中。

Range.Value和Range.Value2属性适用于将大量单元格的单元格值检索到multidimensional array中。 但是这些是实际的单元格值(至less在Range.Value2中是这样的,我不太确定Range.Value在某些值上做了什么)。

如果我想检索在单元格中显示的实际文本,我可以使用Range.Text属性。 这有一些警告。 首先,您需要自动调整单元格,否则,如果不是所有文本都以当前单元格宽度可见,则可能会得到类似于####的内容。 其次,Range.Text不适用于一个以上的单元格,因此您必须遍历范围内的所有单元格,对于大型数据集来说,这可能非常慢。

我尝试的另一种方法是将范围复制到剪贴板,然后将剪贴板文本parsing为制表符分隔的数据stream,并将其传输到multidimensional array中。 这似乎工作得很好,虽然它比得到Range.Value2慢,对于大型数据集要比获得Range.Text快得多。 但是,我不喜欢使用系统剪贴板的想法。 如果这是一个需要60秒的非常长的操作,并且该操作正在运行,则用户可能决定切换到另一个应用程序,并且将非常不高兴地发现其剪贴板不工作或者具有神秘数据。

有没有办法,我可以有效地检索格式化的单元格值到multidimensional array?

我已经添加了一些从VSTO应用程序中的几个function区button运行的示例代码。 第一个设置一些好的testing值和数字格式,第二个button将显示在MessageBox中使用这些方法之一检索时的样子。

我的系统上的示例输出是(由于区域设置,您可能会有所不同):

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% 

Range.Text和Clipboard方法产生正确的输出,但是如上所述,它们都有问题:Range.Text慢,剪贴板是不好的做法。

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

我find了一个部分解决scheme。 将NumberFormat值应用于Value2的parsing的double。 这只适用于单个单元格,因为在数组中返回具有不同格式的NumberFormat数组将返回System.DBNull。

 double.Parse(o.Value2.ToString()).ToString(o.NumberFormat.ToString()) 

date不适用于此。 如果您知道哪些列包含某些内容(如格式化的date),则可以在Double上使用DateTime.FromOADate,然后在NumberFormat上使用value.ToString(format)。 下面的代码接近但不完整。

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