MFC创建UI线程并建立包含ProgressCtrl进度条对话框的简单方法

项目中由于一些原因,需要在单独线程中创建一个包含ProgressCtrl进度条的对话框用于显示操作进度,因此想到利用MFC的UI线程来实现。虽然想法简单,但实现起来还是颇费了一番周折,捣鼓了一阵子,算是找到了一个比较简单的创建UI线程的方法,现记录下来。

所谓UI线程,无非就是一个比普通Work线程多一个消息循环的线程。再延伸一下,想到MFC的对话框工程自动创建的也应该就是一个UI线程而已。因此创建UI线程无需大费周折,直接使用VS2013,新建一个最简单的MFC对话框工程,命名为ProgressCtrlThread,自动生成的主线程代码如下:

主程序头文件、类定义:

// ProgressCtrlThread.h : PROJECT_NAME 应用程序的主头文件
//

#pragma once

#ifndef __AFXWIN_H__
    #error "在包含此文件之前包含“stdafx.h”以生成 PCH 文件"
#endif

#include "resource.h"       // 主符号


// CProgressCtrlThreadApp: 
// 有关此类的实现,请参阅 ProgressCtrlThread.cpp
//

class CProgressCtrlThreadApp : public CWinApp
{
public:
    CProgressCtrlThreadApp();

// 重写
public:
    virtual BOOL InitInstance();

// 实现

    DECLARE_MESSAGE_MAP()
};

extern CProgressCtrlThreadApp theApp;

主程序源文件、类实现:

// ProgressCtrlThread.cpp : 定义应用程序的类行为。
//

#include "stdafx.h"
#include "ProgressCtrlThread.h"
#include "ProgressCtrlThreadDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CProgressCtrlThreadApp

BEGIN_MESSAGE_MAP(CProgressCtrlThreadApp, CWinApp)
    ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()


// CProgressCtrlThreadApp 构造

CProgressCtrlThreadApp::CProgressCtrlThreadApp()
{
    // 支持重新启动管理器
    m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;

    // TODO:  在此处添加构造代码,
    // 将所有重要的初始化放置在 InitInstance 中
}


// 唯一的一个 CProgressCtrlThreadApp 对象

CProgressCtrlThreadApp theApp;


// CProgressCtrlThreadApp 初始化

BOOL CProgressCtrlThreadApp::InitInstance()
{
    // 如果一个运行在 Windows XP 上的应用程序清单指定要
    // 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
    //则需要 InitCommonControlsEx()。  否则,将无法创建窗口。
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);
    // 将它设置为包括所有要在应用程序中使用的
    // 公共控件类。
    InitCtrls.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&InitCtrls);

    CWinApp::InitInstance();


    AfxEnableControlContainer();

    // 创建 shell 管理器,以防对话框包含
    // 任何 shell 树视图控件或 shell 列表视图控件。
    CShellManager *pShellManager = new CShellManager;

    // 激活“Windows Native”视觉管理器,以便在 MFC 控件中启用主题
    CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));

    // 标准初始化
    // 如果未使用这些功能并希望减小
    // 最终可执行文件的大小,则应移除下列
    // 不需要的特定初始化例程
    // 更改用于存储设置的注册表项
    // TODO:  应适当修改该字符串,
    // 例如修改为公司或组织名
    SetRegistryKey(_T("应用程序向导生成的本地应用程序"));

    CProgressCtrlThreadDlg dlg;
    m_pMainWnd = &dlg;
    INT_PTR nResponse = dlg.DoModal();
    if (nResponse == IDOK)
    {
        // TODO:  在此放置处理何时用
        //  “确定”来关闭对话框的代码
    }
    else if (nResponse == IDCANCEL)
    {
        // TODO:  在此放置处理何时用
        //  “取消”来关闭对话框的代码
    }
    else if (nResponse == -1)
    {
        TRACE(traceAppMsg, 0, "警告: 对话框创建失败,应用程序将意外终止。\n");
        TRACE(traceAppMsg, 0, "警告: 如果您在对话框上使用 MFC 控件,则无法 #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS。\n");
    }

    // 删除上面创建的 shell 管理器。
    if (pShellManager != NULL)
    {
        delete pShellManager;
    }

    // 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
    //  而不是启动应用程序的消息泵。
    return FALSE;
}

对话框程序创建完成,下面要做的就是将其修改为UI线程的格式。
主要工作包括:
1. 将自动生成的主线程ProgressCtrlThread.h文件和ProgressCtrlThread.cpp文件直接复制到需要创建UI线程的工程文件夹内,并加入工程
2. h文件中删除无用内容
3. cpp文件中删除UI线程无关内容
4. 将父类从CWinApp修改为CWinThread
5. 线程间通信需要使用消息,在本线程类的MESSAGE_MAP中应使用ON_THREAD_MESSAGE,而不是ON_MESSAGE,并且在主程序中向本UI线程发送消息时应使用PostThreadMessage()函数
6. 在h文件中加入宏DECLARE_DYNCREATE(CProgressCtrlThread),在cpp文件中加入宏IMPLEMENT_DYNCREATE(CProgressCtrlThread, CWinThread)
7. 在工程资源中创建一个名为IDD_PROGRESSCTRLTHREAD_DIALOG对话框,并加入ProgressCtrl控件,然后创建与之绑定的类CProgressCtrlThreadDlg
8. 主程序中开启UI线程,使用CWinThread* pThread = AfxBeginThread(RUNTIME_CLASS(CProgressCtrlThread))即可

修改后的h文件:

// ProgressCtrlThread.h : 进度条对话框UI线程头文件
//

#pragma once

#include "resource.h"   


class CProgressCtrlThread : public CWinThread
{
    DECLARE_DYNCREATE(CProgressCtrlThread)  //声明此线程会被动态创建为UI线程

public:
    CProgressCtrlThread();

    // 重写
public:
    virtual BOOL InitInstance();

    // 实现

    DECLARE_MESSAGE_MAP()
protected:
    //注意是线程间消息传递返回void而不是afx_msg LRESULT
    void OnThreadMessages(WPARAM wParam, LPARAM lParam);    
};

修改后cpp文件:

// ProgressCtrlThread.h : 进度条对话框UI线程实现文件
//
#include "stdafx.h"
#include "ProgressCtrlThread.h"
#include "ProgressCtrlThreadDlg.h"

IMPLEMENT_DYNCREATE(CProgressCtrlThread, CWinThread)    

用于与UI线程(而不是其中的对话框)直接通信的自定义消息
#define WM_THREAD_MESSAGES      WM_USER + 99        


BEGIN_MESSAGE_MAP(CProgressCtrlThread, CWinThread)
    //UI线程的消息应在这里映射!而不是在CDialogEx对话框类中映射
    ON_THREAD_MESSAGE(WM_THREAD_MESSAGES, &CProgressCtrlThread::OnThreadMessages)   
END_MESSAGE_MAP()


CProgressCtrlThread::CProgressCtrlThread()
{
    // TODO:  在此处添加构造代码,
    // 将所有重要的初始化放置在 InitInstance 中
}

BOOL CProgressCtrlThread::InitInstance()
{
    CWinThread::InitInstance(); //先启用线程的消息循环

    CProgressCtrlThreadDlg pctdlg;
    m_pMainWnd = &pctdlg;

    INT_PTR nResponse = pctdlg.DoModal();
    if (nResponse == IDOK)
    {
        // TODO:  在此放置处理何时用
        //  “确定”来关闭对话框的代码
    }
    else if (nResponse == IDCANCEL)
    {
        // TODO:  在此放置处理何时用
        //  “取消”来关闭对话框的代码
    }

    // 由于对话框已关闭,所以将返回 FALSE 以便退出此UI线程,
    // 同时将会关闭线程的消息泵。
    // 如果返回TRUE,则仅仅退出此函数,而不会关闭线程的消息循环
    return FALSE;
}

//本UI线程的自定义消息相应函数,注意线程间消息传递返回void而不是afx_msg LRESULT
void CProgressCtrlThread::OnThreadMessages(WPARAM wParam, LPARAM lParam)
{   
    //获取进度条对话框指针
    CProgressCtrlThreadDlg* pdlg = ((CProgressCtrlThreadDlg*)GetMainWnd()); 

    int flag = (int)wParam;
    int c = (int)lParam;
    switch (flag)
    {
    case 1:
        pdlg->m_Progress.SetRange(0, c);    //设置ProgressCtrl的总范围
        pdlg->m_Progress.SetFocus();    //防止默认焦点落在按钮上而误响应回车键
        pdlg->m_StaticText.SetWindowText(_T("数据读取中,请稍后..."));
        break;
    case 2:
        c *= 1.2;   //显示速度加快20%
        pdlg->m_Progress.SetPos(c); //设置进度位置
        break;
    case 3:
        pdlg->m_Progress.SetPos(c); //设置为满进度状态
        pdlg->EndDialog(IDCANCEL);  //退出此对话框
        break;
        }
    default:
        break;
    }

    return;
}

其中对话框中进度条控件m_Progress的控制是通过UI线程外发送线程消息来实现的,主线程代码片段如下所示,主要是ODBC方式操作CRecordset数据库,将数据库数据提取出来,同时在进度条对话框中实时显示读取进度:

//主线程代码片段

... //非关键代码省略
int iDataCount = 0; //数据库存储的条目总数
while (!m_AwsDatabase.IsEOF())
{
    m_AwsDatabase.MoveNext();
}
iDataCount = m_AwsDatabase.GetRecordCount();
m_AwsDatabase.MoveFirst();  //重新指回第一个数据

//创建ProgressCtrl的UI线程,用于显示处理进度条
CWinThread* pThread = AfxBeginThread(RUNTIME_CLASS(CProgressCtrlThread));
//向UI线程发送自定义消息,设置ProgressCtrl进度条的Range
pThread->PostThreadMessage(WM_THREAD_MESSAGES, (WPARAM)1, (LPARAM)iDataCount);  
//读取数据库
int iRow = 0;
while (!m_AwsDatabase.IsEOF())
{
    ... //非关键代码省略
    m_AwsDatabase.MoveNext();
    iRow++;
    //设置UI线程中的进度条
    if (iRow % (iDataCount / 20) == 0)  //将ProgressCtrl分成20份,即以5%的数量增加
    {
        //向UI线程发送自定义消息, 设置ProgressCtrl进度条当前位置
        pThread->PostThreadMessage(WM_THREAD_MESSAGES, (WPARAM)2, (LPARAM)iRow);
    }
}
//向UI线程的主对话框发送自定义消息,直接设置ProgressCtrl进度条为满状态
pThread->PostThreadMessage(WM_THREAD_MESSAGES, (WPARAM)3, (LPARAM)iDataCount);  

注意:
1. 使用此方法在UI线程中创建模态对话框,则本UI线程无需手动关闭,只要模态对话框关闭,CProgressCtrlThread::InitInstance()函数便会返回FALSE,从而导致本线程自动退出并关闭消息泵
2. 两个UI线程宏:DECLARE_DYNCREATE(CProgressCtrlThread)与 IMPLEMENT_DYNCREATE(CProgressCtrlThread, CWinThread)一定要添加,否则创建线程会提示内存不足

上述方法实现了UI线程的创建以及对其模态进度条对话框的控制,实际上在消息传递方式上博主也有些疑惑:向UI线程中的对话框发送消息还有一种思路,就是设法获取到对话框的句柄,然后直接使用::PostMessage(),在UI线程的对话框类的MESSAGE_MAP消息映射中实现ON_MESSAGE()宏,如:
//获取UI线程主对话框句柄
HWND hdlg = pThread->GetMainWnd()->GetSafeHwnd();
//向UI线程的主对话框发送自定义消息,设置ProgressCtrl进度条的Range
BOOL success = ::PostMessage(hdlg, WM_PROGRESS_CHANGED, (WPARAM)1, (LPARAM)iDataCount);

但不知何故,这种直接向UI线程的对话框中发送自定义消息的方式总是无法成功,如果哪位高人能够解决博主的疑惑还请留言赐教。

参考文献:

MFC中如何给子线程发送消息
MFC 线程消息传递问题二—两个线程之间进行消息的传递
关于MFC下多线程,在线程中创建非模态对话框以及消息传递
MFC线程中创建非模态对话框
MFC启动和关闭线程

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值