在MFC中,通过edit control控件实时显示函数的执行进度

本文介绍在MFC程序中,如何通过创建工作者线程并利用自定义消息更新EditControl控件,实现在长时间运算过程中实时显示进度。解决了主线程在处理耗时任务时界面无响应的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 应用场景

最近在做一个单机版的MFC程序,程序的功能是点击按钮,程序执行运算函数,整个运算过程时间较长,(涉及图片预处理、相位解包裹、点云处理、曲面重建等过程),我需要在edit control控件实时显示函数的执行进度。

2. 最初的解决方法

最初想到的解决办法是:每执行完函数中的一段程序后,在程序之后写上如下代码。(在控制台程序中是用这种思路实现的)

UpdateData(TRUE);
	GetStringTime(TimeString);
	m_outedit = m_outedit + (CString)"程序开始执行,加载图片" + TimeString + (CString)"\r\n";
	UpdateData(FALSE);

其中m_outedit是我对edit control控件关联的变量。

3. 最初的解决方法遇到的问题
  1. 程序在执行完上述代码后并不会立即更新控件,直到整个函数执行完成才会更新控件,因此该问题是致命的。
  2. 由于程序的运算过程较为复杂,在程序运算的过程中,在界面上点击其他按钮会出现程序未响应的现象,也就是假死现象。参考文献.
    接下来重点解决这两个问题。
4. 激动人心的充电环节
4.1 思路:

每个系统中都有线程(至少都有一个主线程),而线程最重要的作用就是并行处理,提高软件的并发率。针对界面来说,还能提高界面的响应能力。
一般的,为了应用的稳定性,在数据处理等耗时操作会单独在一个线程中运行,而所有与主UI线程有关的控件数据刷新应该到主UI线程中处理。也就是数据处理线程发消息,让界面UI去更新控件
在MFC中线程分为界面线程和工作者线程,界面实际就是一个线程画出来的东西,这个线程维护一个“消息队列”,“消息队列”也是界面线程和工作者线程的最大区别,MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环参考文献.
我的解决思路:我使用主线程(用户界面线程)处理消息(包括按钮的点击以及edit control控件的刷新),新建了一个工作者线程进行复杂的函数运算,在工作者线程中,将执行进度的字符串保存到静态变量中,通过消息投递函数触发主线程对edit control控件进行刷新。

4.2 创建线程的方法

第一种AfxBeginThread();第二种CreateThread();第三种_beginthread()。参考文献.

4.3 三种创建线程的方法具体有什么区别呢?

CreateThread这个函数是windows提供给用户的Win32 API函数,是SDK的标准形式,在使用的过程中要考虑到进程的同步与互斥的关系,进程间的同步互斥等一系列会导致操作系统死锁的因素,用起来比较繁琐一些,初学的人在用到的时候可能会产生不可预料的错误,建议多使用AfxBeginThread,AfxBeginThread是MFC的全局函数,是编译器对原来的CreateThread函数的封装,用与MFC编程(当然,只要修改了项目属性,console和win32项目都能调用)。_beginthread是C的运行库函数。参考文献.

4.4 AfxBeginThread();

接下来主要介绍一下AfxBeginThread()函数。MFC提供了两个重载版的AfxBeginThread,一个用于用户界面线程,另一个用于工作者线程
由于我创建的是工作者线程,因此详细介绍使用AfxBeginThread()函数创建工作者线程。下图是函数定义:

CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam,
	int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0,
	DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

参数1为线程的入口函数,开启线程所要执行的函数,此函数必须为静态函数;
参树2为传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程,通常把this指针传过去,这样线程函数就可以使用和操作类的成员了;
参数3指定线程优先级,如果为0,则与创建该线程的线程相同,其中THREAD_PRIORITY_NORMAL的宏定义为0;
参数4指定新创建的线程的栈的大小。如果为 0,新创建的线程具有和主线程一样的大小的栈;
参数5是一个创建标识,默认为0,线程在创建后开始线程的执行。如果是CREATE_SUSPENDED,在线程创建后线程挂起,直到调用:ResumeThread。
参数6指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性,如果为 NULL,那么新创建的线程就具有和主线程一样的安全性。

5. 具体实现过程
5.1 创建工作者线程

在.cpp文件中创建工作者线程,我是在按钮响应函数下创建的,也就是点击按钮创建线程。

CWinThread* mythread = AfxBeginThread(
	Calcul,
	this,
	THREAD_PRIORITY_NORMAL,
	0,
	0,
	NULL
);

其中Calcul是我的运算函数。
在.h件中声明运算函数。

static UINT Calcul(LPVOID lpParam);

在.cpp文件中定义Calcul函数。

UINT CMy3DmeasurementMFC41Dlg::Calcul(LPVOID lpParam)
{
	CMy3DmeasurementMFC41Dlg *Dlg = (CMy3DmeasurementMFC41Dlg *)lpParam;

	Dlg->GetStringTime(Dlg->TimeString);
	OutCstring = OutCstring + (CString)"程序开始执行,加载图片" + Dlg->TimeString + (CString)"\r\n";

	::SendMessage(Dlg->m_hWnd, WM_UPDATE_STATIC, 0, 0);

	//::SetWindowText(::GetDlgItem(Dlg->m_hWnd, IDC_OUTEDIT), OutCstring);
	//Dlg->GetDlgItem(IDC_OUTEDIT)->SetWindowText(OutCstring);
	//我的数据处理过程
	//
	return 0;
}

其中OutCstring这个变量就是我要刷新edit control控件所要用到的。
这里要说明的是:
1.上图中的GetStringTime是我写的函数,函数功能是以CString的格式输出当前时间,后面对这个函数进行详细介绍。
2.我在运算函数中定义了类的指针,通过指针的方式调用类的函数与变量,但是其中的OutCstring并没有使用指针的形式调用。原因是我定义的OutCstring是一个静态变量,方便我在用户界面线程中使用该变量。后面会再详细说一下静态变量。
3.我这里使用return 0来退出线程。

5.2 自定义消息函数的添加

继续我们的实现过程:
我这里通过自定义消息函数SendMessage,来通知主线程(用户界面线程),更新edit control控件,也可以在工作者线程内通过全局函数更新控件内容。这位仁兄介绍了MFC子线程中更新控件内容的两种办法,我试了这两种方法均可行(参考上一张代码图片注释部分),但是我还是建议大家通过发送自定义消息更新控件内容,尽量避免在线程中掺杂对主界面的操作。
首先,在.h件中定义一个消息ID。

#define WM_UPDATE_STATIC (WM_USER + 100)

然后在VS的菜单栏点击项目->类向导->消息->添加自定义消息,在上面的编辑框中填入我们定义的消息ID:WM_UPDATE_STATIC,点击确定;VS会非常贴心地帮分别我们在.h文件中添加自定义消息响应函数的声明,在.cpp文件中添加消息映射和自定义消息响应函数的定义。
在这里插入图片描述
.h文件中的自定义消息响应函数的声明

afx_msg LRESULT OnUpdateStatic(WPARAM wParam, LPARAM lParam);

.cpp文件中的消息映射

BEGIN_MESSAGE_MAP(CMy3DmeasurementMFC41Dlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_CALCUL, &CMy3DmeasurementMFC41Dlg::OnBnClickedCalcul)
	ON_MESSAGE(WM_UPDATE_STATIC, &CMy3DmeasurementMFC41Dlg::OnUpdateStatic)
END_MESSAGE_MAP()

.cpp文件中的自定义消息响应函数的定义

afx_msg LRESULT CMy3DmeasurementMFC41Dlg::OnUpdateStatic(WPARAM wParam, LPARAM lParam)
{
	GetDlgItem(IDC_OUTEDIT)->SetWindowText(OutCstring);
	return 0;
}

以上,就是我的全部实现过程。

6. 几个用到的知识点
6.1 关于静态变量

对静态变量的理解:一个类可以实例化多个对象,每一个对象都有各自的存储空间来存储各自内部的变量,但是对于静态变量而言,无论这个类实例化多少个对象,这个静态变量有且仅有一个存储空间,而且类的每一个对象均可访问这个静态变量。
静态变量的定义:

static CString OutCstring;

静态变量的初始化:

CString CMy3DmeasurementMFC41Dlg::OutCstring = (CString)"打开程序"+(CString)"\r\n";

CMy3DmeasurementMFC41Dlg::CMy3DmeasurementMFC41Dlg(CWnd* pParent /*=NULL*/)

第一行代码是初始化,第二行代码是构造函数,是用来表明在哪个位置对静态变量进行初始化。(注意是构造函数前面)

6.2 我的GetStringTime函数
void CMy3DmeasurementMFC41Dlg::GetStringTime(CString &StringTime)
{
	StringTime = "";

	SYSTEMTIME sys;	

	CString StringTime_Hour; CString StringTime_Minute; CString StringTime_Second; 

	GetLocalTime(&sys);

    StringTime_Hour.Format(_T("%d"), sys.wHour); StringTime_Minute.Format(_T("%d"), sys.wMinute); StringTime_Second.Format(_T("%d"), sys.wSecond); 

	StringTime = StringTime_Hour + (CString)":" + StringTime_Minute + (CString)":" + StringTime_Second;
}

我这个GetStringTime函数功能是以CString的格式输出当前时间,其中GetLocalTime()这个函数我觉得还是比较好用的,我这里用到的是时分秒。其次使用Format函数将整型变量转化为CString变量,最后是CString的拼接,注意CString变量可以连续拼接。

6.3 SendMessage与PostMessage

我在这儿用的是SendMessage,也就是我的自定义消息响应函数执行完成后,工作者线程中的函数才会继续执行。
如果使用PostMessage,我的自定义消息响应函数和工作者线程中的函数会同步执行。

6.4 edit control控件和button control控件的设置
  1. 我在更新edit control控件的变量中应用了(CString)"\r\n",但是在输出的过程中并不会换行,需要对edit control控件的属性进行设置。参考文献.
    Auto HScroll 设置为 False
    MultiLine 设置为 True
    Want Return 设置为 True。
  2. 点击按钮进行计算之后,由于运算过程较长,程序在运算的过程中用户再次点击可能导致程序崩溃,因此在点击按钮之后,将按钮设置为不可点击,将按钮上的文字设置为“正在运算”。在运算过程执行完之后,再将设置更改过来。
GetDlgItem(IDC_CALCUL)->EnableWindow(FALSE);
GetDlgItem(IDC_CALCUL)->SetWindowText((CString)"正在计算");
后记:

若大家发现错误,敬请批评指正,如有疑问,欢迎留言。

<think>好的,用户的问题是关于如何在MFC中从List Control控件获取选中的文本并显示Edit Control控件中。首先,我需要回忆一下MFC中List Control的相关操作。根据引用[3],List Control使用InsertItem和SetItemText来添加数据,所以获取数据可能涉及GetItemText方法。 用户需要获取选中的行,这可能需要用到GetNextItem函数,并用LVNI_SELECTED标志来检测选中的项。接着,获取该行各列的数据,然后组合成字符串显示Edit Control中。根据引用[3],Edit Control的设置可以使用SetWindowText函数。 需要注意的是,List Control可能有不同的视图模式,比如报告视图,才能显示多列数据。确保控件属性设置正确。代码部分应该处理多选的情况,但用户的问题可能只需要处理单选。所以示例代码可能先获取第一个选中项,然后遍历各列。最后将文本设置到Edit Control中。 检查引用[2]提到编辑功能需要额外处理,但这里只是获取数据,可能不需要。引用[4]提到变量管理,确保在对话框类中正确关联控件变量,比如m_listCtrl和m_editInfo。 现在需要将这些步骤整理成清晰的步骤说明,并提供示例代码,确保语法正确,引用正确的来源。同时,生成相关问题,比如多选处理或其他控件操作。</think>要在MFC中获取ListControl选中项的文本并显示EditControl中,可按照以下步骤实现: 1. **获取选中项索引** ```cpp int nSelected = m_listCtrl.GetNextItem(-1, LVNI_SELECTED); ``` 2. **提取单元格文本** ```cpp if(nSelected != -1) { CString strText; // 获取第1列文本(假设有3列) strText += m_listCtrl.GetItemText(nSelected, 0) + _T(" | "); strText += m_listCtrl.GetItemText(nSelected, 1) + _T(" | "); strText += m_listCtrl.GetItemText(nSelected, 2); } ``` 3. **显示EditControl** ```cpp m_editInfo.SetWindowText(strText); ``` 完整示例代码(需在对话框类中添加控件变量): ```cpp void CYourDialog::OnBnClickedButtonGettext() { CString strInfo; int nItem = m_listCtrl.GetNextItem(-1, LVNI_SELECTED); if(nItem != -1) { strInfo.Format(_T("姓名:%s\r\n工号:%s\r\n密码:%s"), m_listCtrl.GetItemText(nItem, 0), m_listCtrl.GetItemText(nItem, 1), m_listCtrl.GetItemText(nItem, 2)); } m_editInfo.SetWindowText(strInfo); } ``` **关键点说明**: 1. 确保ListControl设置为"Report"视图模式以显示多列数据[^3] 2. 使用GetItemText时,第二参数为列索引(从0开始) 3. 通过SetWindowText更新EditControl内容[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值