1. 程式人生 > >Duilib學習之基礎(一個SDK程序)

Duilib學習之基礎(一個SDK程序)

edwin 直接 sample lac tail oda des lap clas

版權聲明:本文為燦哥哥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程序)