MFC关于自定义消息(线程给主窗口发消息)(主窗口给线程发消息)实例

2023-10-07 00:20

本文主要是介绍MFC关于自定义消息(线程给主窗口发消息)(主窗口给线程发消息)实例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

老听公司的老哥们说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_MAPON_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;
}

 

这篇关于MFC关于自定义消息(线程给主窗口发消息)(主窗口给线程发消息)实例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/154856

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

实例:如何统计当前主机的连接状态和连接数

统计当前主机的连接状态和连接数 在 Linux 中,可使用 ss 命令来查看主机的网络连接状态。以下是统计当前主机连接状态和连接主机数量的具体操作。 1. 统计当前主机的连接状态 使用 ss 命令结合 grep、cut、sort 和 uniq 命令来统计当前主机的 TCP 连接状态。 ss -nta | grep -v '^State' | cut -d " " -f 1 | sort |

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

使用JS/Jquery获得父窗口的几个方法(笔记)

<pre name="code" class="javascript">取父窗口的元素方法:$(selector, window.parent.document);那么你取父窗口的父窗口的元素就可以用:$(selector, window.parent.parent.document);如题: $(selector, window.top.document);//获得顶级窗口里面的元素 $(

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

Java Websocket实例【服务端与客户端实现全双工通讯】

Java Websocket实例【服务端与客户端实现全双工通讯】 现很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发 出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点 – 浏 览器需要不断的向服务器发出请求,然而HTTP

ActiveMQ—消息特性(延迟和定时消息投递)

ActiveMQ消息特性:延迟和定时消息投递(Delay and Schedule Message Delivery) 转自:http://blog.csdn.net/kimmking/article/details/8443872 有时候我们不希望消息马上被broker投递出去,而是想要消息60秒以后发给消费者,或者我们想让消息没隔一定时间投递一次,一共投递指定的次数。。。 类似