Duilib学习之基础(一个SDK程序)
版权声明:本文为灿哥哥http://blog.csdn.net/caoshangpa原创文章,转载请标明出处。 https://blog.csdn.net/caoshangpa/article/details/84201952
一个Win32窗口程序
创建一个空的Win32工程,然后输入以下代码。
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
// 窗口过程
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
MessageBox(hWnd, "你单击了鼠标左键", "WM_LBUTTONDOWN", MB_OK);
break;
case WM_CHAR:
char szChar[64];
sprintf_s(szChar, 64, "你按下了键盘键:%c", wParam);
MessageBox(hWnd, szChar, "WM_CHAR", MB_OK);
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
SetTextColor(hdc, RGB(255, 0, 0));
SetBkColor(hdc, RGB(0, 255, 0));
TextOut(hdc, 200, 200, "Hello World!", strlen("Hello World!"));
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
// 注册窗口类
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_HAND);
wcex.hbrBackground = (HBRUSH)(GetStockObject(BLACK_BRUSH));
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "mywndclass";
wcex.hIconSm = NULL;
RegisterClassEx(&wcex);
// 创建窗口
HWND hWnd = CreateWindowEx(0, "mywndclass", "This is a win32 wnd", WS_OVERLAPPEDWINDOW,
100, 100, 800, 600, NULL, NULL, hInstance, NULL);
// 显示窗口
ShowWindow(hWnd, nCmdShow);
// 窗口消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
运行效果
所用的Windows API在MSDN中都可以查到,这里重点说一下TranslateMessage和DispatchMessage。
TranslateMessage函数将虚拟键消息转换成字符消息。比如:
消息WM_KEYDOWN和WM_KEYUP组合产生一个WM_CHAR或WM_DEADCHAR消息
消息WM_SYSKEYDOWN和WM_SYSKEYUP组合产生一个WM_SYSCHAR或 WM_SYSDEADCHAR 消息
然后放在队列中,等待下一次线程调用GetMessage或PeekMessage时被读出
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
if (msg.message==WM_KEYDOWN)
{
MessageBox(0, L"KeyDown1", 0, 0);
}
/*
WM_KEYDOWN和WM_KEYUP组合产生一个WM_CHAR或WM_DEADCHAR消息。
消息WM_SYSKEYDOWN和WM_SYSKEYUP组合产生一个WM_SYSCHAR或 WM_SYSDEADCHAR 消息
放在队列中,等待下一次线程调用GetMessage或PeekMessage时被读出
将虚拟键消息转换为字符消息。
字符消息被送到调用线程的消息队列中,在下一次线程调用函数GetMessage或PeekMessage时被读出。
*/
TranslateMessage(&msg);
GetMessage(&msg, NULL, 0, 0);
if (msg.message == WM_KEYDOWN)
{
MessageBox(0, L"KeyDown2", 0, 0);
}
if (msg.message == WM_CHAR){
MessageBox(0, L"Translate", 0, 0);
}
/*
该函数调度一个消息给窗口程序。通常调度从GetMessage取得的消息。
消息被调度到的窗口程序即是MainProc()函数
*/
DispatchMessage(&msg);
}
}
会发现按下一个键后会产生WM_KEYDOWN消息,经过TranslateMessage翻译后组合生成WM_CHAR消息
然后投放到消息队列中,使用getMessage取出刚刚投放的消息,判断后,发现产生的是一个WM_CHAR消息
TtanslateMessage函数仅为那些由键盘驱动器映射为ASCII字符的键产生WM_CHAR消息
但是TranslateMessage不会丢弃原来的WM_KEYDOWN消息。依旧可以继续调用函数: WndProc(HWND, UINT, WPARAM, LPARAM)处理这个消息
case WM_KEYDOWN:
MessageBox(0, L"Key Down", 0, 0);
break;
只需要记住这条主线,注册窗口类->创建窗口->显示窗口->窗口消息循环,在接下来学习DUiLib的过程中我们就不会被绕晕了。
使用DuiLib的CWindowWnd
上面的win32窗口程序还是面向过程编程,C++是擅长面向对象编程OOP的,我们很自然的想到可以用一个类去封装窗口句柄HWND和对应创建窗口、显示窗口等方法,DuiLib中CWindowWnd就是这个窗口类,分别在UIBase.h和UIBase.cpp中声明和定义。
创建一个空的Win32工程,配置好Duilib头文件和库文件,然后输入以下代码。
#include "UIlib.h"
using namespace DuiLib;
class CFrameWnd : public CWindowWnd
{
public:
virtual LPCTSTR GetWindowClassName() const {
return _T("FrameWnd");
}
virtual void OnFinalMessage(HWND hWnd) {
delete this;
}
};
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nShowCmd) {
// new一个窗口对象
CFrameWnd* pFrame = new CFrameWnd;
// 注册窗口类、创建窗口
pFrame->Create(NULL, _T("sample01"), UI_WNDSTYLE_FRAME, UI_WNDSTYLE_EX_FRAME,
100, 100, 800, 600, NULL);
// 显示窗口、进入窗口消息循环
pFrame->ShowModal();
return 0;
}
运行效果
使用CFrameWnd继承自CWindowWnd,CWindowWnd必须实现的一个纯虚接口是GetWindowClassName来表明它的窗口类名。在OnFinalMessage中delete this是因为DuiLib中需要使用new来生成一个窗口,delete可以防止内存泄漏(在后面的DuiLib程序中可以看到都只有new而没有delete,这是因为DuiLib内部在窗口销毁时已经做了delete的操作)。
一.创建窗口
HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
{
if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
ASSERT(m_hWnd!=NULL);
return m_hWnd;
}
bool CWindowWnd::RegisterWindowClass()
{
WNDCLASS wc = { 0 };
wc.style = GetClassStyle();
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hIcon = NULL;
wc.lpfnWndProc = CWindowWnd::__WndProc;
wc.hInstance = CPaintManagerUI::GetInstance();
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = GetWindowClassName();
ATOM ret = ::RegisterClass(&wc);
ASSERT(ret!=NULL || ::GetLastError()==ERROR_CLASS_ALREADY_EXISTS);
return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
}
可以发现在调用CreateWindowEx这个windows API前会先调用RegisterWindowClass注册窗口类。 这里的实例句柄传入的是CPaintManagerUI::GetInstance(),因为此前并未用CPaintManagerUI::SetInstance(HINSTANCE hInst)进行设置,所以这里的实例句柄是NULL,但这并不影响窗口的创建。还有一点需要注意的是 CreateWindowEx的最后一个参数将this指针作为参数传递了进去,这个玩意在后面可有妙用。
二.显示窗口并进入消息循环
UINT CWindowWnd::ShowModal()
{
ASSERT(::IsWindow(m_hWnd));
UINT nRet = 0;
HWND hWndParent = ::GetWindowOwner(m_hWnd);
::ShowWindow(m_hWnd, SW_SHOWNORMAL);
::EnableWindow(hWndParent, FALSE);
MSG msg = { 0 };
while( ::IsWindow(m_hWnd) && ::GetMessage(&msg, NULL, 0, 0) ) {
if( msg.message == WM_CLOSE && msg.hwnd == m_hWnd ) {
nRet = msg.wParam;
::EnableWindow(hWndParent, TRUE);
::SetFocus(hWndParent);
}
if( !CPaintManagerUI::TranslateMessage(&msg) ) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
if( msg.message == WM_QUIT ) break;
}
::EnableWindow(hWndParent, TRUE);
::SetFocus(hWndParent);
if( msg.message == WM_QUIT ) ::PostQuitMessage(msg.wParam);
return nRet;
}
可以发现先调用了ShowWindow去显示窗口,然后进入了我们熟悉的GetMessage消息循环。考虑到可能是子窗口关闭WM_CLOSE 所以有些额外处理。
三.窗口过程
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWindowWnd* pThis = NULL;
if( uMsg == WM_NCCREATE ) {
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
pThis->m_hWnd = hWnd;
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
}
else {
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if( uMsg == WM_NCDESTROY && pThis != NULL ) {
LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
if( pThis->m_bSubclassed ) pThis->Unsubclass();
pThis->m_hWnd = NULL;
pThis->OnFinalMessage(hWnd);
return lRes;
}
}
if( pThis != NULL ) {
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
窗口过程因为是回调函数,所以声明成static类型,static类型是不能使用非static成员的,那么问题来了,他怎么去获取CWindowWnd对象指针,然后去调用CWindowWnd里面的方法呢。这就是之前CreateWindowEx将this指针传进去的原因了,在WM_NCCREATE时通过将lParam转化成LPCREATESTRUCT,里面的lpCreateParams就是this指针了,然后通过SetWindowLongPtr将this设置为用户数据,再处理其它的WM_消息时通过GetWindowLongPtr获取到this指针,进而可以调用CWindowWnd的方法了(比如这里调用了HandleMessage来处理感兴趣的WM_消息),这是我们自己封装窗口类难以想到的一点吧(小tips:在声明回调函数时我们一般将最后一个参数设置为用户数据)。当然有的同学会说使用map容器将HWND与CWindowWnd对应起来,这也是一种方法,但总归没有使用用户数据来的直接简便。
参考链接:https://blog.csdn.net/GG_SiMiDa/article/details/70792890
---------------------
作者:灿哥哥
来源:CSDN
原文:https://blog.csdn.net/caoshangpa/article/details/84201952
版权声明:本文为博主原创文章,转载请附上博文链接!
Duilib学习之基础(一个SDK程序)的更多相关文章
- swift学习:第一个swift程序
原文:swift学习:第一个swift程序 最近swift有点火,赶紧跟上学习.于是,个人第一个swift程序诞生了... 新建项目
- Spring学习之第一个AOP程序
IOC和AOP是Spring的两大基石,AOP(面向方面编程),也可称为面向切面编程,是一种编程范式,提供从另一个角度来考虑程序结构从而完善面向对象编程(OOP). 在进行 OOP 开发时,都是基于对 ...
- Android开发学习之三——第一个Android程序
下面我们建立第一个Android程序. 打开Eclipse,开始如下步骤: 1.File ==> New ==> Android Application Project 出现如下窗口: 2 ...
- python flask框架学习(二)——第一个flask程序
第一个flask程序 学习自:知了课堂Python Flask框架——全栈开发 1.用pycharm新建一个flask项目 2.运行程序 from flask import Flask # 创建一个F ...
- JavaWeb学习系列——第一个JavaWeb程序
创建JavaWeb项目 Eclipse中新建一个Dynamic Web Project 指定项目名称.依赖环境 勾选生成web.xml选项 更改项目编译输出目录,项目右键 ->propertie ...
- c++学习笔记---03---从一个小程序说起2
从一个小程序说起2 要求:编写一个程序,要求用户输入一串整数和任意数目的空格,这些整数必须位于同一行中,但允许出现在该行中的任何位置.当用户按下键盘上的"Enter"键时,数据输入 ...
- c++学习笔记---02---从一个小程序说起
从一个小程序说起 这一讲的主要目的是帮助大家在C语言的背景知识上与C++建立联系. 问题探索 问题:对一个整型数组求和. 要求:定义一个存储着 n 个元素的数组,要求用C语言完成这个任务. 赶紧的:大 ...
- python新手第一天学习笔记-第一个ptyhon程序和python变量
一.python 的注释和第一个python 程序 : 1.单行注释 # Author Xiajq 2.多行注释 ''' ------------注释内容----------------------- ...
- Android学习笔记_70_一个应用程序启动另一个应用程序的Activity
第一种(我自己写的) :之前在网上看来一些,很多不是我要的可以启动另外一个应用程序的主Activity. //这些代码是启动另外的一个应用程序的主Activity,当然也可以启动任意一个Activit ...
- JAVA学习之第一个HelloWorld程序
第一个HelloWorld程序 第一步,创建java类型的文件 第二步,在创建文件的目录中打开cmd窗口 第三步,使用javac 命令将java文件编译为.class类型的字节码文件 第四步,使用ja ...
随机推荐
- Yii中的数据库事务的使用方法小结
在项目中遇到批量删除的地方一般会使用到事务,下面列举一个用法实例与大家分享. 查看代码 打印 01 <?php 02 $array=array( 03 0=>array('us ...
- CUDA程序计时
之前写的CUDA程序,想测量一下性能,网上很多用的是CPU端计时,很不准确.翻了一下书,发现这里应该使用事件来计时. CUDA中的事件本质上是一个GPU时间戳,这个时间戳是在用户指定的时间点上记录的. ...
- AC日记——[SCOI2010]幸运数字 bzoj 1853
1853: [Scoi2010]幸运数字 Time Limit: 2 Sec Memory Limit: 64 MBSubmit: 2405 Solved: 887[Submit][Status] ...
- osstatus -9801 workerman websocket 小程序不带端口
帮事业部的同事,解决问题,坑总结 小程序出现,osstatus -9801 情况好多,说一下配置环境可解决的方法和问题 tls 1.2, php 5.6+, nginx, workerman 做的 w ...
- 牛客网 Wannafly挑战赛11 A.白兔的分身术
水一水博客. 链接:https://www.nowcoder.com/acm/contest/73/A来源:牛客网 A.白兔的分身术 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C ...
- c实现的trim函数
功能:去掉字符串首尾的空格,换行符等空白. 代码: #include <string.h> #include <stdio.h> #include <ctype.h> ...
- 邁向IT專家成功之路的三十則鐵律 鐵律十九:IT人待業之道-寬心
說來很多人可能不相信,筆者從來不把失業當作是一件嚴重的事,相反的我會把它當作是一個很好的轉機.針對一個隨時做好準備的IT人,三個月或半年沒有上班完全沒有甚麼好擔心的.只是如何善用待業的時間,說實在的真 ...
- SharpSSH
SharpSSH sharpssh is a pure .NET implementation of the SSH2 client protocol suite. It provides an AP ...
- hdu 5389 Zero Escape (dp)
题目:http://acm.hdu.edu.cn/showproblem.php? pid=5389 题意:定义数根:①把每一位上的数字加起来得到一个新的数,②反复①直到得到的数仅仅有1位.给定n,A ...
- 怎样制作gif图片?怎样制作你项目的动态效果图到你的csdn?
怎样制作gif图?怎样上传你项目的动态效果图到你的csdn? 这仅仅是笔者用的方法.有其它方法的欢迎分享. 一张或几张展示了你的项目的功能及效果的动态图放在博客文章开头会为你的文章润色不少. 相信非常 ...