前言
老听公司的老哥们说MFC基于消息机制什么的巴拉巴拉一大堆,实际上自己并没有真真用过,每次看讲解什么的也是一知半懂,像我这种半路出家的,不遇到实际问题根本就搞不懂.由于目前做到公司的项目,按照需求,需要用到消息机制,索性就一次搞个明白
我理解的信号就是当线程或主线程执行完某一步,需要把当前执行的结果反馈给主线程或线程,这时候通过一个消息符号(自定义),该消息符号约定了具体做什么(接收到该信号就执行已定操作)
线程给主窗口(主线(进)程)发消息
这个相对而言比较容易实现,也较能理解
我来模拟一下需求:
首先打开MFC项目的时候会创建一个线程,该线程会从远程服务器上获取信息用于校验本次是否需要升级,需要升级则显示提供点击的升级按钮
那么问题出现了,子线程在检测到需要升级的时候该怎样让主窗口显示升级按钮呢?
下面给出部分代码(只讲解发送消息部分)
if (0 == strcmp(pthis->CURes.GetResCode().c_str(),"040200")) //业务受理成功
{
if (0 == strcmp(pthis->CURes.GetIsUpdate().c_str(),"0")) //程序需要升级
{
//进行升级模式判断
if (0 == strcmp(pthis->CURes.GetIsForce().c_str(),"0"))//强制升级
{
pthis->PostMessage(WN_STEP_TIPS,0,StepValve::MUSTUPDATE);
//判断下载包类型
if (0 == strcmp(pthis->CURes.GetPackageType().c_str(),"0")) //全量包
{
MustUpdate();
}
else //增量包
{
//返回自定义消息,消息触发单击按钮事件
vector<pair<string,string>> vecDownLoadInfo;
DownLoadInFoCTV(pthis->CURes,vecDownLoadInfo);
pthis->PostMessage(WN_CLICK_CONTINUE_BUTTON); //单击建议升级按钮
}
}
else //建议升级
{
if (0 == strcmp(pthis->CURes.GetPackageType().c_str(),"0")) //全量包
{
//这里需要显示升级按钮
pthis->PostMessage(WN_SHOW_BUTTON1);
}
else //增量包
{
//建议升级显示升级按钮
pthis->PostMessage(WN_STEP_TIPS,0,StepValve::SUGGESTUPDATE);
//建议升级需要展示操作按钮
pthis->PostMessage(WN_SHOW_BUTTON);
Sleep(500);
}
}
}
}
可以看到我这段代码中基本需要反馈的信息都是通过发消息实现的
发消息的函数有两个:sendmessage 和postmessage
大体区别是sendmessage是阻断式的,当调用时会卡在这步直到执行成功(消息发送成功),虽然sendmessage能确保消息发送成功,但却会出现假使消息发送失败,界面就一直卡住的情况,因此除非是该消息特别重要,不然不建议使用
PostMessage是非阻断的,发起发送消息后就过了,不管消息有没有发送,不管,虽然消息可能会发送失败(这种情况很少),但能保证界面不卡
具体使用情况本人没有测试,但目前使用postmessage没有问题
先一步一步讲吧
一
如果你需要发消息,那么首先你得要有一个消息
消息在主函数头文件中定义
#define WN_SHOW_BUTTON WM_USER+1 //自定义消息,用于显示按钮
#define WN_UPDATE_PROGRESS WM_USER+3 //更新进度
#define WN_UPDATE_PERCENT WM_USER+4 //更新百分比
#define WN_LOGBOOTFAIL WM_USER+5 //获取升级信息失败
#define WN_CLICK_CONTINUE_BUTTON WM_USER+6 //单击继续升级按钮
#define WN_STEP_TIPS WM_USER+7 //升级步骤信息提示
#define WN_SHOW_BUTTON1 WM_USER+8 //展示升级按钮
就挑第一行吧,宏定义一个消息,名为WN_SHOW_BUTTON,他的消息值为WM_USER+1,WM_USER是专门用来让你自定义消息的值,如果消息过多,就在后面加数值,推荐直接加大数字,万一你是在已有项目中添加消息,用小数字可能会有重复,可能被别人使用过了
二
在类的定义中定义消息处理函数
//消息处理函数
afx_msg LRESULT ShowButton(WPARAM,LPARAM);
afx_msg LRESULT UpdateProgress(WPARAM,LPARAM);
afx_msg LRESULT UpdatePrecent(WPARAM,LPARAM);
afx_msg LRESULT LogBootFail(WPARAM,LPARAM);
afx_msg LRESULT ClickContinue(WPARAM,LPARAM);
afx_msg LRESULT Step_Tip(WPARAM,LPARAM);
afx_msg LRESULT ShowButton1(WPARAM,LPARAM);
消息处理函数格式是固定的,后面有参数的实际作用讲解,如果你是在MFC中添加消息定义函数,可以在类的定义中添加public:权限
三
在相应的.cpp中关联信号与函数
比如我这里主窗口叫1122Dlg,那么找到1122Dlg.cpp,从cpp上面开始找,找到BEGIN_MESSAGE_MAP(CMy1122Dlg, CDialog)
例
BEGIN_MESSAGE_MAP(CMy1122Dlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//这里开始添加
ON_MESSAGE(WN_SHOW_BUTTON,&CMy1122Dlg::ShowButton)
ON_MESSAGE(WN_UPDATE_PROGRESS,&CMy1122Dlg::UpdateProgress)
ON_MESSAGE(WN_UPDATE_PERCENT,&CMy1122Dlg::UpdatePrecent)
ON_MESSAGE(WN_LOGBOOTFAIL,&CMy1122Dlg::LogBootFail)
ON_MESSAGE(WN_CLICK_CONTINUE_BUTTON,&CMy1122Dlg::ClickContinue)
ON_MESSAGE(WN_STEP_TIPS,&CMy1122Dlg::Step_Tip)
ON_MESSAGE(WN_SHOW_BUTTON1,&CMy1122Dlg::ShowButton1)
//这里结束添加
//}}AFX_MSG_MAP
ON_BN_CLICKED(IDC_GiveUp, &CMy1122Dlg::OnBnClickedGiveup)
ON_BN_CLICKED(IDC_Continue, &CMy1122Dlg::OnBnClickedContinue)
ON_BN_CLICKED(IDC_Continue1, &CMy1122Dlg::OnBnClickedContinue1)
END_MESSAGE_MAP()
// CMy1122Dlg 消息处理程序
关联的格式也是固定的,ON_MESSAGE(1,2);1是自定义消息名字,2是你想要将消息关联的函数
四
实现关联的消息
我这里展示建议升级步骤中显示升级按钮的消息响应函数,即我上面的
#define WN_SHOW_BUTTON WM_USER+1 //自定义消息,用于显示按钮
//实现消息函数,显示按钮
LRESULT CMy1122Dlg::ShowButton(WPARAM,LPARAM)
{
CEdit * bBreak = (CEdit *)GetDlgItem(IDC_GiveUp);
bBreak->ShowWindow(TRUE);
CEdit * bContinue = (CEdit *)GetDlgItem(IDC_Continue);
bContinue->ShowWindow(TRUE);
return 0;
}
注意:所有的自定义消息被绑定消息处理函数后都要去实现!!!
我这边就是将按钮显示,可能你会不懂为什么,我这里讲下思路:我在窗口上已经添加了该按钮用于升级,但我不知道本次打开是否需要升级,因此我在窗口初始化中将按钮隐藏,直到子线程发送展示按钮的消息,此时将按钮显示
展示运行
具体流程是在线程中需要发送的位置发送消息
然后主线程响应消息
至此线程向主线程发消息就算实现了
但是上面说了,在定义消息响应函数时固定的格式中有两个参数,这里讲下参数的作用
我这里给出postmessage的参数,第一个参数是消息类型,二三两个参数是用于控制消息执行的入参
例如
postmessage中2,3两个参数是默认参数,用不到的话就不要填写,但有时可能会出现同一个消息会有不同的处理,这时候就要在发消息的时候将处理的方式传入
例如:
我写了一个专门用于显示当前步骤的处理函数,通过发消息的方式显示当前步骤(我的所有升级步骤全部在线程中执行)
主要步骤如下
#define WN_STEP_TIPS WM_USER+7 //升级步骤信息提示
afx_msg LRESULT Step_Tip(WPARAM,LPARAM);
ON_MESSAGE(WN_STEP_TIPS,&CMy1122Dlg::Step_Tip)
//升级步骤信息提示实现
LRESULT CMy1122Dlg::Step_Tip(WPARAM,LPARAM Lparam)
{
enum StepValve key = (enum StepValve)Lparam;
if (GET_DOWNLOADINFO == key)
{
ShowInFoToIDC_StepTips(_T("获取升级信息..."));
}
if (MUSTUPDATE == key)
{
ShowInFoToIDC_StepTips(_T("本次需要强制升级..."));
}
if (DOWNLOADFILE == key)
{
ShowInFoToIDC_StepTips(_T("正在下载文件..."));
}
if (CHECKFILEHASH == key)
{
ShowInFoToIDC_StepTips(_T("正在校验文件..."));
}
if (CORRECT_CHECKFILEHASH == key)
{
ShowInFoToIDC_StepTips(_T("校验下载文件成功..."));
}
if (ERROR_CHECKFILEHASH == key)
{
ShowInFoToIDC_StepTips(_T("校验下载文件失败..."));
}
if (ERROR_MUSTUPDATE == key)
{
ShowInFoToIDC_StepTips(_T("强制升级失败..."));
}
if (CORRECT_MUSTUPDATE == key)
{
ShowInFoToIDC_StepTips(_T("强制升级成功..."));
}
if (SETUPFILE == key)
{
ShowInFoToIDC_StepTips(_T("正在进行文件安装..."));
}
if (ERROR_SETUPFILE == key)
{
ShowInFoToIDC_StepTips(_T("文件安装失败..."));
}
if (SUGGESTUPDATE == key)
{
ShowInFoToIDC_StepTips(_T("建议升级..."));
}
return 0;
}
调用该消息
pthis->PostMessage(WN_STEP_TIPS,0,StepValve::MUSTUPDATE);
一般使用第三个参数
我这里的第三个参数是个枚举类型
enum StepValve
{
GET_DOWNLOADINFO = 1, //获取升级信息
DOWNLOADFILE, //下载文件
ERROR_DOWNLOADFILE, //文件下载失败
CHECKFILEHASH, //校验文件
CORRECT_CHECKFILEHASH, //检验文件成功
ERROR_CHECKFILEHASH, //文件校验失败
UNZIPFILE, //解压文件
ERROR_UNZIPFILE, //解压文件失败
SETUPFILE, //文件安装
ERROR_SETUPFILE, //文件安装失败
MUSTUPDATE, //强制升级
ERROR_MUSTUPDATE, //强制升级失败
CORRECT_MUSTUPDATE, //强制升级成功
SUGGESTUPDATE, //建议升级
CORRECT_SUGGESTUPDATE, //建议升级成功
ERROR_SUGGESTUPDATE, //建议升级失败
};
当然你可以用int类型直接代替StepValve
需要注意的是消息实现中转化的参数类型需要和postmessage中的参数类型一致,例如
我发送消息pthis->PostMessage(WN_STEP_TIPS,0,StepValve::MUSTUPDATE);
那么在消息响应函数中
enum StepValve key = (enum StepValve)Lparam;//将参数类型转化
至此,postmessage与消息响应函数中的两个参数就讲完了
主线程给子线程发消息
这种情况使用不多,往往在用户与界面交互时用到
情景:MFC程序打开时会播放背景音乐,当然了,这个播放的功能是子线程的,那么如果用户不希望播放背景音乐了,就需要关闭背景音乐,这时候主界面上给出一个按钮,点击一次关闭背景音乐,再次点击打开背景音乐
这个其实步骤简单,但是较为难以理解,这涉及到windows的消息内核机制
还是一步一步讲吧
一
还是定义消息类型
#define WN_THREAD WM_USER+10086
二
添加按钮单击事件响应函数
在响应函数中发送消息
如:
void C小程序Dlg::OnBnClickedMusic() //界面上用于控制背景音乐播放跟暂停的按钮
{
// TODO: 在此添加控件通知处理程序代码
//向线程发消息
PostThreadMessage(m_threadId,WN_THREAD,NULL,NULL);
}
这样就能实现每次单击按钮向线程发送消息,这里使用的是PostThreadMessage,我代码里的运用应该可以看懂的,第一个参数是线程ID,第二个参数是消息类型
线程ID怎么获得?
答案是在创建线程时 //创建播放背景音乐线程
m_handle = (HANDLE)_beginthreadex(NULL, 0, ThreadFunMain,this, 0, &m_threadId);
我这里将这些参数句柄等都写在类里面
public:
HANDLE m_handle;
unsigned int m_threadId;
HANDLE m_hhEvent;
BOOL key;
到这里还算简单
三
这里需要设置消息了,你单击按钮时程序线程怎么知道你单击了呢
在窗口初始化函数中:
m_hhEvent = CreateEvent(NULL,NULL,FALSE,NULL);
//创建播放背景音乐线程
m_handle = (HANDLE)_beginthreadex(NULL, 0, ThreadFunMain,this, 0, &m_threadId);
WaitForSingleObject(m_hhEvent,INFINITE);
CloseHandle(m_hhEvent);
CloseHandle(m_handle); //释放线程资源
这里比较难以理解的是
m_hhEvent = CreateEvent(NULL,NULL,FALSE,NULL);跟WaitForSingleObject(m_hhEvent,INFINITE);
我理解的是这两句中间的线程可以接受来自用户的消息,等待时间为INFINITE不限时
四
在线程中处理消息
unsigned int __stdcall ThreadFunMain(void * v)
{
C小程序Dlg *pthis = (C小程序Dlg*)v;
PlaySound(MAKEINTRESOURCE(IDR_WAVE1),AfxGetResourceHandle(),SND_ASYNC|SND_RESOURCE|SND_NODEFAULT|SND_LOOP);
MSG msg;
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);//保证不丢失消息
SetEvent(pthis->m_hhEvent);
while (GetMessage(&msg, 0, 0, 0))
{
switch (msg.message)
{
case WN_THREAD:
{
if (pthis->key)
{
pthis->key = 0;
pthis->MessageBox(_T("打开"),_T("tips"),MB_OK);
PlaySound(MAKEINTRESOURCE(IDR_WAVE1),AfxGetResourceHandle(),SND_ASYNC|SND_RESOURCE|SND_NODEFAULT|SND_LOOP);
}
else
{
pthis->key = 1;
pthis->MessageBox(_T("关闭"),_T("tips"),MB_OK);
PlaySound(NULL,NULL,SND_PURGE);//停止播放
}
}
break;
default:
break;
}
}
//_endthreadex(0);
return 0;
}
这里面有三个函数较难理解
PeekMessage (可有可无,我两种都试过)
SetEvent :设置事件
GetMessage :获取消息队列中的消息
效果展示
由于在MFC中演示较为困难,这里我给出win32控制台中的代码,可以便于理解
#include <Windows.h>
#include <iostream>
#include <process.h>
using namespace std;
#define WN_THREAD WM_USER+1
//定义全局事件句柄
HANDLE hEvent;
//回调函数
unsigned int __stdcall CallBack(void * v)
{
cout << "进入线程" << endl;
MSG msg;
//PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);//保证不丢失消息
SetEvent(hEvent);
/* while ()
{*/
GetMessage(&msg, 0, 0, 0);
switch (msg.message) {
case WN_THREAD:
{
cout << "线程响应事件" << endl;
}
break;
default:
break;
}
/*}*/
return 0;
}
int main()
{
//创建事件
hEvent = CreateEvent(NULL,NULL,FALSE,NULL);
if (NULL == hEvent) {
cout << "创建事件失败, 错误为" << GetLastError() << endl;
return 1;
}
unsigned int threadId;
//创建线程
HANDLE hThread = (HANDLE)_beginthreadex(NULL,0,CallBack,NULL,0,&threadId);
//Sleep(10000);
WaitForSingleObject(hEvent,INFINITE);
CloseHandle(hEvent);
//向线程发消息
PostThreadMessage(threadId,WN_THREAD,NULL,NULL);
//WaitForSingleObject(hThread,INFINITE);
CloseHandle(hThread);
while(1)
{
;
}
return 0;
}