将DataGridView数据导出到Excel的方法大家应该都不陌生,无非就是对DataGridView进行行和列的遍历,将数据写入到Excel Workbook中,不过项目中需要添加对Excel的COM引用,这个会受到客户端环境的限制。如果直接将数据以CSV的形式导出就更简单了,CSV是一种简单的以分隔符(如逗号,Tab制表符等)分隔数据的文件,支持用Excel或记事本打开。一般而言,直接将数据导出到CSV文件中速度会比较快,但是如果将数据导出到Excel标准格式的文件中时需要调用COM组件中的对象,因此速度会慢一些,这时考虑在UI中加入一个实时进度条是有必要的。如何在多线程中使用ProgressBar相信很多人也都不陌生了,最简单的方法就是使用BackgroundWorker组件,将数据导出的代码放到BackgroundWorker的DoWork事件处理程序中去完成,并实时报告进度,BackgroundWorker的ProgressChanged事件用于更新UI线程中ProgressBar的值,BackgroundWorker的RunWorkerCompleted事件则用来处理操作完成之后的一些事情,例如隐藏ProgressBar,或者显示错误信息等。但是,这里会有一个问题。数据导出的方法一般来说是一个公共方法,它允许在应用程序的不同地方被调用,因此该方法不应该依赖于任何一个窗体或UI元素,我们应该想办法将它独立出来专门写成一个类,同时它还要能支持ProgressBar的更新。
按照传统的使用BackgroundWorker的方法,我们需要将BackgroundWorker传递到这个类中,同时类里面还需要处理与BackgroundWorker相关的事件,以及与UI相关的一些事情,这显然违背了敏捷开发中的“单一职责原则——一个类只允许有一个引起它变化的原因”。引入太多与UI相关的元素会导致类不够通用,实现起来也很困难。如何从DataGridView导出数据到Excel或CSV文件应该不是什么难事,现在的主要问题是如何支持ProgressBar的进度实时更新。
一个比较好的办法是采用.NET的事件驱动方式。我们可以在类中定义几个事件,例如开始进行数据导出时的事件,数据正在导出时的事件,数据导出完成之后的事件。调用端通过注册这些事件来取得与类的联系。说白了,联系的纽带就是事件,这个和WinForm程序中各个窗体之间进行互动的原理一样。来看看这些事件的主要用途。
1. 开始进行数据导出时的事件
该事件在数据真正开始导出之前被触发,用来获取数据导出总共需要多少步(通常可以认为是DataGridView数据中行和列的总和,试具体导出方式而定)。调用方在事件处理程序中可以更新ProgressBar的属性,如Value,Maximnm,Visible等。
2. 数据正在导出时的事件
该事件主要负责更新ProgressBar的进度。我们规定每导出一行或一列数据即触发一次事件。调用方在事件处理程序中更新ProgressBar的进度。
3. 数据导出完成之后的事件
在导出数据的方法中,无论什么情况(成功结束或抛出异常)只要结束了数据导出即触发该事件,同时传递一个自定义的继承自EventArgs类的参数。调用方在事件处理程序可以根据这个参数来判断数据导出是否成功完成,从而决定是在UI线程中显示错误信息或给出工程完成的提示信息。
注意调用方在事件处理程序中必须使用Invoke通过代理来更新UI,否则会抛跨线程UI操作的异常。而对于像MessageBox.Show()这样的模态对话框,如果不是在Invoke中通过代理来调用,它会因为在非UI线程中被执行而失去模态对话框的作用。
类的完整代码:ExportDataGridViewToExcel.zip
前端调用的例子:
private
ExportDataGridViewToExcel export
=
null
;
public
DetailsTable()
{
InitializeComponent();
export
=
new
ExportDataGridViewToExcel();
export.DataGridViewControl
=
this
.dataGridView1;
export.ExportStartingEvent
+=
new
EventHandler(export_ExportStartingEvent);
export.ExportProgressingEvent
+=
new
EventHandler(export_ExportProgressingEvent);
export.ExportEndedEvent
+=
new
EventHandler
<
ExportDataGridViewToExcel.ExportEndedEventArgs
>
(export_ExportEndedEvent);
}
//
Data export end event.
void
export_ExportEndedEvent(
object
sender, ExportDataGridViewToExcel.ExportEndedEventArgs e)
{
if
(e.IsCompleted)
{
if
(
this
.InvokeRequired)
{
this
.Invoke(
new
MethodInvoker(
delegate
()
{
MessageBox.Show(
"
Complete!
"
,
"
Prompt
"
, MessageBoxButtons.OK, MessageBoxIcon.Information);
}));
}
}
else
{
if
(
this
.InvokeRequired)
{
this
.Invoke(
new
MethodInvoker(
delegate
()
{
MessageBox.Show(
this
,
"
Export file failed.\r\n{0}
"
+
e.Errors
==
null
?
string
.Empty : e.Errors.Message,
"
Errors
"
, MessageBoxButtons.OK, MessageBoxIcon.Error);
}));
}
}
if
(
this
.InvokeRequired)
{
this
.Invoke(
new
MethodInvoker(
delegate
()
{
this
.toolStripProgressBar.Visible
=
false
;
}));
}
}
//
Data export in process event.
void
export_ExportProgressingEvent(
object
sender, EventArgs e)
{
int
nowValue
=
Convert.ToInt32(sender);
if
(
this
.InvokeRequired)
{
this
.Invoke(
new
MethodInvoker(
delegate
()
{
this
.toolStripProgressBar.Value
=
nowValue;
}));
}
}
//
Data export starting event.
void
export_ExportStartingEvent(
object
sender, EventArgs e)
{
int
maxValue
=
Convert.ToInt32(sender);
if
(
this
.InvokeRequired)
{
this
.Invoke(
new
MethodInvoker(
delegate
()
{
this
.toolStripProgressBar.Value
=
0
;
this
.toolStripProgressBar.Maximum
=
maxValue;
this
.toolStripProgressBar.Visible
=
true
;
}));
}
}
//
Export to CSV.
private
void
exportToCSVToolStripMenuItem_ExportToCSV_Click(
object
sender, EventArgs e)
{
this
.saveFileDialog_CSV.FileName
=
this
.Text;
if
(saveFileDialog_CSV.ShowDialog()
==
System.Windows.Forms.DialogResult.OK)
{
export.FilePath
=
saveFileDialog_CSV.FileName;
Thread thread
=
new
Thread(
new
ParameterizedThreadStart(export.DataGridViewToCSV));
thread.Start(saveFileDialog_CSV.OpenFile());
}
}
//
Export to Excel.
private
void
exportToExcelToolStripMenuItem_ExportToExcel_Click(
object
sender, EventArgs e)
{
this
.saveFileDialog_Excel.FileName
=
this
.Text;
if
(saveFileDialog_Excel.ShowDialog()
==
System.Windows.Forms.DialogResult.OK)
{
export.FilePath
=
saveFileDialog_Excel.FileName;
Thread thread
=
new
Thread(
new
ThreadStart(export.DataGridViewToExcel));
thread.Start();
}
}
private
void
copyToolStripMenuItem_Copy_Click(
object
sender, EventArgs e)
{
DataObject d
=
this
.dataGridView1.GetClipboardContent();
Clipboard.SetDataObject(d);
}
private
void
toolStripButton_ToExcel_Click(
object
sender, EventArgs e)
{
this
.exportToExcelToolStripMenuItem_ExportToExcel.PerformClick();
}
private
void
toolStripButton_ToCSV_Click(
object
sender, EventArgs e)
{
this
.exportToCSVToolStripMenuItem_ExportToCSV.PerformClick();
}
最后三个事件处理程序依次是:DataGridView上下文菜单中的Copy事件;DataGridView上下文菜单中Export to Excel事件,可以直接使用exportToExcelToolStripMenuItem工具条按钮的PerformClick方法;DataGridView上下文菜单中的Export to CSV事件,直接使用exportToCSVToolStripMenuItem工具条按钮的PerformClick方法。