您能否使用 Excel Interop 一次粘贴一组单元格而不使用剪贴板?

发布于 2024-10-31 00:12:35 字数 561 浏览 1 评论 0 原文

我正在尝试将数据从 DataSet 传输到 Excel 工作簿中。不幸的是,我需要更多的控制,而不仅仅是通过 ADO.NET 链接到 Excel 并使用标准 SQL 来选择和插入数据,因此我使用 Excel 互操作。

我最初的算法涉及循环遍历数据集的所有表/行/项目,并单独设置 Excel 中每个单元格的公式。这可行,但传输所有数据需要将近半分钟。

我决定尝试不同的解决方案:将每个表转换为制表符分隔的字符串(使用 StringBuilderstring.Join() 的组合,将字符串复制到剪贴板,并使用互操作在 Excel Worksheet 对象上调用“粘贴”命令,

这也可以,并且将时间缩短了 50% 以上,但我对使用剪贴板进行传输有点偏执。如果用户在传输过程中开始执行涉及剪贴板的其他操作,会发生什么情况?我还想知道如果我可以直接粘贴字符串而不是使用剪贴板作为中介,是否会更快

。我的问题...是否有一些命令可以让我直接从 C# 字符串在 Excel 中“粘贴”数据块,而无需使用剪贴板?

I'm trying to transfer data from a DataSet into an Excel workbook. Unfortunately, I need more control than I can get by simply linking to Excel via ADO.NET and using standard SQL to select and insert the data, so I'm using excel interop.

My original algorithm involved looping through all the tables/rows/items of the data set and individually setting the Formula of each cell in Excel. This worked, but it was taking nearly half a minute to transfer all the data.

I decided to try a different solution: convert each table to a tab-delimited string (using a combination of StringBuilder and string.Join(), copying the string to the clipboard, and using interop to call the Paste command on the Excel Worksheet object.

This also works and cuts the time by a little more than 50%, but I'm a little paranoid about using the clipboard to transfer data. What happens if the user starts doing other things involving the clipboard while the transfer is occurring? I also wonder if it might be even faster if I could paste a string directly rather than having to use the clipboard as an intermediary.

So, that's my question...is there some command available that would allow me to "paste" a block of data at once in Excel directly from a C# string, without without having to use the clipboard?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

画中仙 2024-11-07 00:12:36

创建一个与目标范围具有相同维度的二维对象数组(第一个数组索引是行数;第二个是列数)。然后使用该数组设置范围的 value 属性。

例子:

void SetRange(Worksheet worksheet, DataSet dataSet)
{
    object[] values = GetValuesFromDataSet(dataSet);
    int rowCount = values.GetUpperBound(0);
    int columnCount = values.GetUpperBound(1);
    Range range = worksheet.Range(worksheet.Cells(1, 1), worksheetCells.(rowCount, columnCount));
    range.Value = values;
}

Create a 2-dimensional object array with the same dimensions as your target range (the first array index is the row count; the second is the column count). Then set the range's value property with that array.

Example:

void SetRange(Worksheet worksheet, DataSet dataSet)
{
    object[] values = GetValuesFromDataSet(dataSet);
    int rowCount = values.GetUpperBound(0);
    int columnCount = values.GetUpperBound(1);
    Range range = worksheet.Range(worksheet.Cells(1, 1), worksheetCells.(rowCount, columnCount));
    range.Value = values;
}
聽兲甴掵 2024-11-07 00:12:36

您可以使用 HttpResponse 对象及其 write() 方法将字符串刷新到 Excel 中。首先创建一个 Excel 模板字符串。请参考这个例子。

private static string getWorkbookTemplate()
{
    StringBuilder sb = new StringBuilder(818);
    sb.AppendFormat(@"<?xml version=""1.0""?>{0}", Environment.NewLine);
    sb.AppendFormat(@"<?mso-application progid=""Excel.Sheet""?>{0}", Environment.NewLine);
    sb.AppendFormat(@"<Workbook xmlns=""urn:schemas-microsoft-com:office:spreadsheet""{0}", Environment.NewLine);
    sb.AppendFormat(@" xmlns:o=""urn:schemas-microsoft-com:office:office""{0}", Environment.NewLine);
    sb.AppendFormat(@" xmlns:x=""urn:schemas-microsoft-com:office:excel""{0}", Environment.NewLine);
    sb.AppendFormat(@" xmlns:ss=""urn:schemas-microsoft-com:office:spreadsheet""{0}", Environment.NewLine);
    sb.AppendFormat(@" xmlns:html=""http://www.w3.org/TR/REC-html40"">{0}", Environment.NewLine);
    sb.AppendFormat(@" <Styles>{0}", Environment.NewLine);
    sb.AppendFormat(@"  <Style ss:ID=""Default"" ss:Name=""Normal"">{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Alignment ss:Vertical=""Bottom""/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Borders/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Font ss:FontName=""Verdana"" x:Family=""Swiss"" ss:Size=""12"" ss:Color=""#0000A0""/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Interior/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <NumberFormat/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Protection/>{0}", Environment.NewLine);
    sb.AppendFormat(@"  </Style>{0}", Environment.NewLine);
    sb.AppendFormat(@"  <Style ss:ID=""s62"">{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Font ss:FontName=""Calibri"" x:Family=""Swiss"" ss:Size=""11"" ss:Color=""#000000""{0}", Environment.NewLine);
    sb.AppendFormat(@"    ss:Bold=""1""/>{0}", Environment.NewLine);
    sb.AppendFormat(@"  </Style>{0}", Environment.NewLine);
    sb.AppendFormat(@"  <Style ss:ID=""s63"">{0}", Environment.NewLine);
    sb.AppendFormat(@"   <NumberFormat ss:Format=""Short Date""/>{0}", Environment.NewLine);
    sb.AppendFormat(@"  </Style>{0}", Environment.NewLine);
    sb.AppendFormat(@" </Styles>{0}", Environment.NewLine);
    sb.Append(@"{0}\r\n</Workbook>");
    return sb.ToString();
}

private static string replaceXmlChar(string input)
{
    input = input.Replace("&", "&");
    input = input.Replace("<", "<");
    input = input.Replace(">", ">");
    input = input.Replace("\"", """);
    input = input.Replace("'", "'");
    return input;
}

private static string getCell(Type type, object cellData)
{
    Object data = (cellData is DBNull) ? "" : cellData;
    if (type.Name.Contains("Int") || type.Name.Contains("Double") || type.Name.Contains("Decimal")) return string.Format("<Cell><Data ss:Type=\"Number\">{0}</Data></Cell>", data);
    if (type.Name.Contains("Date") && data.ToString() != string.Empty)
    {
        return string.Format("<Cell ss:StyleID=\"s63\"><Data ss:Type=\"DateTime\">{0}</Data></Cell>", Convert.ToDateTime(data).ToString("yyyy-MM-dd"));
    }
    return string.Format("<Cell><Data ss:Type=\"String\">{0}</Data></Cell>", replaceXmlChar(data.ToString()));
}
private static string getWorksheets(DataSet source)
{
    StringWriter sw = new StringWriter();
    if (source == null || source.Tables.Count == 0)
    {
        sw.Write("<Worksheet ss:Name=\"Sheet1\">\r\n<Table>\r\n<Row><Cell><Data ss:Type=\"String\"></Data></Cell></Row>\r\n</Table>\r\n</Worksheet>");
        return sw.ToString();
    }
    foreach (DataTable dt in source.Tables)
    {
        if (dt.Rows.Count == 0)
            sw.Write("<Worksheet ss:Name=\"" + replaceXmlChar(dt.TableName) + "\">\r\n<Table>\r\n<Row><Cell  ss:StyleID=\"s62\"><Data ss:Type=\"String\"></Data></Cell></Row>\r\n</Table>\r\n</Worksheet>");
        else
        {
            //write each row data                
            int sheetCount = 0;
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                if ((i % rowLimit) == 0)
                {
                    //add close tags for previous sheet of the same data table
                    if ((i / rowLimit) > sheetCount)
                    {
                        sw.Write("\r\n</Table>\r\n</Worksheet>");
                        sheetCount = (i / rowLimit);
                    }
                    sw.Write("\r\n<Worksheet ss:Name=\"" + replaceXmlChar(dt.TableName) +
                             (((i / rowLimit) == 0) ? "" : Convert.ToString(i / rowLimit)) + "\">\r\n<Table>");
                    //write column name row
                    sw.Write("\r\n<Row>");
                    foreach (DataColumn dc in dt.Columns)
                        sw.Write(string.Format("<Cell ss:StyleID=\"s62\"><Data ss:Type=\"String\">{0}</Data></Cell>", replaceXmlChar(dc.ColumnName)));
                    sw.Write("</Row>");
                }
                sw.Write("\r\n<Row>");
                foreach (DataColumn dc in dt.Columns)
                    sw.Write(getCell(dc.DataType, dt.Rows[i][dc.ColumnName]));
                sw.Write("</Row>");
            }
            sw.Write("\r\n</Table>\r\n</Worksheet>");
        }
    }

    return sw.ToString();
}
public static string GetExcelXml(DataTable dtInput, string filename)
{
    string excelTemplate = getWorkbookTemplate();
    DataSet ds = new DataSet();
    ds.Tables.Add(dtInput.Copy());
    string worksheets = getWorksheets(ds);
    string excelXml = string.Format(excelTemplate, worksheets);
    return excelXml;
}

public static string GetExcelXml(DataSet dsInput, string filename)
{
    string excelTemplate = getWorkbookTemplate();
    string worksheets = getWorksheets(dsInput);
    string excelXml = string.Format(excelTemplate, worksheets);
    return excelXml;
}

public static void ToExcel(DataSet dsInput, string filename, HttpResponse response)
{
    string excelXml = GetExcelXml(dsInput, filename);
    response.Clear();
    response.AppendHeader("Content-Type", "application/vnd.ms-excel");
    response.AppendHeader("Content-disposition", "attachment; filename=" + filename);
    response.Write(excelXml);
    response.Flush();
    response.End();
}

public static void ToExcel(DataTable dtInput, string filename, HttpResponse response)
{
    DataSet ds = new DataSet();
    ds.Tables.Add(dtInput.Copy());
    ToExcel(ds, filename, response);
}

You can use HttpResponse object and its write() method to flush the string into excel. Create an excel template string first. Refer to this example.

private static string getWorkbookTemplate()
{
    StringBuilder sb = new StringBuilder(818);
    sb.AppendFormat(@"<?xml version=""1.0""?>{0}", Environment.NewLine);
    sb.AppendFormat(@"<?mso-application progid=""Excel.Sheet""?>{0}", Environment.NewLine);
    sb.AppendFormat(@"<Workbook xmlns=""urn:schemas-microsoft-com:office:spreadsheet""{0}", Environment.NewLine);
    sb.AppendFormat(@" xmlns:o=""urn:schemas-microsoft-com:office:office""{0}", Environment.NewLine);
    sb.AppendFormat(@" xmlns:x=""urn:schemas-microsoft-com:office:excel""{0}", Environment.NewLine);
    sb.AppendFormat(@" xmlns:ss=""urn:schemas-microsoft-com:office:spreadsheet""{0}", Environment.NewLine);
    sb.AppendFormat(@" xmlns:html=""http://www.w3.org/TR/REC-html40"">{0}", Environment.NewLine);
    sb.AppendFormat(@" <Styles>{0}", Environment.NewLine);
    sb.AppendFormat(@"  <Style ss:ID=""Default"" ss:Name=""Normal"">{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Alignment ss:Vertical=""Bottom""/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Borders/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Font ss:FontName=""Verdana"" x:Family=""Swiss"" ss:Size=""12"" ss:Color=""#0000A0""/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Interior/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <NumberFormat/>{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Protection/>{0}", Environment.NewLine);
    sb.AppendFormat(@"  </Style>{0}", Environment.NewLine);
    sb.AppendFormat(@"  <Style ss:ID=""s62"">{0}", Environment.NewLine);
    sb.AppendFormat(@"   <Font ss:FontName=""Calibri"" x:Family=""Swiss"" ss:Size=""11"" ss:Color=""#000000""{0}", Environment.NewLine);
    sb.AppendFormat(@"    ss:Bold=""1""/>{0}", Environment.NewLine);
    sb.AppendFormat(@"  </Style>{0}", Environment.NewLine);
    sb.AppendFormat(@"  <Style ss:ID=""s63"">{0}", Environment.NewLine);
    sb.AppendFormat(@"   <NumberFormat ss:Format=""Short Date""/>{0}", Environment.NewLine);
    sb.AppendFormat(@"  </Style>{0}", Environment.NewLine);
    sb.AppendFormat(@" </Styles>{0}", Environment.NewLine);
    sb.Append(@"{0}\r\n</Workbook>");
    return sb.ToString();
}

private static string replaceXmlChar(string input)
{
    input = input.Replace("&", "&");
    input = input.Replace("<", "<");
    input = input.Replace(">", ">");
    input = input.Replace("\"", """);
    input = input.Replace("'", "'");
    return input;
}

private static string getCell(Type type, object cellData)
{
    Object data = (cellData is DBNull) ? "" : cellData;
    if (type.Name.Contains("Int") || type.Name.Contains("Double") || type.Name.Contains("Decimal")) return string.Format("<Cell><Data ss:Type=\"Number\">{0}</Data></Cell>", data);
    if (type.Name.Contains("Date") && data.ToString() != string.Empty)
    {
        return string.Format("<Cell ss:StyleID=\"s63\"><Data ss:Type=\"DateTime\">{0}</Data></Cell>", Convert.ToDateTime(data).ToString("yyyy-MM-dd"));
    }
    return string.Format("<Cell><Data ss:Type=\"String\">{0}</Data></Cell>", replaceXmlChar(data.ToString()));
}
private static string getWorksheets(DataSet source)
{
    StringWriter sw = new StringWriter();
    if (source == null || source.Tables.Count == 0)
    {
        sw.Write("<Worksheet ss:Name=\"Sheet1\">\r\n<Table>\r\n<Row><Cell><Data ss:Type=\"String\"></Data></Cell></Row>\r\n</Table>\r\n</Worksheet>");
        return sw.ToString();
    }
    foreach (DataTable dt in source.Tables)
    {
        if (dt.Rows.Count == 0)
            sw.Write("<Worksheet ss:Name=\"" + replaceXmlChar(dt.TableName) + "\">\r\n<Table>\r\n<Row><Cell  ss:StyleID=\"s62\"><Data ss:Type=\"String\"></Data></Cell></Row>\r\n</Table>\r\n</Worksheet>");
        else
        {
            //write each row data                
            int sheetCount = 0;
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                if ((i % rowLimit) == 0)
                {
                    //add close tags for previous sheet of the same data table
                    if ((i / rowLimit) > sheetCount)
                    {
                        sw.Write("\r\n</Table>\r\n</Worksheet>");
                        sheetCount = (i / rowLimit);
                    }
                    sw.Write("\r\n<Worksheet ss:Name=\"" + replaceXmlChar(dt.TableName) +
                             (((i / rowLimit) == 0) ? "" : Convert.ToString(i / rowLimit)) + "\">\r\n<Table>");
                    //write column name row
                    sw.Write("\r\n<Row>");
                    foreach (DataColumn dc in dt.Columns)
                        sw.Write(string.Format("<Cell ss:StyleID=\"s62\"><Data ss:Type=\"String\">{0}</Data></Cell>", replaceXmlChar(dc.ColumnName)));
                    sw.Write("</Row>");
                }
                sw.Write("\r\n<Row>");
                foreach (DataColumn dc in dt.Columns)
                    sw.Write(getCell(dc.DataType, dt.Rows[i][dc.ColumnName]));
                sw.Write("</Row>");
            }
            sw.Write("\r\n</Table>\r\n</Worksheet>");
        }
    }

    return sw.ToString();
}
public static string GetExcelXml(DataTable dtInput, string filename)
{
    string excelTemplate = getWorkbookTemplate();
    DataSet ds = new DataSet();
    ds.Tables.Add(dtInput.Copy());
    string worksheets = getWorksheets(ds);
    string excelXml = string.Format(excelTemplate, worksheets);
    return excelXml;
}

public static string GetExcelXml(DataSet dsInput, string filename)
{
    string excelTemplate = getWorkbookTemplate();
    string worksheets = getWorksheets(dsInput);
    string excelXml = string.Format(excelTemplate, worksheets);
    return excelXml;
}

public static void ToExcel(DataSet dsInput, string filename, HttpResponse response)
{
    string excelXml = GetExcelXml(dsInput, filename);
    response.Clear();
    response.AppendHeader("Content-Type", "application/vnd.ms-excel");
    response.AppendHeader("Content-disposition", "attachment; filename=" + filename);
    response.Write(excelXml);
    response.Flush();
    response.End();
}

public static void ToExcel(DataTable dtInput, string filename, HttpResponse response)
{
    DataSet ds = new DataSet();
    ds.Tables.Add(dtInput.Copy());
    ToExcel(ds, filename, response);
}
春风十里 2024-11-07 00:12:36

你能不能只创建一个 CSV 字符串,并使用:

File.WriteAllText()

Can you not just create a CSV string, and use:

File.WriteAllText()
不羁少年 2024-11-07 00:12:36

我建议在 VB.NET 中创建一个支持可选参数(更简洁的代码)的库,然后从 C# 调用它。与其他答案一样,这一切都取决于 Range.Value2 调用。

这是 VB.NET 中的一些示例代码

Public Function GetObjectArray(ByVal range_ref As Range) As Object(,)
    If range_ref.Count > 0 Then
        Return CType(range_ref.Value2, Object(,))
    Else
        Return New Object(,) {{range_ref.Value2}}
    End If
End Function

Public Sub SetObjectArray(ByVal range_ref As Range, ByVal values As Object(,))
    If range_ref.Count > 0 Then
        range_ref.Value2 = values
    Else
        range_ref.Value2 = values(0, 0)
    End If
End Sub

检查计数的原因是因为对于单个引用 .Value2 返回一个值,而不是可以转换为 object[,] 的数组

您实际上可以使用 object[,] 数组转换回 double[,] 数组.com/q/5083565/380384">此处发布了使用 Array.Copy() 的技巧。

Public Function GetValueArray(ByVal range_ref As Range) As Double(,)
    Dim temp As Object(,) = GetObjectArray(range_ref)
    Dim N As Integer = temp.GetLength(0)
    Dim M As Integer = temp.GetLength(1)
    Dim res As Double(,) = New Double(N - 1, M - 1) {}
    Array.Copy(temp, res, temp.Length)
    Return res
End Function

Public Sub SetValueArray(ByVal range_ref As Range, ByVal values As Double(,))
    Dim N As Integer = values.GetLength(0)
    Dim M As Integer = values.GetLength(1)
    Dim temp As Object(,) = Array.CreateInstance( _
        GetType(Object), _
        New Integer() {N, M}, _
        New Integer() {1, 1})
    Array.Copy(values, temp, values.Length)
    SetObjectArray(range_ref, temp)
End Sub

I recommend creating a library in VB.NET which supports optional parameters (cleaner code), and then call it from C#. Like the other answers it all depends on the Range.Value2 calls.

here is some sample code in VB.NET

Public Function GetObjectArray(ByVal range_ref As Range) As Object(,)
    If range_ref.Count > 0 Then
        Return CType(range_ref.Value2, Object(,))
    Else
        Return New Object(,) {{range_ref.Value2}}
    End If
End Function

Public Sub SetObjectArray(ByVal range_ref As Range, ByVal values As Object(,))
    If range_ref.Count > 0 Then
        range_ref.Value2 = values
    Else
        range_ref.Value2 = values(0, 0)
    End If
End Sub

The reason for checking the count, is because for a singular reference .Value2 returns a value and not an array that can be cast to object[,],

You can actually convert the object[,] array back and from a double[,] array using a trick posted here with Array.Copy().

Public Function GetValueArray(ByVal range_ref As Range) As Double(,)
    Dim temp As Object(,) = GetObjectArray(range_ref)
    Dim N As Integer = temp.GetLength(0)
    Dim M As Integer = temp.GetLength(1)
    Dim res As Double(,) = New Double(N - 1, M - 1) {}
    Array.Copy(temp, res, temp.Length)
    Return res
End Function

Public Sub SetValueArray(ByVal range_ref As Range, ByVal values As Double(,))
    Dim N As Integer = values.GetLength(0)
    Dim M As Integer = values.GetLength(1)
    Dim temp As Object(,) = Array.CreateInstance( _
        GetType(Object), _
        New Integer() {N, M}, _
        New Integer() {1, 1})
    Array.Copy(values, temp, values.Length)
    SetObjectArray(range_ref, temp)
End Sub
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文