【发布时间】:2015-07-11 16:38:31
【问题描述】:
我希望能够从 Excel 中高效地检索格式化单元格值的多维数组。当我说格式化值时,我的意思是我希望得到它们与它们在 Excel 中显示的完全一致,并应用了所有单元格 NumberFormat。
Range.Value 和 Range.Value2 属性非常适合将大量单元格的单元格值检索到多维数组中。但这些是实际的单元格值(至少 Range.Value2 是这样,我不太确定 Range.Value 对某些值做了什么)。
如果我想检索单元格中显示的实际文本,我可以使用 Range.Text 属性。这有一些警告。首先,您需要自动调整单元格,否则如果不是所有文本都以当前单元格宽度可见,您可能会得到类似 #### 的内容。其次,Range.Text 一次不能用于多个单元格,因此您必须循环遍历该范围内的所有单元格,这对于大型数据集可能非常慢。
我尝试的另一种方法是将范围复制到剪贴板,然后将剪贴板文本解析为制表符分隔的数据流,并将其传输到多维数组中。这似乎工作得很好,虽然它比获取 Range.Value2 慢,但对于大型数据集来说,它比获取 Range.Text 快得多。但是,我不喜欢使用系统剪贴板的想法。如果这是一个非常长的操作,需要 60 秒,并且在该操作运行时,用户可能会决定切换到另一个应用程序,并且会非常不高兴地发现他们的剪贴板要么不起作用,要么里面有神秘的数据。
有没有一种方法可以有效地将格式化的单元格值检索到多维数组中?
我添加了一些从 VSTO 应用程序中的几个功能区按钮运行的示例代码。第一个设置了一些良好的测试值和数字格式,第二个按钮将显示在 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 很慢,Clipboard 是不好的做法。
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();
}
【问题讨论】:
-
试试这个:将格式化的工作表导出到 .csv 文件。创建一个新工作表,然后导入(不要打开).csv 文件。执行此操作时,文本导入向导将打开,您将每列指定为文本。然后,您可以通过一个步骤将这个新工作表的 UsedRange 放入变量数组中。
-
谢谢,但我认为该方法会非常缓慢且容易出错,因为 Excel 不是最强大的 csvs 程序。我永远不会相信 Excel 能够正确导出包含 Unicode 字符、前导零、日期、单元格值中的分隔符、单元格值中的换行符等的 csv。
-
没有意识到您对 Unicode 字符的要求。我刚刚使用了您提供的示例数据,效果很好。
标签: c# excel vba vsto clipboard