WM_USER

28 篇文章 0 订阅
23 篇文章 0 订阅

谈谈WM_USER如何使用。

使用的例子

功能

以一个简单的计算一个整数的平方作为例子。UI如下:

A Simple Calculator

用户输入a的值,然后单击Calculator计算结果。

下面给出通常实现该功能的代码,之后再拓展开来讨论WM_USER。

资源文件

IDD_CALC_DIALOG DIALOGEX 0, 0, 181, 66
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "A Simple Calculator"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    RTEXT           "a:",IDC_STATIC,19,20,24,8
    EDITTEXT        IDC_A,51,19,46,12,ES_AUTOHSCROLL
    RTEXT           "a * a:",IDC_STATIC,19,40,24,8
    EDITTEXT        IDC_AXA,51,39,46,12,ES_AUTOHSCROLL | ES_READONLY
    PUSHBUTTON      "Calculate",IDC_CALC,113,25,54,14
END

DDX

头文件:

public:
    DWORD m_a;
    DWORD m_axa;
    afx_msg void OnBnClickedCalc();
};

实现文件:

CCalcDlg::CCalcDlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(CCalcDlg::IDD, pParent)
    , m_a(0)
    , m_axa(0)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CCalcDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_A, m_a);
    DDX_Text(pDX, IDC_AXA, m_axa);
}

BEGIN_MESSAGE_MAP(CCalcDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_CALC, &CCalcDlg::OnBnClickedCalc)
END_MESSAGE_MAP()

void CCalcDlg::OnBnClickedCalc()
{
    UpdateData(TRUE);
    m_axa = m_a * m_a;
    UpdateData(FALSE);
}

WM_USER自定义消息

思路

现在对上面的代码进行改造,把简单的代码变得复杂:

  • OnBnClickedCalc()只负责更新a的值,然后发一个消息出来;
  • 另外的地方,即该消息的接收者计算a的平方,然后把这个结果更新到UI上面。

下面几小节即是实现自定义消息的几个步骤(五步)。

Step 1: 定义消息ID

定义自定义消息ID。可以在一个专门的头文件中,或者stdafx.h中,根据项目需要而定。在本例中,简单地定义在对话框类的cpp文件中:

#define MY_MSG_ID WM_USER + 0

注意,这里是+0。——因为MSDN有下面的说明:

The WM_USER constant is used by applications to help define private messages for use by private window classes, usually of the form WM_USER+X, where X is an integer value.

Message numbers in the second range (WM_USER through 0x7FFF) can be defined and used by an application to send messages within a private window class. These values cannot be used to define messages that are meaningful throughout an application, because some predefined window classes already define values in this range. For example, predefined control classes such as BUTTON, EDIT, LISTBOX, and COMBOBOX may use these values. Messages in this range should not be sent to other applications unless the applications have been designed to exchange messages and to attach the same meaning to the message numbers.

这里的描述说从WM_USER开始属于用户可以用的范围,包括WM_USER。我们看到一些介绍也是用+0这种例子。

当然,后面会说明由此带来的问题。

Step 2: 消息映射

下面的ON_MESSAGE一行:

BEGIN_MESSAGE_MAP(CCalcDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_CALC, &CCalcDlg::OnBnClickedCalc)
    ON_MESSAGE(MY_MSG_ID, OnMyMsg)
END_MESSAGE_MAP()

Step 3: 头文件中增加消息处理函数的原型

afx_msg LRESULT OnMyMsg(WPARAM wParam, LPARAM lParam);

VC6需要放到AFX_MSG中,如下:

// Generated message map functions
//{{AFX_MSG(CCalcDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnBnClickedCalc();
afx_msg LRESULT OnMyMsg(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

Step 4: cpp文件增加实现代码

这个代码的处理流程,需要和下一步的SendMessage或PostMessage一起考虑,即如何解释消息的两个参数。

LRESULT CCalcDlg::OnMyMsg(WPARAM wParam, LPARAM lParam)
{
    DWORD a = (DWORD)wParam;
    m_axa = a * a;
    UpdateData(FALSE);

    return 0L;
}

Step 5:发送消息

有两个对应的函数,即SendMessage和PostMessage。前者是同步,后者是异步,根据需要选择其中一个即可。

如前面描述的,这里仅仅是更新控件对应的成员变量的值,并没有计算a的平方值。

void CCalcDlg::OnBnClickedCalc()
{
    UpdateData(TRUE);

    ::PostMessage(GetSafeHwnd(), MY_MSG_ID, m_a, 0);
    //::SendMessage(GetSafeHwnd(), MY_MSG_ID, m_a, 0);
}

效果

以上改造后的效果,基本上是可行的。但如果仔细的话,发现鼠标去选中两个编辑框控件的时候,平方值变成了0!!!

问题分析

跟踪发现,即便没有单击Calculate按钮,OnMyMsg也会去执行,此时入参wParam是0,所以计算出来的平方值变成0了。

也就是说,这个所谓的WM_USER+0自定义消息,除了自己发送而收到外,还会莫名其妙地多次收到。

在《Programming.Microsoft.Visual.C++,5th.Ed》中发现了下面一段话:

The Windows constant WM_USER is the first message ID available for user-defined messages. The application framework uses a few of these messages, so we’ll skip over the first five messages.

这里没有进一步阐述到底哪些信息占用了WM_USER开始的几个消息ID。紧接着这个描述,这本书的例子用的起始值是5:

#define WM_GOODBYE     WM_USER + 5

就本文的小例子而言,试验发现+1就可以解决问题。

进行折腾

以上描述的是一个对话框类的内部触发消息、执行消息。现在讨论对话框类和其他的class的消息交互。

功能变更

在上面的基础上,把功能变更为:单击Calculate之后,自动计算1~10000的平方,并在UI上依次显示每个计算结果。

进一步复杂化,把数值计算放在单独的一个class中,比如CMathLib。对话框类直接告诉CMathLib要计算1~10000的平方,且CMathLib在计算每个值之后都要把结果显示在屏幕上,每个数值计算并显示在UI之后要停留2秒钟的时间,让用户可以清晰看到计算过程。

考虑到1~10000的长时间计算过程,UI上增加一个Stop按钮。

v2

下面直接上代码。

资源文件

两个编辑框都编程只读,多了一个Stop按钮。

IDD_CALC_DIALOG DIALOGEX 0, 0, 181, 66
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "A Simple Calculator"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    RTEXT           "a:",IDC_STATIC,19,20,24,8
    EDITTEXT        IDC_A,51,19,46,12,ES_AUTOHSCROLL | ES_READONLY
    RTEXT           "a * a:",IDC_STATIC,19,40,24,8
    EDITTEXT        IDC_AXA,51,39,46,12,ES_AUTOHSCROLL | ES_READONLY
    PUSHBUTTON      "Calculate",IDC_CALC,113,18,54,14
    PUSHBUTTON      "Stop",IDC_STOP,114,38,52,14
END

my_data_types.h

#pragma once

#define MY_MSG_ID WM_USER + 1

CMathLib

头文件:

#pragma once
class CMathLib
{
public:
    CMathLib(void);
    ~CMathLib(void);

    void SetOwner(CWnd *pOwner);
    void Square(DWORD maxValue);
    void Stop();

    static DWORD WINAPI ThreadProc(LPVOID lpThreadParameter);

private:
    BOOL m_bStop;
    DWORD m_dwMaxValue;
    CWnd *m_pOwner;
};

.cpp:

#include "StdAfx.h"
#include "MathLib.h"
#include "my_data_types.h"

CMathLib::CMathLib(void): m_bStop(FALSE), m_dwMaxValue(1), m_pOwner(NULL)
{
}

CMathLib::~CMathLib(void)
{
}

void CMathLib::SetOwner(CWnd *pOwner)
{
    m_pOwner = pOwner;
}

void CMathLib::Square(DWORD maxValue)
{
    m_dwMaxValue = maxValue;

    // You should CloseHandle(hThread) at sp.
    HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc, this, CREATE_SUSPENDED, NULL);
    ::ResumeThread(hThread);
}

DWORD WINAPI CMathLib::ThreadProc(LPVOID lpThreadParameter) 
{
    CMathLib* pMathLib = (CMathLib*)lpThreadParameter;
    DWORD result;
    HANDLE hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

    for (DWORD i = 1; i < pMathLib->m_dwMaxValue; i++) {
        result = i * i;
        ::PostMessage(pMathLib->m_pOwner->m_hWnd, MY_MSG_ID, i, result);
        ::WaitForSingleObject(hEvent, 2000);

        if (pMathLib->m_bStop) break;
    }

    return 0;
}

void CMathLib::Stop()
{
    m_bStop = TRUE;
}

对话框

头文件:

public:
    DWORD m_a;
    DWORD m_axa;
    afx_msg void OnBnClickedCalc();
    afx_msg LRESULT OnMyMsg(WPARAM wParam, LPARAM lParam);
    afx_msg void OnBnClickedStop();
};

.cpp文件:

#include "my_data_types.h"
#include "MathLib.h"

CCalcDlg::CCalcDlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(CCalcDlg::IDD, pParent)
    , m_a(0)
    , m_axa(0)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CCalcDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_A, m_a);
    DDX_Text(pDX, IDC_AXA, m_axa);
}

BEGIN_MESSAGE_MAP(CCalcDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_CALC, &CCalcDlg::OnBnClickedCalc)
    ON_MESSAGE(MY_MSG_ID, OnMyMsg)
    ON_BN_CLICKED(IDC_STOP, &CCalcDlg::OnBnClickedStop)
END_MESSAGE_MAP()

static CMathLib m_mathLib; //m_: Module level. You can define it as a member.

void CCalcDlg::OnBnClickedCalc()
{
    m_mathLib.SetOwner(this);
    m_mathLib.Square(10000);
}

LRESULT CCalcDlg::OnMyMsg(WPARAM wParam, LPARAM lParam)
{
    m_a = (DWORD)wParam;
    m_axa = (DWORD)lParam;

    UpdateData(FALSE);

    return 0L;
}

void CCalcDlg::OnBnClickedStop()
{
    m_mathLib.Stop();
}
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值