• 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
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    侧边框伸缩
    百度登录界面
    PHP 判断是否包含在某个字符串中
    三个等于号===和两个等于号==的区别
    PHP的魔法方法
    Apache和PHP环境配置
    群同构与线性空间同构的区别
    SciPy0.11.0(or higher)安装
    博客搬家
    简单的组件传值
  • 原文地址:https://www.cnblogs.com/findumars/p/10217543.html
Copyright © 2020-2023  润新知