Winform 实现进度条弹窗和任务控制

最近要给一个 Winform 项目添加功能,需要一个能显示进度条的弹窗,还要求能够中止任务,所以就做了一个,在此做个记录总结。虽然用的是比较老的 Winform 技术,不过其中的原理都是相通的。

一、弹窗前台

首先提供一个 Winform 控件居中的小技巧:

将控件放在 TableLayoutPanel 容器中,然后将控件的 Anchor 属性设置为 None,这样控件就能在容器中居中了:

将容器的 Anchor 属性设置为 Top, Left, Right,这样容器就能随着窗口左右拉伸了:

最终弹窗界面如下:

使用了 CSkin 界面库(v16.1.14.3),(注意:如果拖拽 dll 到工具箱拖不了,可以使用右键复制粘贴的方式),窗体继承 Skin_DevExpress,进度条使用 SkinProgressBar,按钮使用 SkinButton,主要是使用了一些圆角效果:

二、弹窗后台

先添加两个事件供外界订阅,分别为窗体载入时触发的执行操作事件,和点击中止按钮后触发的终止操作事件:

/// <summary>
/// 执行操作事件
/// </summary>
public event Action OperateAction;

/// <summary>
/// 终止操作事件
/// </summary>
public event Action AbortAction;

/// <summary>
/// 中止按钮点击事件
/// </summary>
private void btn_Abort_Click(object sender, EventArgs e)
{
  AbortAction?.Invoke();
  DialogResult = DialogResult.Abort;
  //Close(); //不需要手动关闭;
}

/// <summary>
/// 窗体载入事件
/// </summary>
private void FormProgressDialog_Load(object sender, EventArgs e)
{
  Task.Factory.StartNew(() =>
  {
    OperateAction?.Invoke();
    DialogResult = DialogResult.OK;
  });
}

点击中止按钮后还将弹窗结果设为 Abort,会自动关闭弹窗;而业务操作正常执行完毕,弹窗结果为 OK。

供外界设置文本信息以及进度条进度的方法如下:

/// <summary>
/// 设置显示信息(值为null时保持不变)
/// </summary>
/// <param name="rtfTitleContent">富文本格式的标题内容</param>
/// <param name="totalMessage">总体消息</param>
/// <param name="currentMessage">当前消息</param>
public void SetInfo(string rtfTitleContent = null, string totalMessage = null, string currentMessage = null)
{
  if (rtfTitleContent != null) rtb_Title.Rtf = rtfTitleContent;
  if (totalMessage != null) lbl_Total.Text = totalMessage;
  if (currentMessage != null) lbl_Current.Text = currentMessage;
}

/// <summary>
/// 设置进度
/// </summary>
/// <param name="currentValue">当前数值</param>
/// <param name="totalValue">总数值</param>
public void SetProsess(double currentValue, double totalValue)
{
  try
  {
    progressBar.Value = (int)(currentValue / totalValue * 100);
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex);
  }
}

剩下就是两个设置富文本框 RichTextBox 的方法,包括设置彩色内容和隐藏 RichTextBox 光标的方法,文末会给出代码地址,此处不再赘述。

三、使用方法

首先映入眼帘的是两个成员变量,一个是用于任务取消的 CancellationTokenSource 对象,另一个是用于线程同步的 AutoResetEvent 对象(用于取消任务后的一些信息同步);然后是主测试方法(一个按钮点击事件方法)中的一些信息设置:

然后设置 CancellationTokenSource 对象的 Token,给它注册一个取消任务时调用的委托方法,里面先等待同步信号结果再进行本次执行结果的判断:

接下来订阅弹窗中的那两个事件,在执行操作事件中开启任务,并传递 Token;在中止事件中停止任务:

需要注意的是,停止任务后,任务内部并不会自己停止,需要判断 Token 的 IsCancellationRequested 字段来决定相应的操作,比如结束循环。然后,因为在之前注册的取消的委托方法中,进行了等待,所以我们在执行完业务方法(BusinessMethod)并设置好相关状态值后,需要判断任务是否取消,如果取消,说明注册的取消的委托方法中已经在等待了,所以要调用 Set () 进行放行。

有人可能就会问了,foreach 循环开始时不是判断过是否取消了吗?这里怎么又判断?这是因为,比如在一轮循环中,已经执行过了开头的是否已取消的判断(IsCancellationRequested 为 false),开始执行耗时的业务方法了,此时用户点击中止按钮,IsCancellationRequested 被置为 true,所以业务方法执行后再次判断会得到最新的状态,然后,循环将在下一轮开始时结束。

另外,由于实际使用这个的项目是 .NET 4.0 框架,所以 Task 的一些方法没有,大家用新框架的话可以使用新方法。或者使用 Microsoft.Bcl.Async 包,然后使用 TaskEx。

继续流程,接下来以模态框方式弹出窗口,并获取结果。业务处理方法中模拟了耗时操作并返回是否成功。

最后给出完整代码:

#region 测试任务进度条弹窗

private CancellationTokenSource _Cts; //任务取消令牌;
private AutoResetEvent _AutoResetEvent = new AutoResetEvent(false);//参数传 false,则 WaitOne 时阻塞等待;

/// <summary>
/// 测试任务进度弹窗
/// </summary>
private void BtnProgressDialog_Click(object sender, EventArgs e)
{
  _AutoResetEvent.Reset();
  string businessName = "业务1";

  FormProgressDialog progressWindow = new FormProgressDialog()
  {
    Text = "任务处理窗口",
  };

  progressWindow.SetColorfulTitle("业务1 ", Color.DarkOrange, true);
  progressWindow.SetColorfulTitle("正在执行中......", Color.Black);
  progressWindow.SetInfo(null, "", "");

  List<string> orders = new List<string>(){"订单1", "订单2", "订单3", "订单4", "订单5" }; //业务数据;
  List<string> leftList = orders.Select(x => x).ToList(); //剩余(未处理)数据;
  int successCount = 0; //成功数量;

  _Cts = new CancellationTokenSource();

  //注册一个将在取消此 CancellationToken 时调用的委托;
  _Cts.Token.Register(async () =>
  {
    ShowInfo("操作终止");

    await Task.Run(() =>
    {
      _AutoResetEvent.WaitOne(1000 * 5); //等待有可能还在执行的业务方法;

      if (successCount < orders.Count)
      {
        ShowInfo($"{businessName} 有 {orders.Count - successCount} 项任务被终止,可在消息框中查看具体项。");

        foreach (var leftName in leftList)
        {
          ShowInfo($"【{businessName}】的【{leftName}】执行失败,失败原因:【手动终止】。");
        }
      }
    });

  });

  progressWindow.OperateAction += () =>
  {
    Task task = new Task(() =>
    {
      foreach (var order in orders)
      {
        //判断是否被取消;
        if (_Cts.Token.IsCancellationRequested)
        {
          break;
        }

        progressWindow.TryBeginInvoke(new Action(() =>
        {
          progressWindow.SetInfo(null, $"共{orders.Count}项,已执行{successCount}项", $"当前正在执行:{order}");
        }));

        if (BusinessMethod(order, businessName))
        {
          successCount++;
          leftList.RemoveAll(x => x == order);

          if (_Cts.Token.IsCancellationRequested)
          {
            _AutoResetEvent.Set(); //放行 Register 委托处的等待;
          }
        }

        progressWindow.TryBeginInvoke(new Action(() =>
        {
          progressWindow.SetProsess(orders.IndexOf(order) + 1, orders.Count);
        }));
      }
    }, _Cts.Token);

    task.Start();
    task.Wait();
  };

  progressWindow.AbortAction += () =>
  {
    _Cts.Cancel();
  };

  var result = progressWindow.ShowDialog();
  int leftCount = orders.Count - successCount;
  if (result == DialogResult.OK || leftCount <= 0)
  {
    ShowInfo($"{businessName} 整体完成。");
  }
  else if (result == DialogResult.Abort)
  {
    //移到 _Cts.Token.Register 处一起判断,不然数目可能不准;
    //ShowInfo($"{businessName} 有 {leftCount} 项任务被终止,可在消息框中查看具体项。");
  }
}

/// <summary>
/// 业务处理方法
/// </summary>
private bool BusinessMethod(string order, string businessName)
{
  string errStr = $"【{businessName}】的 {order} 任务失败,失败原因:";

  //测试
  Thread.Sleep(1000 * 2);

  try
  {
    //业务方法;

    ShowInfo($"【{businessName}】的 {order} 任务执行成功。");
    return true;
  }
  catch (Exception ex)
  {
    ShowInfo($"{errStr}{ex.Message}");
  }

  return false;
}

#endregion

四、效果展示和代码地址

正常执行(动图):

中止执行(动图):

代码地址:https://gitee.com/dlgcy/Practice/tree/master/WinFormPractice

转载自 独立观察员•博客

以上就是Winform 实现进度条弹窗和任务控制的详细内容,更多关于Winform 进度条弹窗和任务控制的资料请关注我们其它相关文章!

(0)

相关推荐

  • WinForm实现窗体最大化并遮盖任务栏的方法

    本文实例讲述了WinForm实现窗体最大化并遮盖任务栏的方法.分享给大家供大家参考.具体实现方法如下: using System; using System.Windows.Forms; using System.Drawing; namespace CSImageFullScreenSlideShow { public class FullScreen { private FormWindowState winState; private FormBorderStyle brdStyle; p

  • 在Winform动态启动、控制台命令行的方法

    需求winForm 程序输出类型为 windows 程序(不是命令行程序)在运行时想输入一些信息编译开发调试,如何实现这一功能 解答: AllocConsole.FreeConsole 这两个 API 可以在任何时候调用和关闭 命令行. 代码演示:API 部分 复制代码 代码如下: using System.Runtime.InteropServices; namespace WindowsFormsApplication1{    public partial class NativeMeth

  • visual studio 2019使用net core3.0创建winform无法使用窗体设计器

    微软发布正式版net core3.0后,迫不及待的想体验一下用visual studio 2019在net core3.0下创建winform程序.创建方法很简单,和以前visual studio版本步骤差不多. 创建完成之后,尴尬的事情发生了,无法使用窗体设计器,双击Form1.cs文件不行,使用快捷键shift+F7也不行,在网上找了很久,发现好多人都遇到过这种问题,目前有两种解决方案 方案1 项目中创建多目标框架,包含net framework和net core. 打开csproj文件,将

  • C#中winform控制textbox输入只能为数字的方法

    本文实例讲述了C#中winform控制textbox输入只能为数字的方法.分享给大家供大家参考.具体实现方法如下: 添加keyPress事件,控制键盘输入只能是自然数: 复制代码 代码如下: /// <summary> /// 控制键盘输入只能是自然数 /// </summary> /// <param name="sender"></param> /// <param name="e"></para

  • C# Winform调用百度接口实现人脸识别教程(附源码)

    百度是个好东西,这篇调用了百度的接口(当然大牛也可以自己写),人脸检测技术,所以使用的前提是有网的情况下.当然大家也可以去参考百度的文档. 话不多说,我们开始: 第一步,在百度创建你的人脸识别应用 打开百度AI开放平台链接: 点击跳转百度人脸检测链接,创建新应用 创建成功成功之后.进行第二步 第二步,使用API Key和Secret Key,获取 AssetToken 平台会分配给你相关凭证,拿到API Key和Secret Key,获取 AssetToken 接下来我们创建一个AccessTo

  • C# Winform下载文件并显示进度条的实现代码

    方法一: 效果如下图所示: 代码如下: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace WinShowDown { public partial class F

  • C# Winform实现波浪滚动效果

    本文实例为大家分享了C# Winform实现波浪滚动效果的具体代码,供大家参考,具体内容如下 设计思路 1.首先,理解一个概念:正弦波,余弦波.相信接受过九年义务教育的同志都清楚, 听说某华大学的高材生表示小学一年级就会计算三角函数,~~~~. 2.本人使用的是C#实现的,至于有人说JAVA效率更高,那也可以使用JAVA,只要原理清晰就ok. 3.碍于本人算法技术的局限,最终产生的效果并不是最优解,人山人海的CSDN里希望能有读者看完我的见解后能提出更好的算法思想!:) 4.既然是平面运动,我们

  • C# Winform中如何绘制动画示例详解

    前言 这里介绍一个.net自身携带的类ImageAnimator,这个类类似于控制动画的时间轴,使用ImageAnimator.CanAnimate可以判断一个图片是否为动画,调用ImageAnimator.Animate可以开始播放动画,即每经过一帧的时间触发一次OnFrameChanged委托,我们只要在该委托中将Image的活动帧选至下一帧再迫使界面重绘就可以实现动画效果了. 为了方便以后的使用,我将这些代码整合到了一起,形成一个AnimateImage类,该类提供了CanAnimate.

  • Winform应用程序如何使用自定义的鼠标图片

    首先,建立图片与鼠标的对应关系. class MouseStyle { [DllImport("user32.dll")] public static extern IntPtr SetCursor(IntPtr cursorHandle); static MouseStyle() { InitMouseStyle(); } private static void InitMouseStyle() { if (Hand == null) { Hand = SetCursor("

  • winform 实现控制输入法

    这里文章写出来并不是为了炫耀什么,只是觉得发现些好东西就分享出来而已,同时也做个记录,方便以后查找 开始正文 1.先介绍本文会用到的windows的API,网上有很详细的资料,我这里就只简要说明一下 ImmGetContext(IntPtr hwnd):获取当前正在输入的窗口的输入法句柄 ImmSetOpenStatus(IntPtr himc, bool b):设置输入法的状态 InputLanguage类:提供方法和字段以管理输入语言:这是winform里面自带的输入法管理类,msdn上有详

  • C# WinForm-Timer控件的使用

    比如在窗体中显示时间: 错误思路一:我在窗体结构函数中写入一个死循环,每隔一秒显示一次当前时间 public Form6() { InitializeComponent(); while (true) { label1.Text = DateTime.Now.ToString("yyyy年MM月dd日hh时mm分ss秒"); System.Threading.Thread.Sleep(1000); } } 错误原因:结构函数无限循环,结构函数读不完代码是无法打开窗体的 错误思路二:放置

随机推荐