C# Windows 表单 - 我如何 运行 我在不同线程上导出到 excel 以及我在哪里包含进度条的代码?
C# Windows Form - How do I run my export to excel on a different Thread AND where do I include code for a progress bar?
我有时会在数据网格视图中看到超过 100000 行,我希望用户能够更快地将它们导出到 excel。目前,我的 windows 表单处于 "Not Responding" 阶段,但它实际上是在后端执行导出。我想在不同的线程上执行导出以便更快,并且我想添加一个进度条来显示导出本身的进度。
我尝试了以下方法:
- 创建新任务 - 导出时间实际上变长了
- 使用 Thread thread = new Thread(Export) 创建与 运行 不同的线程 - 当显示对话框行为 运行
时会出错
我的代码如下:
private void BtnSearchExportCSV_Click(object sender, EventArgs e)
{
Export();
}
private void CopyAllToClipBoard()
{
dgvSearchFilter.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
dgvSearchFilter.MultiSelect = true;
dgvSearchFilter.RowHeadersVisible = false;
dgvSearchFilter.SelectAll();
DataObject dataObj = dgvSearchFilter.GetClipboardContent();
if (dataObj != null)
{
Invoke((Action)(() => { Clipboard.SetDataObject(dataObj); }));
//Clipboard.SetDataObject(dataObj);
}
}
private void releaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch (Exception ex)
{
obj = null;
MessageBox.Show("Exception Occurred while releasing object " + ex.ToString());
}
finally
{
GC.Collect();
}
}
private void Export()
{
try
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "Excel Documents (*.xls)|*.xls";
sfd.FileName = "Export.xls";
if (sfd.ShowDialog() == DialogResult.OK)
{
// Copy DataGridView results to clipboard
CopyAllToClipBoard();
object misValue = System.Reflection.Missing.Value;
Excel.Application xlexcel = new Excel.Application();
// Without this you will get two confirm overwrite prompts
xlexcel.DisplayAlerts = false;
Excel.Workbook xlWorkBook = xlexcel.Workbooks.Add(misValue);
Excel.Worksheet xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
// Paste clipboard results to worksheet range
Excel.Range CR = (Excel.Range)xlWorkSheet.Cells[1, 1];
CR.Select();
xlWorkSheet.PasteSpecial(CR, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, true);
// For some reason column A is always blank in the worksheet. ¯\_(ツ)_/¯
// Delete blank column A and select cell A1
//Excel.Range delRng = xlWorkSheet.get_Range("A:A").Cells;
//delRng.Delete(Type.Missing);
//xlWorkSheet.get_Range("A1").Select();
// Save the excel file under the captured location from the SaveFileDialog
xlWorkBook.SaveAs(sfd.FileName, Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);
xlexcel.DisplayAlerts = true;
xlWorkBook.Close(true, misValue, misValue);
xlexcel.Quit();
releaseObject(xlWorkSheet);
releaseObject(xlWorkBook);
releaseObject(xlexcel);
// Clear Clipboard and DataGridView selection
Clipboard.Clear();
dgvSearchFilter.ClearSelection();
// Open the newly saved excel file
if (File.Exists(sfd.FileName))
System.Diagnostics.Process.Start(sfd.FileName);
}
}
catch (Exception exception)
{
MessageBox.Show("The following exception occurred: " + exception.ToString());
}
}
}
我对 C# 越来越熟悉了。不过,我还是第一次遇到这样的事情。
谢谢。
将您的操作移至另一个线程不会使其更快,但它不会再阻塞 UI。用户不会看到 "unresponsive application".
因为您的代码是由 Button.Click
触发的,而 运行 是由 UI 线程触发的。
如果您的操作需要时间,您的 UI 将被您的操作阻塞。
不要将UI代码SaveFileDialog
和操作逻辑混在一起。
尽早使用 return 会提高代码的可读性。它减少了嵌套语句的大小。你可以 google 得到它。
System.Threading.ThreadStateException
发生是因为您在线程中使用了 SaveFileDialog
和 ClipBoard
。要解决此问题,您需要将这两个函数从 Thread
调用函数中移出。如果你真的想让它工作。以下可能会使它起作用。但是我不建议这种实现方式。
Thread op = new Thread( operation );
op.SetApartmentState( ApartmentState.STA );
op.Start();
下面的示例,包括您放置进度的位置 window:
private void Export()
{
// Do UI check first
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "Excel Documents (*.xls)|*.xls";
sfd.FileName = "Export.xls";
// If failed , early return
if (sfd.ShowDialog() != DialogResult.OK)
{
return;
}
ProgressWindow prg = new ProgressWindow();
prg.Show();
// Do your copy and export code below, you may use task or thread if you don't want to let current form unresponsive.
operation();
// After finished, close your progress window
prg.Close();
}
void operation()
{
// Copy DataGridView results to clipboard
CopyAllToClipBoard();
object misValue = System.Reflection.Missing.Value;
Excel.Application xlexcel = new Excel.Application();
// Without this you will get two confirm overwrite prompts
xlexcel.DisplayAlerts = false;
Excel.Workbook xlWorkBook = xlexcel.Workbooks.Add(misValue);
Excel.Worksheet xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
// Paste clipboard results to worksheet range
Excel.Range CR = (Excel.Range)xlWorkSheet.Cells[1, 1];
CR.Select();
xlWorkSheet.PasteSpecial(CR, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, true);
// For some reason column A is always blank in the worksheet. ¯\_(ツ)_/¯
// Delete blank column A and select cell A1
//Excel.Range delRng = xlWorkSheet.get_Range("A:A").Cells;
//delRng.Delete(Type.Missing);
//xlWorkSheet.get_Range("A1").Select();
// Save the excel file under the captured location from the SaveFileDialog
xlWorkBook.SaveAs(sfd.FileName, Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);
xlexcel.DisplayAlerts = true;
xlWorkBook.Close(true, misValue, misValue);
xlexcel.Quit();
releaseObject(xlWorkSheet);
releaseObject(xlWorkBook);
releaseObject(xlexcel);
// Clear Clipboard and DataGridView selection
Clipboard.Clear();
dgvSearchFilter.ClearSelection();
// Open the newly saved excel file
if (File.Exists(sfd.FileName))
System.Diagnostics.Process.Start(sfd.FileName);
}
根据您的描述,ProgressWindow 可能是 Form
without "close" button.
所以我一直在四处走动,尝试不同的方法和代码,并实现了我自己的代码。到目前为止,我遇到(和修改)的最成功和最快速的代码如下:
var headers = dgvSearchFilter.Columns.Cast<DataGridViewColumn>();
string delimiter = ",";
DataTable dt = new DataTable();
foreach (DataGridViewColumn col in dgvSearchFilter.Columns)
{
dt.Columns.Add(new DataColumn(col.Name, typeof(string)));
}
foreach (DataGridViewRow row in dgvSearchFilter.Rows)
{
DataRow dataRow = dt.NewRow();
foreach (DataGridViewCell cell in row.Cells)
{
if (row.Cells[cell.ColumnIndex].Value == null || row.Cells[cell.ColumnIndex].Value == DBNull.Value || String.IsNullOrWhiteSpace(row.Cells[cell.ColumnIndex].Value.ToString()))
{
dataRow[cell.ColumnIndex] = " ";
}
else
{
dataRow[cell.ColumnIndex] = cell.Value.ToString();
}
}
dt.Rows.Add(dataRow);
}
string unique = DateTime.Now.ToString("yyyyMMddHHmmssffff");
string fileName = "SQLQueryOutput_" + unique + ".csv";
using (StreamWriter swr = new StreamWriter(File.Open(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), fileName), FileMode.CreateNew), Encoding.Default, 1000000))
{
swr.WriteLine(string.Join(",", headers.Select(column => "\"" + column.HeaderText + "\"").ToArray()));
foreach (DataRow dr in dt.Rows)
{
var line = dr.ItemArray.Select(r => r.ToString().Contains(delimiter) || r.ToString().Contains("\n") ? "\"" + r + "\"" : r);
swr.WriteLine(string.Join(delimiter, line));
}
}
MessageBox.Show("Your file was generated and its ready for use.");
这不是 excel 格式,它是 CSV。但是,您可以在不同的线程上使用它。它会在您的桌面上生成一个带有 name_uniqueValue 的 CSV。
基本上,您将数据网格视图的列转换为逗号分隔值。然后将它们添加到 DataTable 中。一个接一个地循环遍历 DataGridView 并将值添加到 DataTable。然后使用 StreamWriter 将这些值写入 CSV。
almost/less 超过一分钟的 100 万行。
所有想要将 DataGridView 转换为 CSV 的人都可以尝试一下。
我有时会在数据网格视图中看到超过 100000 行,我希望用户能够更快地将它们导出到 excel。目前,我的 windows 表单处于 "Not Responding" 阶段,但它实际上是在后端执行导出。我想在不同的线程上执行导出以便更快,并且我想添加一个进度条来显示导出本身的进度。
我尝试了以下方法:
- 创建新任务 - 导出时间实际上变长了
- 使用 Thread thread = new Thread(Export) 创建与 运行 不同的线程 - 当显示对话框行为 运行 时会出错
我的代码如下:
private void BtnSearchExportCSV_Click(object sender, EventArgs e)
{
Export();
}
private void CopyAllToClipBoard()
{
dgvSearchFilter.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
dgvSearchFilter.MultiSelect = true;
dgvSearchFilter.RowHeadersVisible = false;
dgvSearchFilter.SelectAll();
DataObject dataObj = dgvSearchFilter.GetClipboardContent();
if (dataObj != null)
{
Invoke((Action)(() => { Clipboard.SetDataObject(dataObj); }));
//Clipboard.SetDataObject(dataObj);
}
}
private void releaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch (Exception ex)
{
obj = null;
MessageBox.Show("Exception Occurred while releasing object " + ex.ToString());
}
finally
{
GC.Collect();
}
}
private void Export()
{
try
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "Excel Documents (*.xls)|*.xls";
sfd.FileName = "Export.xls";
if (sfd.ShowDialog() == DialogResult.OK)
{
// Copy DataGridView results to clipboard
CopyAllToClipBoard();
object misValue = System.Reflection.Missing.Value;
Excel.Application xlexcel = new Excel.Application();
// Without this you will get two confirm overwrite prompts
xlexcel.DisplayAlerts = false;
Excel.Workbook xlWorkBook = xlexcel.Workbooks.Add(misValue);
Excel.Worksheet xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
// Paste clipboard results to worksheet range
Excel.Range CR = (Excel.Range)xlWorkSheet.Cells[1, 1];
CR.Select();
xlWorkSheet.PasteSpecial(CR, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, true);
// For some reason column A is always blank in the worksheet. ¯\_(ツ)_/¯
// Delete blank column A and select cell A1
//Excel.Range delRng = xlWorkSheet.get_Range("A:A").Cells;
//delRng.Delete(Type.Missing);
//xlWorkSheet.get_Range("A1").Select();
// Save the excel file under the captured location from the SaveFileDialog
xlWorkBook.SaveAs(sfd.FileName, Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);
xlexcel.DisplayAlerts = true;
xlWorkBook.Close(true, misValue, misValue);
xlexcel.Quit();
releaseObject(xlWorkSheet);
releaseObject(xlWorkBook);
releaseObject(xlexcel);
// Clear Clipboard and DataGridView selection
Clipboard.Clear();
dgvSearchFilter.ClearSelection();
// Open the newly saved excel file
if (File.Exists(sfd.FileName))
System.Diagnostics.Process.Start(sfd.FileName);
}
}
catch (Exception exception)
{
MessageBox.Show("The following exception occurred: " + exception.ToString());
}
}
}
我对 C# 越来越熟悉了。不过,我还是第一次遇到这样的事情。
谢谢。
将您的操作移至另一个线程不会使其更快,但它不会再阻塞 UI。用户不会看到 "unresponsive application".
因为您的代码是由
Button.Click
触发的,而 运行 是由 UI 线程触发的。 如果您的操作需要时间,您的 UI 将被您的操作阻塞。不要将UI代码
SaveFileDialog
和操作逻辑混在一起。尽早使用 return 会提高代码的可读性。它减少了嵌套语句的大小。你可以 google 得到它。
System.Threading.ThreadStateException
发生是因为您在线程中使用了SaveFileDialog
和ClipBoard
。要解决此问题,您需要将这两个函数从Thread
调用函数中移出。如果你真的想让它工作。以下可能会使它起作用。但是我不建议这种实现方式。Thread op = new Thread( operation ); op.SetApartmentState( ApartmentState.STA ); op.Start();
下面的示例,包括您放置进度的位置 window:
private void Export()
{
// Do UI check first
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "Excel Documents (*.xls)|*.xls";
sfd.FileName = "Export.xls";
// If failed , early return
if (sfd.ShowDialog() != DialogResult.OK)
{
return;
}
ProgressWindow prg = new ProgressWindow();
prg.Show();
// Do your copy and export code below, you may use task or thread if you don't want to let current form unresponsive.
operation();
// After finished, close your progress window
prg.Close();
}
void operation()
{
// Copy DataGridView results to clipboard
CopyAllToClipBoard();
object misValue = System.Reflection.Missing.Value;
Excel.Application xlexcel = new Excel.Application();
// Without this you will get two confirm overwrite prompts
xlexcel.DisplayAlerts = false;
Excel.Workbook xlWorkBook = xlexcel.Workbooks.Add(misValue);
Excel.Worksheet xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
// Paste clipboard results to worksheet range
Excel.Range CR = (Excel.Range)xlWorkSheet.Cells[1, 1];
CR.Select();
xlWorkSheet.PasteSpecial(CR, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, true);
// For some reason column A is always blank in the worksheet. ¯\_(ツ)_/¯
// Delete blank column A and select cell A1
//Excel.Range delRng = xlWorkSheet.get_Range("A:A").Cells;
//delRng.Delete(Type.Missing);
//xlWorkSheet.get_Range("A1").Select();
// Save the excel file under the captured location from the SaveFileDialog
xlWorkBook.SaveAs(sfd.FileName, Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);
xlexcel.DisplayAlerts = true;
xlWorkBook.Close(true, misValue, misValue);
xlexcel.Quit();
releaseObject(xlWorkSheet);
releaseObject(xlWorkBook);
releaseObject(xlexcel);
// Clear Clipboard and DataGridView selection
Clipboard.Clear();
dgvSearchFilter.ClearSelection();
// Open the newly saved excel file
if (File.Exists(sfd.FileName))
System.Diagnostics.Process.Start(sfd.FileName);
}
根据您的描述,ProgressWindow 可能是 Form
without "close" button.
所以我一直在四处走动,尝试不同的方法和代码,并实现了我自己的代码。到目前为止,我遇到(和修改)的最成功和最快速的代码如下:
var headers = dgvSearchFilter.Columns.Cast<DataGridViewColumn>();
string delimiter = ",";
DataTable dt = new DataTable();
foreach (DataGridViewColumn col in dgvSearchFilter.Columns)
{
dt.Columns.Add(new DataColumn(col.Name, typeof(string)));
}
foreach (DataGridViewRow row in dgvSearchFilter.Rows)
{
DataRow dataRow = dt.NewRow();
foreach (DataGridViewCell cell in row.Cells)
{
if (row.Cells[cell.ColumnIndex].Value == null || row.Cells[cell.ColumnIndex].Value == DBNull.Value || String.IsNullOrWhiteSpace(row.Cells[cell.ColumnIndex].Value.ToString()))
{
dataRow[cell.ColumnIndex] = " ";
}
else
{
dataRow[cell.ColumnIndex] = cell.Value.ToString();
}
}
dt.Rows.Add(dataRow);
}
string unique = DateTime.Now.ToString("yyyyMMddHHmmssffff");
string fileName = "SQLQueryOutput_" + unique + ".csv";
using (StreamWriter swr = new StreamWriter(File.Open(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), fileName), FileMode.CreateNew), Encoding.Default, 1000000))
{
swr.WriteLine(string.Join(",", headers.Select(column => "\"" + column.HeaderText + "\"").ToArray()));
foreach (DataRow dr in dt.Rows)
{
var line = dr.ItemArray.Select(r => r.ToString().Contains(delimiter) || r.ToString().Contains("\n") ? "\"" + r + "\"" : r);
swr.WriteLine(string.Join(delimiter, line));
}
}
MessageBox.Show("Your file was generated and its ready for use.");
这不是 excel 格式,它是 CSV。但是,您可以在不同的线程上使用它。它会在您的桌面上生成一个带有 name_uniqueValue 的 CSV。
基本上,您将数据网格视图的列转换为逗号分隔值。然后将它们添加到 DataTable 中。一个接一个地循环遍历 DataGridView 并将值添加到 DataTable。然后使用 StreamWriter 将这些值写入 CSV。 almost/less 超过一分钟的 100 万行。
所有想要将 DataGridView 转换为 CSV 的人都可以尝试一下。