SegeX Progress:MFC通用进度条

----哆啦刘小洋 原创,转载需说明出处

1 简介

SegeX组件之一:SegeX EvProgress。应用级源代码,首次公开。EvProgress是基于MFC的通用进度条,功能强大又简单易用。几行代码就可以让你的计算具有进度显示的功能,并且具有多种高级功能:1. 支持任意多个进度条同时存在;2.支持在子函数中继续主函数中的进度;3.支持两级子进度条;4.加入进度条后可避免长时间计算的”应用程序没有响应”;5.支持调节大小、背景颜色、背景图片;6.全自动管理。
源代码下载附后。

2 进度条实现基本原理

“工程地球物理系统(SegeX)“公开的原创组件之一。
本篇文章并不打算深入到实现原理的末端细节,感兴趣的读者可以阅读源代码,这里仅从宏观介绍一下实现原理。
1)全局模式。组件利用专门的处理类CEvProgressManager,管理任意多个进度条窗口CEvProgressWin。用户接口使用全局宏EV_START_NEW_PROGRESS_AUTO()来声明一个新的进度条。
2)栈管理。当声明一个新的进度条后,先前的进度条被压栈,因此这之后所有的进度操作都是针对当前的进度条。
3)自动释放。EV_START_NEW_PROGRESS_AUTO声明时,后台组件辅助类CEvProgressAutoDestroy会记录当前进度条,当EV_START_NEW_PROGRESS_AUTO所在的作用域结束时,CEvProgressAutoDestroy自动释放本次声明的进度条。
4)子函数中仍然可以继续主函数的进度,且子函数与主函数完全脱耦,无参数传递,不用关心也不需要知道是谁调用,也不用关心进度占主进度多少(按100%处理即可)。
5)全局设置。组件利用全局函数设置进度条的尺寸、背景颜色、背景图片等全局参数。
6)局部设置。组件在新开进度条之后,也可以对该进度条进行专门的背景图片设置等。

3 使用方法

首先,该组件是针对MFC Windows桌面应用程序,不支持控制台程序。因此假定你的工程是MFC Windows桌面应用程序,例如对话框程序。
1)将ev_progress.h 、ev_progress.cpp复制到工程指定目录。
2)在要使用的文件中增加头文件:#include “ev_progress.h”
3)ev_progress.cpp会用到预编译头文件,如果这里出错,请改为你的工程预编译头文件。(vc6一般是stdafx.h,而VC2022是pch.h)
4)然后可以开始使用了。详见后续多种使用场景。

3 .1 简单情况

最简单也是最常用的使用场景是在一个函数中,需要长时间计算,这时给一个进度条可提升交互体验,并可避免长时间纯计算引起的计算机假死(应用程序没有响应)现象。
举例说明:

void test_Progress_Simple()
{
	int i;

	//开始一个进度条
	EV_START_NEW_PROGRESS_AUTO(_T("Title"), _T("Prompt..."), NULL, 0, 1, 0);
	for (i = 0; i < 10000; ++i)
	{
		//设置进度条进度
		EV_DO_PROGRESS_CHECK_ABORT(i * 1.0 / 10000, NULL, NULL)
			return; //如果点击了“取消”按钮
		EV_END_DO_PROGRESS_CHECK_ABORT()
	}	
}

在这里插入图片描述

代码中主要有三行控制进度条的代码:

1)EV_START_NEW_PROGRESS_AUTO(lpstrTitle, lpstrMainPrompt, lpstrSubPrompt, dStart, dEnd, nShowSub):申明一个新的进度条。
lpstrTitle:进度条窗口的标题
lpstrMainPrompt:主进度条提示
lpstrSubPrompt:子进度条提示
dStart:主进度起始值。组件的进度基于0~1。
dEnd:主进度结束值。
nShowSub:是否显示子进度。0:不显示,1:显示。

2)EV_DO_PROGRESS_CHECK_ABORT(dValue, lpstrMainPrompt, lpstrSubPrompt):设置进度条进度的语句。
dValue:进度值。进度基于0~1。
lpstrMainPrompt:主进度条提示。这里可以改变它,不需要改变,设为NULL。
lpstrSubPrompt:子进度条提示。这里可以改变它,不需要改变,设为NULL。

3)EV_END_DO_PROGRESS_CHECK_ABORT():结束进度条进度的设置
组件支持取消操作,因此在这两句中间的代码,就是点击了“取消”之后,你需要执行的代码。如果不允许取消,两句中间可以空着。
组件在执行EV_DO_PROGRESS_CHECK_ABORT时,会处理Windows消息队列,防止消息堵塞,因此可以防止计算机没有响应的现象。

3.2 两个前后独立的进度条

组件支持顺序任意多个进度条,操作总是针对最后一个进度条操作。
举例说明:

//两个前后独立的进度条
void test_Progress_2()
{
	int i, j;

	//用应用程序的主图标作为进度条窗口图标
	EV_SET_PROGRESS_WINDOW_ICON(128);//IDR_MAINFRAME

	//开始一个进度条
	EV_START_NEW_PROGRESS_AUTO(_T("Title"), _T("Prompt..."), NULL, 0, 1, 0);
	for (i = 0; i < 10000; ++i)
	{
		//插入一个新的进度条
		if (i == 0)
		{
			EV_START_NEW_PROGRESS_AUTO(_T("Title2"), _T("Prompt2..."), NULL, 0, 1, 0);
			for (j = 0; j < 10000; ++j)
			{
				EV_DO_PROGRESS_CHECK_ABORT(j * 1.0 / 10000, NULL, NULL)
					; //如果点击了“取消”按钮,什么也不做(也即是不能取消)
				EV_END_DO_PROGRESS_CHECK_ABORT()
			}
		}
		
		//继续第一个进度条
		EV_DO_PROGRESS_CHECK_ABORT(i * 1.0 / 10000, NULL, NULL)
			return; //如果点击了“取消”按钮,终止函数运行
		EV_END_DO_PROGRESS_CHECK_ABORT()
	}
}

代码在新建一个进度条之后,马上建立了第二个进度条并操作,第二个进度条结束后,继续第一个进度条的操作。
同时该段代码用EV_SET_PROGRESS_WINDOW_ICON()函数设置了一下窗口的图标。你需要根据你的工程设置图标资源号。

3.3 实际应用的一般情况

实际应用中一般经常遇到进程分几段,每段会占用总进度一定占比,而每段又是调用的子函数,比如下面的示意性代码:

void xxx()
{
	//第1段 占总进度的 %0 ~ %30 
	Sub_Function1();

	//第2段 占总进度的 %30~100%
	Sub_Function2();
}

想实现上述功能,使用EvProgress将变得十分简单,实例如下:

//一般情况
bool Sub_Function1(int n)
{
	int j;
	for (j = 0; j < n * 1000; j++)
	{
		//只需关心本过程的进度值
		EV_DO_PROGRESS_CHECK_ABORT(j * 1.0 / (n * 1000), NULL, NULL);//按占比100%进度处理
			return false;
		EV_END_DO_PROGRESS_CHECK_ABORT();
	}
	return true;
}
void test_Progress_Normal()
{
	//开始一个进度条
	EV_START_NEW_PROGRESS_AUTO(_T("Title"), _T("Prompt..."), NULL, 0, 1, 0);

	//第1段 %0~%30 
	EV_SET_PROGRESS_RANGE(0, 0.3); //设置后续进度在总进度的占比范围
	Sub_Function1(30);

	//第2段 %30~100%
	EV_SET_PROGRESS_RANGE(0.3, 1); //设置后续进度在总进度的占比范围
	Sub_Function1(70);
}

如代码所示,在第一段,只需要用EV_SET_PROGRESS_RANGE设置该段在总进度的占比范围,然后调用子函数即可。而在子函数呢,只需按照100%的进度去赋进度值即可。第二段和第一段一样,只需要先设置该段在总进度的占比范围(0.3, 1)即可。

3.4 带子进度条

有时计算时间很长,主函数调用子函数,或主循环中带大计算量的子循环,为了防止看上去一动不动,组件支持一级子进度。
举例说明:

//带子进度条
bool Sub_Function();
void test_Progress_Sub()
{
	int i;

	EV_START_NEW_PROGRESS_AUTO(_T("waiting..."), _T("Main:"), _T("Sub"), 0.0, 1.0, 1/*子进度条数为1(1个子进度*/);
	for(i =0 ; i< 50; i++)
	{
        //因为外层循环是50次,所以每次子循环占据主进度1/50。模式为按比例。
		EV_START_SUB_PROGRESS(1.0/50/*本次子进度占主进度设置范围的1/50*/, 
                              EV_PROGRESS_MODE_SCALE_ABSOLUTE/*子进度按主进度占比通知主进度*/);
		
		if(!Sub_Function())
			return;

		EV_END_SUB_PROGRESS();
	}
}
bool Sub_Function()
{
	int j;
	for(j =0; j< 1000; j++)
	{
		//只需关心本过程的进度值
		EV_DO_PROGRESS_CHECK_ABORT((j*1.0 / 999), NULL, NULL);
			return false;
		EV_END_DO_PROGRESS_CHECK_ABORT();
	}
	return true;
}

在这里插入图片描述

EV_START_SUB_PROGRESS(dRange, nMode):开始一个子进度。
dRange:子进度占总进度值(0~1)。
nMode:
EV_PROGRESS_MODE_SCALE_ABSOLUTE:占主进度总范围比例(取值:0~1)
EV_PROGRESS_MODE_RANGE_ABSOLUTE:占主进度初始范围值(取值:0~1)

注意EV_DO_PROGRESS_CHECK_ABORT()总是针对当前计算过程中赋值。因此组件使用非常灵活实用。比如一个子过程可能被2个主函数调用(上面代码中Sub_Function作为一个子函数),并且占2个主函数的计算量不一样,但通过上述代码,只需主函数指定子函数的进度比,子函数就可以在2个不同地方良好工作。
甚至还有第三个主函数调用了子函数,但主函数并没有申请进度条,子函数中的EV_DO_PROGRESS_CHECK_ABORT()也会正常工作,只是什么都不做而已,但不会引起程序崩溃。

4 其他

4.1 使用限制

在一个作用域(就是一对{})里,只能声明一次EV_START_NEW_PROGRESS_AUTO()。例如下代码会产生编译错误:

void test_error()
{
	EV_START_NEW_PROGRESS_AUTO(_T("waiting1..."), _T("Main1:"), _T("Sub1"), 0.0, 1.0, 1);
    
    //...
    
    EV_START_NEW_PROGRESS_AUTO(_T("waiting2..."), _T("Main2:"), _T("Sub2"), 0.0, 1.0, 1);   
}

增加一个作用域,可以解决,代码如下:

void test_error()
{
   EV_START_NEW_PROGRESS_AUTO(_T("waiting1..."), _T("Main1:"), _T("Sub1"), 0.0, 1.0, 1);
   
   //...
   {
   	EV_START_NEW_PROGRESS_AUTO(_T("waiting2..."), _T("Main2:"), _T("Sub2"), 0.0, 1.0, 1);   
   }
}

4.2 其他全局宏(函数)

必须放在EV_START_NEW_PROGRESS_AUTO之前, 之后对所有进度条有效:

//	1)EV_SET_PROGRESS_WINDOW_ICON(UINT idIcon);//指定进度条对话框图标
//	2)EV_SET_PROGRESS_CONFIRM_CANCEL(BOOL bConfirm); //指定取消是否需要确认
//	3)EV_SET_PROGRESS_COLOR(DWORD forecolor, DWORD backcolor); //指定进度条前景色和背景色
//	4)EV_SET_PROGRESS_CTRL_SIZE(int width, int height); //指定进度条的宽度和高度
//	5)EV_SET_PROGRESS_BITMAP(UINT idFore, UINT idBack); //指定全局进度条的位图

4.3 当前进度条设置

必须放在EV_START_NEW_PROGRESS_AUTO之后,对当前进度条有效:

//	1)EV_SET_PROGRESS_STRINGS(LPCTSTR lpstrTitle, LPCTSTR lpstrMainPrompt, LPCTSTR lpstrSubPrompt);//指定显示的文本
//	2)EV_SET_SHOW_SUBPROGRESS(int nSubMark = 0, BOOL bUpdateWin = TRUE);
//	3)EV_GET_PROGRESS_RANGE(double& dStart, double& dEnd);//获取进度限定范围
//	4)EV_SET_PROGRESS_BITMAP_CURRENT(IDB_BITMAP_MAIN[, IDB_BITMAP_SUB]);//指定当前进度条的位图(优先于全局)

下载完整的代码资源。代码资源附带完整的VC实例工程。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在 WPF 中,可以使用 ed:Arc 控件来绘制圆弧进度条,并设置两端圆角。具体步骤如下: 1. 引入 ed:Arc 命名空间: ```xaml xmlns:ed="http://schemas.efxui.com/xaml" ``` 2. 在 XAML 中使用 ed:Arc 控件,并设置 StrokeStartLineCap 和 StrokeEndLineCap 属性为 Round: ```xaml <ed:Arc Stroke="Gray" StrokeThickness="10" StrokeStartLineCap="Round" StrokeEndLineCap="Round" ArcThickness="10" ArcStart="90" ArcEnd="{Binding Progress}" /> ``` 其中,StrokeStartLineCap 和 StrokeEndLineCap 属性设置线端点的形状,ArcThickness 属性设置圆弧的宽度,ArcStart 和 ArcEnd 属性设置圆弧的起点和终点。 3. 在 ViewModel 中定义 Progress 属性,并使用 Timer 定时更新进度: ```csharp public class MainWindowViewModel : INotifyPropertyChanged { private int _progress; private Timer _timer; public int Progress { get => _progress; set { if (_progress != value) { _progress = value; OnPropertyChanged(nameof(Progress)); } } } public MainWindowViewModel() { _timer = new Timer(100); _timer.Elapsed += (sender, e) => { Progress += 5; if (Progress > 360) { Progress = 0; } }; _timer.Start(); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } ``` 完整代码示例: MainWindow.xaml: ```xaml <Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ed="http://schemas.efxui.com/xaml" Title="MainWindow" Height="200" Width="200"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBlock Text="Progress:" Margin="5" /> <ed:Arc Grid.Row="1" Stroke="Gray" StrokeThickness="10" StrokeStartLineCap="Round" StrokeEndLineCap="Round" ArcThickness="10" ArcStart="90" ArcEnd="{Binding Progress}" /> </Grid> </Window> ``` MainWindow.xaml.cs: ```csharp public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new MainWindowViewModel(); } } ``` MainWindowViewModel.cs: ```csharp public class MainWindowViewModel : INotifyPropertyChanged { private int _progress; private Timer _timer; public int Progress { get => _progress; set { if (_progress != value) { _progress = value; OnPropertyChanged(nameof(Progress)); } } } public MainWindowViewModel() { _timer = new Timer(100); _timer.Elapsed += (sender, e) => { Progress += 5; if (Progress > 360) { Progress = 0; } }; _timer.Start(); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值