文章目录
- 1、DuiLib控件消息响应处理
- 2、基本的消息响应处理 Notify
- 3、仿 MFC 形式消息响应 DUI_DECLARE_MESSAGE_MAP
- 4、事件委托 MakeDelegate
- 5、消息捕获(拦截)原生消息 HandleMessage
1、DuiLib控件消息响应处理
<?xml version="1.0" encoding="UTF-8"?>
<Window size="640,480" mininfo="640,480" caption="0,0,0,35" sizebox="4,4,4,4">
<Default name="Button" value="bordersize="5" bordercolor="#FF222222"" />
<Font shared="true" id="0" name="幼圆" size="12" default="true" />
<Font shared="true" id="1" name="微软雅黑" size="18" underline="true" bold="true"/>
<VerticalLayout>
<!--标题栏-->
<HorizontalLayout height="50" bkcolor="#FFD6DBE9" inset="4,4,8,6" >
<HorizontalLayout width="185">
<Control bkimage="logo.png" height="32" width="32" />
<Label text="duilib tutorial" height="32" padding="8,-2,4,0" font="1" />
</HorizontalLayout>
<Control />
<HorizontalLayout childpadding="3" width="100">
<Button name="minbtn" height="32" width="32" normalimage="btn_min_normal.png" hotimage="btn_min_hovered.png" pushedimage="btn_min_pushed.png" />
<Button name="maxbtn" height="32" width="32" normalimage="btn_max_normal.png" hotimage="btn_max_hovered.png" pushedimage="btn_max_pushed.png" />
<Button name="restorebtn" visible="false" height="32" width="32" normalimage="btn_reset_normal.png" hotimage="btn_reset_hovered.png" pushedimage="btn_reset_pushed.png" />
<Button name="closebtn" height="32" width="32" normalimage="btn_close_normal.png" hotimage="btn_close_hovered.png" pushedimage="btn_close_pushed.png" />
</HorizontalLayout>
</HorizontalLayout>
<!--窗口内容区域-->
<HorizontalLayout bkcolor="#FFD81E06">
</HorizontalLayout>
</VerticalLayout>
</Window>
上一篇我们介绍了如何通过命名的 XML 控件转化为实际可操控的对象,实际上我们已经可以调用这些控件的一些方法来操作控件了,比如:
void MainWndFrame::InitWindow(){
btn_min_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("btn_wnd_min")));
btn_max_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("btn_wnd_max")));
btn_close_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("btn_wnd_close")));
btn_min_->SetVisible(false);
}
我们调用了 CButtonUI 的 SetVisible 方法,将最小化控件隐藏了。但实际这并没有什么作用,我们真正需要的是点击某个控件后执行某些操作。
2、基本的消息响应处理 Notify
接下来我们希望实现更实用的功能,点击最小化按钮把窗口最小化、点击关闭按钮把窗口关闭等。这就要涉及到对控件消息的处理,同样父类 WindowImplBase 提供了 Notify 虚函数,可以提供我们覆写并处理消息。一下代码实现了点击最小化按钮将窗口最小化的功能。
// new_test_demo.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "new_test_demo.h"
#include<iostream>
#include"UIlib.h"
using namespace DuiLib;
//duilib_tutorial.cpp: 定义应用程序的入口点。
//
//WindowImplBase窗口基类
class MainWndFrame : public WindowImplBase
{
public:
MainWndFrame();
protected:
virtual CDuiString GetSkinFolder() override; // 获取皮肤文件的目录,如果有多层目录这里可以设置
virtual CDuiString GetSkinFile() override; // 设置皮肤文件名字
virtual LPCTSTR GetWindowClassName(void) const override; // 设置当前窗口的 class name
virtual void InitWindow() override; //窗口初始化函数
virtual void Notify(TNotifyUI& msg) override; //通知事件处理函数
//virtual DuiLib::UILIB_RESOURCETYPE GetResourceType() const override;
//virtual LPCTSTR GetResourceID() const override;
public:
static const LPCTSTR kClassName;
static const LPCTSTR kMainWndFrame;
private:
CButtonUI* btn_min_;
CButtonUI* btn_max_;
CButtonUI* btn_reset_;
CButtonUI* btn_close_;
};
MainWndFrame::MainWndFrame(){
btn_min_ = nullptr;
btn_max_ = nullptr;
btn_reset_ = nullptr;
btn_close_ = nullptr;
}
DuiLib::CDuiString MainWndFrame::GetSkinFolder()
{
// GetInstancePath 接口返回默认的皮肤文件位置
// 在 main 函数中我们可以通过 SetResourcePath 来设置路径
return m_PaintManager.GetInstancePath();
}
DuiLib::CDuiString MainWndFrame::GetSkinFile()
{
// 成员变量定义的皮肤文件名
return kMainWndFrame;
}
LPCTSTR MainWndFrame::GetWindowClassName(void) const
{
// 成员变量定义的窗口 class name
return kClassName;
}
void MainWndFrame::InitWindow(){
btn_min_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("minbtn")));
btn_max_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("maxbtn")));
btn_reset_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("restorebtn")));
btn_close_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("closebtn")));
//btn_min_->SetVisible(false);
}
void MainWndFrame::Notify(TNotifyUI& msg){
if (msg.sType == DUI_MSGTYPE_CLICK){
CDuiString str_name = msg.pSender->GetName();
if (str_name == _T("mintbn")){
SendMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0);
}
else if (str_name == _T("maxbtn")){
SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE, 0);
}
else if (str_name==_T("restorebtn")){
SendMessage(WM_SYSCOMMAND, SC_RESTORE, 0);
}
else if (str_name == _T("closebtn")){
SendMessage(WM_SYSCOMMAND, SC_CLOSE, 0);
}
}
__super::Notify(msg);
}
//theme压缩包资源
//DuiLib::UILIB_RESOURCETYPE MainWndFrame::GetResourceType() const
//{
// return UILIB_ZIPRESOURCE;
//}
//
//LPCTSTR MainWndFrame::GetResourceID() const
//{
// return MAKEINTRESOURCE(IDR_ZIPRES1);
//}
//生成的窗口名字
const LPCTSTR MainWndFrame::kClassName = _T("main_wnd_frame");
//theme文件下的xml文件
const LPCTSTR MainWndFrame::kMainWndFrame = _T("main_wnd_frame.xml");
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
//UNREFERENCED_PARAMETER(hPrevInstance);
//UNREFERENCED_PARAMETER(lpCmdLine);
// 设置窗口关联的实例
CPaintManagerUI::SetInstance(hInstance);
// 设置皮肤的默认路径
CPaintManagerUI::SetCurrentPath(CPaintManagerUI::GetInstancePath());
//theme文件夹
CPaintManagerUI::SetResourcePath(_T("theme"));
//theme压缩包
//CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());
//CPaintManagerUI::SetResourceZip(_T("theme.zip"));
// 创建窗口
MainWndFrame* pMainWndFrame = new MainWndFrame();
if (nullptr == pMainWndFrame){
return 0;
}
pMainWndFrame->Create(nullptr, MainWndFrame::kClassName, UI_WNDSTYLE_DIALOG, 0);
pMainWndFrame->CenterWindow();
pMainWndFrame->ShowWindow();
CPaintManagerUI::MessageLoop();
if (nullptr != pMainWndFrame)
{
delete pMainWndFrame;
}
return 0;
}
首先我们在 Notify 函数中判断了一下消息的类型,如果是鼠标点击那么我们获取一下触发的控件名称,根据名称判断是不是 btn_wnd_min 然后执行指定操作。最后别忘记调用父类的 Notify 函数来继续其他消息的处理(其实父类什么都没做)。
然后这里我在加上放大和关闭按钮:
void MainWndFrame::Notify(TNotifyUI& msg){
if (msg.sType == DUI_MSGTYPE_CLICK){
CDuiString str_name = msg.pSender->GetName();
if (str_name == _T("minbtn")){
SendMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0);
}
else if (str_name == _T("maxbtn")){
SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE, 0);
}
else if (str_name==_T("restorebtn")){
SendMessage(WM_SYSCOMMAND, SC_RESTORE, 0);
}
else if (str_name == _T("closebtn")){
SendMessage(WM_SYSCOMMAND, SC_CLOSE, 0);
}
}
__super::Notify(msg);
}
3、仿 MFC 形式消息响应 DUI_DECLARE_MESSAGE_MAP
以上是一个基本的响应过程。另外还有一种类似 MFC 方式的响应方法,首先在 new_test_demo.h 中添加一句 DUI_DECLARE_MESSAGE_MAP()。
void MainWndFrame::OnClick(TNotifyUI& msg){
CDuiString str_name = msg.pSender->GetName();
if (str_name == _T("mintbn")){
SendMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0);
return;
}
else if (str_name == _T("maxbtn")){
SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE, 0);
return;
}
else if (str_name == _T("restorebtn")){
SendMessage(WM_SYSCOMMAND, SC_RESTORE, 0);
return;
}
else if (str_name == _T("closebtn")){
Close();
return;
}
return;
}
然后在 new_test_demo.h 中添加如下代码:
DUI_BEGIN_MESSAGE_MAP(MainWndFrame, CNotifyPump)
DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK, OnClick)
DUI_END_MESSAGE_MAP()
这样我们就将 DUI_MSGTYPE_CLICK 类型的消息映射到了 OnClick 函数中,而 OnClick 函数在父类 WindowImplBase 中已经提供了一个虚函数了。
void WindowImplBase::OnClick(TNotifyUI& msg)
{
CDuiString sCtrlName = msg.pSender->GetName();
if( sCtrlName == _T("closebtn") )
{
Close();
return;
}
else if( sCtrlName == _T("minbtn"))
{
SendMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0);
return;
}
else if( sCtrlName == _T("maxbtn"))
{
SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE, 0);
return;
}
else if( sCtrlName == _T("restorebtn"))
{
SendMessage(WM_SYSCOMMAND, SC_RESTORE, 0);
return;
}
return;
}
可以看出,DuiLib 已经默认帮我们实现了几个按钮的鼠标点击功能。我们只需要根据它设定的名字修改一下我们控件的 name 属性就可以实现几个功能了。当然如果我们要添加其他控件的处理,是需要覆写这个 OnClick 函数的。
修改完成后最小化、最大化、还原三个按钮都可以正常工作了,但是关闭按钮点击后并不能完全退出程序,而仅仅是把程序隐藏了,这主要原因是当我们点击关闭按钮时调用的是父类的 Close 函数,该函数发送了退出消息后,窗口接收到该消息的处理函数 OnClose 未做任何措施,如下所示:
父类中的**WindowImplBase::OnClose()**任务管理器中的进程并没有结束掉:
要解决这个问题很简单,我们只需要覆写一下这个 OnClose 方法,然后执行退出操作就可以了。
LRESULT MainWndFrame::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (uMsg == WM_CLOSE)
{
PostQuitMessage(0L);
}
return __super::OnClose(uMsg, wParam, lParam, bHandled);
}
覆写完成后,我们三个功能按钮(哦不,是四个)就都可以正常使用了。另外我自己还发现了两个小问题,窗口的标题栏双击是无法最大化的,这个解决很简单,在 main 函数创建窗口的时候,将窗口的 UI_WNDSTYLE_DIALOG 属性修改为 UI_WNDSTYLE_FRAME 就可以了,至于两个参数什么意思,我有一篇DuiLib文章介绍了,有兴趣可以去查看。
pMainWndFrame->Create(nullptr, MainWndFrame::kClassName, UI_WNDSTYLE_FRAME, 0);
另外一个问题是窗口是无法拖动放大缩小的,这个也很好解决,我们修改 XML,添加上窗口最小大小和可拖动范围就可以了。如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Window size="640,480" mininfo="640,480" caption="0,0,0,35" sizebox="4,4,4,4">
<Default name="Button" value="bordersize="5" bordercolor="#FF222222"" />
<Font shared="true" id="0" name="幼圆" size="12" default="true" />
<Font shared="true" id="1" name="微软雅黑" size="18" underline="true" bold="true"/>
<VerticalLayout>
<!--标题栏-->
<HorizontalLayout height="50" bkcolor="#FFD6DBE9" inset="4,4,8,6" >
<HorizontalLayout width="185">
<Control bkimage="logo.png" height="32" width="32" />
<Label text="duilib tutorial" height="32" padding="8,-2,4,0" font="1" />
</HorizontalLayout>
<Control />
<HorizontalLayout childpadding="3" width="100">
<Button name="btn_wnd_min" height="32" width="32" normalimage="btn_min_normal.png" hotimage="btn_min_hovered.png" pushedimage="btn_min_pushed.png" />
<Button name="btn_wnd_max" height="32" width="32" normalimage="btn_max_normal.png" hotimage="btn_max_hovered.png" pushedimage="btn_max_pushed.png" />
<Button name="btn_wnd_reset" height="32" width="32" normalimage="btn_reset_normal.png" hotimage="btn_reset_hovered.png" pushedimage="btn_reset_pushed.png" visible="false"/>
<Button name="btn_wnd_close" height="32" width="32" normalimage="btn_close_normal.png" hotimage="btn_close_hovered.png" pushedimage="btn_close_pushed.png" />
</HorizontalLayout>
</HorizontalLayout>
<!--窗口内容区域-->
<HorizontalLayout bkcolor="#FFD81E06">
</HorizontalLayout>
</VerticalLayout>
</Window>
mininfo 属性决定了窗口最小大小,sizebox 属性是指定当鼠标移动到窗口边缘多少像素的时候显示拖放手势。这里指定的是 4 像素,这样指定后窗口就可以拖动了,而且最小不允许小于默认的 640x480。
4、事件委托 MakeDelegate
除了以上两种方式外,我们还可以通过事件委托的方式来处理指定控件的消息。如下示例演示了事件委托的实现方式。
void MainWndFrame::InitWindow()
{
m_pMinBtn = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("minbtn")));
m_pMaxBtn = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("maxbtn")));
m_pRestoreBtn = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("restorebtn")));
m_pCloseBtn = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("closebtn")));
m_pMinBtn->OnNotify += MakeDelegate(this, &MainWndFrame::OnBtnTest);
}
在 InitWindow 函数中,我们给最小化按钮委托了一个 OnBtnTest 的处理函数,当我们对最小化按钮做某些操作时,就会到达 OnBtnTest 处理函数中。OnBtnTest 的实现如下:
bool MainWndFrame::OnBtnTest(void* param)
{
TNotifyUI* msg = reinterpret_cast<TNotifyUI*>(param);
if (msg->sType == DUI_MSGTYPE_CLICK)
{
// ... do something
}
return true;
}
这种方式同样可以实现处理控件的消息功能,如果对委托的函数指针加以改造,还可以使用 C++11 的 lambda 表达式来实现具体的处理函数功能。
5、消息捕获(拦截)原生消息 HandleMessage
DuiLib 提供了虚函数 HandleMessage,可以提供我们覆写来捕获或者拦截原声的系统消息。比如我们希望监听剪切板的消息时,就可以像一下方法一样来实现。
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT MainWndFrame::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam){
if (uMsg == WM_CHANGECBCHAIN){
//
}
else if (uMsg = WM_DRAWCLIPBOARD){
//
}
return __super::HandleMessage(uMsg, wParam, lParam);
}
老版本的 DuiLib 中窗口创建完成后,按下 ESC 窗口会被关闭,如果想屏蔽掉 ESC 按下的消息,就可以通过这个函数来实现。
// new_test_demo.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "new_test_demo.h"
#include<iostream>
#include"UIlib.h"
using namespace DuiLib;
//duilib_tutorial.cpp: 定义应用程序的入口点。
//
//WindowImplBase窗口基类
class MainWndFrame : public WindowImplBase
{
public:
MainWndFrame();
protected:
virtual CDuiString GetSkinFolder() override; // 获取皮肤文件的目录,如果有多层目录这里可以设置
virtual CDuiString GetSkinFile() override; // 设置皮肤文件名字
virtual LPCTSTR GetWindowClassName(void) const override; // 设置当前窗口的 class name
virtual void InitWindow() override; //窗口初始化函数
//virtual void Notify(TNotifyUI& msg) override; //通知事件处理函数
virtual void OnClick(TNotifyUI& msg) override;
virtual LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) override;
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
DUI_DECLARE_MESSAGE_MAP()
//virtual DuiLib::UILIB_RESOURCETYPE GetResourceType() const override;
//virtual LPCTSTR GetResourceID() const override;
public:
static const LPCTSTR kClassName;
static const LPCTSTR kMainWndFrame;
private:
CButtonUI* btn_min_;
CButtonUI* btn_max_;
CButtonUI* btn_reset_;
CButtonUI* btn_close_;
};
MainWndFrame::MainWndFrame(){
btn_min_ = nullptr;
btn_max_ = nullptr;
btn_reset_ = nullptr;
btn_close_ = nullptr;
}
DUI_BEGIN_MESSAGE_MAP(MainWndFrame, CNotifyPump)
DUI_ON_MSGTYPE(DUI_MSGTYPE_CLICK, OnClick)
DUI_END_MESSAGE_MAP()
DuiLib::CDuiString MainWndFrame::GetSkinFolder()
{
// GetInstancePath 接口返回默认的皮肤文件位置
// 在 main 函数中我们可以通过 SetResourcePath 来设置路径
return m_PaintManager.GetInstancePath();
}
DuiLib::CDuiString MainWndFrame::GetSkinFile()
{
// 成员变量定义的皮肤文件名
return kMainWndFrame;
}
LPCTSTR MainWndFrame::GetWindowClassName(void) const
{
// 成员变量定义的窗口 class name
return kClassName;
}
void MainWndFrame::InitWindow(){
btn_min_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("mintbn")));
btn_max_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("maxbtn")));
btn_reset_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("restorebtn")));
btn_close_ = dynamic_cast<CButtonUI*>(m_PaintManager.FindControl(_T("closebtn")));
//btn_min_->SetVisible(false);
}
//void MainWndFrame::Notify(TNotifyUI& msg){
// if (msg.sType == DUI_MSGTYPE_CLICK){
// CDuiString str_name = msg.pSender->GetName();
// if (str_name == _T("btn_wnd_min")){
// SendMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0);
// }
// else if (str_name == _T("btn_wnd_max")){
// SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE, 0);
// }
// else if (str_name==_T("btn_wnd_reset")){
// SendMessage(WM_SYSCOMMAND, SC_RESTORE, 0);
// }
// else if (str_name == _T("btn_wnd_close")){
// SendMessage(WM_SYSCOMMAND, SC_CLOSE, 0);
// }
// }
// __super::Notify(msg);
//}
void MainWndFrame::OnClick(TNotifyUI& msg){
CDuiString str_name = msg.pSender->GetName();
if (str_name == _T("minbtn")){
SendMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0);
return;
}
else if (str_name == _T("maxbtn")){
SendMessage(WM_SYSCOMMAND, SC_MAXIMIZE, 0);
return;
}
else if (str_name == _T("restorebtn")){
SendMessage(WM_SYSCOMMAND, SC_RESTORE, 0);
return;
}
else if (str_name == _T("closebtn")){
Close();
return;
}
return;
}
LRESULT MainWndFrame::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
if (uMsg == WM_CLOSE){
PostQuitMessage(0L);
}
return __super::OnClose(uMsg, wParam, lParam, bHandled);
}
LRESULT MainWndFrame::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam){
if (uMsg == WM_CHANGECBCHAIN){
//
}
else if (uMsg = WM_DRAWCLIPBOARD){
//
}
return __super::HandleMessage(uMsg, wParam, lParam);
}
//theme压缩包资源
//DuiLib::UILIB_RESOURCETYPE MainWndFrame::GetResourceType() const
//{
// return UILIB_ZIPRESOURCE;
//}
//
//LPCTSTR MainWndFrame::GetResourceID() const
//{
// return MAKEINTRESOURCE(IDR_ZIPRES1);
//}
//生成的窗口名字
const LPCTSTR MainWndFrame::kClassName = _T("main_wnd_frame");
//theme文件下的xml文件
const LPCTSTR MainWndFrame::kMainWndFrame = _T("main_wnd_frame.xml");
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
//UNREFERENCED_PARAMETER(hPrevInstance);
//UNREFERENCED_PARAMETER(lpCmdLine);
// 设置窗口关联的实例
CPaintManagerUI::SetInstance(hInstance);
// 设置皮肤的默认路径
CPaintManagerUI::SetCurrentPath(CPaintManagerUI::GetInstancePath());
//theme文件夹
CPaintManagerUI::SetResourcePath(_T("theme"));
//theme压缩包
//CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());
//CPaintManagerUI::SetResourceZip(_T("theme.zip"));
// 创建窗口
MainWndFrame* pMainWndFrame = new MainWndFrame();
if (nullptr == pMainWndFrame){
return 0;
}
pMainWndFrame->Create(nullptr, MainWndFrame::kClassName, UI_WNDSTYLE_FRAME, 0);
pMainWndFrame->CenterWindow();
pMainWndFrame->ShowWindow();
CPaintManagerUI::MessageLoop();
if (nullptr != pMainWndFrame)
{
delete pMainWndFrame;
}
return 0;
}