分享一个在 WinForm 桌面程序中使用进度条展示报表处理进度的例子,提升用户体验

简介: 分享一个在 WinForm 桌面程序中使用进度条展示报表处理进度的例子,提升用户体验

前言

在有些比较消耗时间的业务场景中,比如生成报表等,如果没有在操作的过程中向用户反馈操作进度,会让用户以为程序 “死” 掉了,用户体验非常不好。

WinForm 桌面程序项目与 Console 项目不一样,如果 Console 项目,我们可以在处理业务的关键节点时,在控制台打印一些消息向用户报告进度,但是 WinForm 桌面程序项目出于线程安全的原因,是无法在处理业务的过程中同时显示 UI 控件的消息的,即你在处理业务的的关键节点时,向 UI 控件如 TextBox 写一些消息,但实际上 UI 是不会马上同时显示你写的消息的,只有在整个业务处理完后,UI 才会呈现你写的消息,但这时已经太迟了,没有太多意义。所以在 WinForm 桌面程序项目里,要在操作的过程中向用户反馈操作进度,只能通过多线程来处理,一个线程在后台处理业务,一个线程通过委托同步展示业务处理进度,不过手动写多线程代码比较麻烦,所以微软在 .NET 2.0 增加了 BackgroundWorker 类,专门用于处理这种情况。使用 BackgroundWorker 可以轻松地启动一个单独的线程上执行一些操作并进行管理,无需我们操心。

可以将它从 “工具箱” 的 “组件” 选项卡中拖到窗体上,也可以通过编程方式创建 BackgroundWorker 类,本文通过一个实际的详细例子演示如何通过编程方式使用 BackgroundWorker 类,大家可以照着做。

Step By Step 步骤

  1. 创建一个 WinForm 类型的项目
  2. 设计窗体 UI 内容,如图:

  3. 打开窗体如 FrmZqReportHandler 的代码
  4. 在顶部(构造方法之前)添加声明 BackgroundWorker 对象的代码
// 声明 BackgroundWorker 对象
private BackgroundWorker m_BackgroundWorker;

5.在构造方法中实例化及配置 BackgroundWorker 对象,(留意注释

public FrmZqReportHandler()
{
  InitializeComponent();
  m_BackgroundWorker = new BackgroundWorker();            // 实例化 BackgroundWorker 对象
  m_BackgroundWorker.WorkerReportsProgress = true;        // 设置可以通告进度
  m_BackgroundWorker.WorkerSupportsCancellation = false;  // 设置不可以取消
  // 声明 DoWork 事件
  m_BackgroundWorker.DoWork += new DoWorkEventHandler(DoWork);
  // 声明 ProgressChanged 事件
  m_BackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(UpdateProgress);
  // 声明 RunWorkerCompleted 事件
  m_BackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CompletedWork);
}

6.在按钮的单击事件里启动 BackgroundWorker,(留意注释

private void btnSelectFile_Click(object sender, EventArgs e)
{
  // 1. 选择文件
  // 代码忽略 ......
  // 2. 如果后台线程空闲,则启动后台操作
  if (!m_BackgroundWorker.IsBusy) {
    // 启动后台操作,触发 DoWork 事件
    // 参数可传可不传,暂时没什么有什么作用
    m_BackgroundWorker.RunWorkerAsync(this);
  }
}

7.编写 DoWork 事件,DoWork 事件中的代码就是后台线程要执行的业务处理方法,(留意注释

void DoWork(object sender, DoWorkEventArgs e)
{
  BackgroundWorker bw = sender as BackgroundWorker;
  // 1. 处理业务逻辑 01
  // 处理业务逻辑代码 ......
  // 传递进度,第一个参数是进度值,第二个参数是附加对象,此方法会触发 ProgressChanged 事件
  bw.ReportProgress(10, obj);
  // ...... 处理业务逻辑代码
  // 7. 处理业务逻辑 07,业务处理完成
  // 处理业务逻辑代码 ......
  // 传递进度,将进度值设置为进度条控件的最大值
  bw.ReportProgress(100, fileUrl);
}

8.编写 ProgressChanged 事件,注意:在这个事件里跟 UI 控件交互,(留意注释

void UpdateProgress(object sender, ProgressChangedEventArgs e)
{
  int progress = e.ProgressPercentage;
  // 根据 `DoWork` 事件传递过来的进度值不断更新进度条的值
  pgBar.Value = progress;
  // 根据进度值向填写业务处理细节,提高用户体验
  if (progress == 1)
  {
    txtPg.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} ......\r\n");
  }
  else if (progress == 10)
  {
    txtPg.AppendText(......
  }
  // ......
  else if (progress == 100)
  {
    txtPg.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 数据处理后是 [{e.UserState}]\r\n");
  }
}

9.编写 RunWorkerCompleted 事件,此事件在后台操作已完成、被取消或引发异常时发生

void CompletedWork(object sender, RunWorkerCompletedEventArgs e)
{
  if (e.Error != null)
  {
    txtPg.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 处理过程中出现错误:[{e.Error}]\r\n");
  }
  else
  {
    txtPg.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 处理完毕");
  }
}

10.至此,代码就完成了,运行效果如下:

总结

  1. BackgroundWorker 类虽然是旧技术,但它在处理 WinForm 业务处理进度,提高用户体验上却是非常好用的,比直接用多线程方便很多,这可见,技术不分新旧,用在恰当的地方最重要。
  2. BackgroundWorker类几个常用属性
  1. WorkerReportsProgress: 设置能否报告进度,通常设置为 true
  2. WorkerSupportsCancellation: 设置能否中途取消,按需设置
  3. IsBusy: 判断后台线程是否正在工作中,只读属性
  4. CancellationPending: 指示应用程序是否已请求取消后台操作,只读属性
  1. BackgroundWorker类几个常用方法
  1. RunWorkerAsync: 启动后台操作,此方法会触发 DoWork 事件
  2. CancelAsync: 请求取消挂起的后台操作,此方法只是将 CancellationPending 属性设置为 true,需要在 DoWork 事件中检查 CancellationPending 属性,来决定是否要继续取消后台操作
  3. ReportProgress: 报告进度,此方法会触发 ProgressChanged 事件
  1. BackgroundWorker类几个常用事件
  1. DoWork: 后台线程,一般在这里处理业务逻辑,不能操作 UI 控件
  2. ProgressChanged: 报告业务处理进度,可以操作 UI 控件
  3. RunWorkerCompleted: 后台操作已完成、被取消或引发异常时触发,可以操作 UI 控件
  1. BackgroundWorker类使用步骤:
  1. 新建 BackgroundWorder 对象
  2. 根据需求, 设置是否能取消、是否报告进度等
  3. 根据需求,设置好相关事件,DoWorker、ProgressChanged、RunWorkerCompleted
  4. 调用 RunWorkerAsyns() 方法,启动线程;
  5. DoWork 在需要取消的位置,判断 CancellationPending 的值,并做相关处理;
  6. DoWork 在适当的位置调用 ReportProgress方法,报告进度

我是老杨,一个奋斗在一线的资深研发老鸟,让我们一起聊聊技术,聊聊人生。

都看到这了,求个点赞、关注、在看三连呗,感谢支持。


相关文章
|
JavaScript
Fastadmin列表的多图预览(一行代码)
Fastadmin列表的多图预览(一行代码)
363 0
|
3月前
|
编解码 前端开发 vr&ar
从零开始的PICO教程(4)--- UI界面绘制与响应事件
这篇文章是PICO开发系列教程的第四部分,主要介绍了如何在PICO 4 VR环境中创建UI界面,包括Canvas和Panel的配置、UI元素的绘制、以及Button和Slider的事件响应绑定,并通过示例展示了数字增减和滑块功能的具体实现。
从零开始的PICO教程(4)--- UI界面绘制与响应事件
|
3月前
|
存储 UED
Winform下拉列表的魔力:解锁字典数据展示的多种炫酷方式,让用户体验再升级!
【8月更文挑战第3天】在Winform开发中,下拉列表(ComboBox)常用于让用户从预设列表中选择。展示字典数据时,可根据需求选择方法:直接显示键、键值组合显示或保持键值关联。直接显示键适合键即信息的情况;键值组合显示则通过拼接实现;若需保持键值关联,则可利用`KeyValuePair`作为数据源,结合`DisplayMember`和`ValueMember`属性实现。具体实现见示例代码。
118 0
|
6月前
|
容器
怎样实现单个图表全屏功能?
怎样实现单个图表全屏功能?
|
6月前
【实用】一个移动端简单的UI弹窗组件,虽算不上高大上,但至少耐看
【实用】一个移动端简单的UI弹窗组件,虽算不上高大上,但至少耐看
|
缓存
《QT从基础到进阶·二十二》QGraphicsView显示大量图形项item导致界面卡顿的解决办法
《QT从基础到进阶·二十二》QGraphicsView显示大量图形项item导致界面卡顿的解决办法
589 0
|
人工智能 小程序 JavaScript
小程序滑动、点击切换简洁UI
开发遇到了,就简单的记录下,小程序滑动或者点击切换样式这里不做演示了,直接上代码wxml js 需要写的代码放在内容1替换即可,即可实现轻松的点击或者滑动切换窗口,js数据交互统一处理即可,同一加载只是显示的排版布局而已,其他一样的...
297 0
小程序滑动、点击切换简洁UI
|
JSON 数据可视化 JavaScript
UI库组件属性太多不知道啥意思?没关系来看看可视化设置(一)
UI库提供了很多组件,组件又带有很多属性,有一些常用属性我们可以记住并且手撸,但是有些不常用的属性,或者需要设置多个属性,这样的情况下写起来就麻烦了,有时候还要打开帮助文档看看属性是怎么设定的,需要设置什么样的属性值。那么有没有优雅的方式来设置组件的各种属性呢?我做了一个在线小工具,可以方便的设置属性,并且可以实时看到效果。
UI库组件属性太多不知道啥意思?没关系来看看可视化设置(一)
|
JSON JavaScript 数据可视化
UI库组件属性太多不知道啥意思?没关系来看看可视化设置(二)
UI库提供了很多组件,组件又带有很多属性,有一些常用属性我们可以记住并且手撸,但是有些不常用的属性,或者需要设置多个属性,这样的情况下写起来就麻烦了,有时候还要打开帮助文档看看属性是怎么设定的,需要设置什么样的属性值。那么有没有优雅的方式来设置组件的各种属性呢?我做了一个在线小工具,可以方便的设置属性,并且可以实时看到效果。
UI库组件属性太多不知道啥意思?没关系来看看可视化设置(二)