你的第一Windows程序——管理应用程序状态

MSDN原文(英文)

管理应用程序状态

一个窗口过程仅仅是一个为每个消息获取调用函数,所以它本质上是无状态的。因此,你需要一个方法来跟踪你的应用程序从一个函数调用下一个函数的状态。

最简单的方法是把一切都放在全局变量中。这对于小程序已经足够了,并且许多SDK示例都使用这种方式。然而在一个大型程序,它会导致全局变量的扩散。此外,你可能有几个窗口,每个都有其自己的窗口过程,跟踪哪个窗口应该访问哪些变量变得混乱和易出错。

CreateWindowEx函数提供了一种方法可以将任何数据结构传递给一个窗口,当这个函数被调用,以下两个消息发送到你。
  • WM_NCCREATE
  • WM_CREATE
这些消息按列出的顺序发送(在CreateWindowEx期间这发送的不止这两个消息,但我们在这个讨论里可以忽略)。

WM_NCCREATE和WM_CREATE消息在窗口可见前发送,这是制定和初始化你的UI的好地方——例如,确定窗口的初始布局。

函数CreateWindowEx的最后一个参数是void*类型的指针,在这个参数你可以传递任何你想要的指针值,当窗口过程处理WM_NCCREATE和WM_CREATE消息,它可以从消息数据中提取这个值。

让我们看看如何使用这个参数将应用程序数据传递到你窗口。首先定义一个类或结构保存状态信息:
// 定义一个结构保存一些状态信息

struct StateInfo {

    // ... (结构成员没有显示)

};

当你调用CreateWindowsEx函数,将结构指针传递给最后的void*参数。
StateInfo *pState = new (std::nothrow) StateInfo;
    
    if (pState == NULL)
    {
        return 0;
    }

    // 在这里初始化结构成员 (示例没有显示).

    HWND hwnd = CreateWindowEx(
        0,                              // Optional window styles.
        CLASS_NAME,                     // Window class
        L"Learn to Program Windows",    // Window text
        WS_OVERLAPPEDWINDOW,            // Window style

        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

        NULL,       // Parent window    
        NULL,       // Menu
        hInstance,  // Instance handle
        pState      // 额外的数据信息
        );

当你收到WM_NCCREATE和WM_CREATE消息,每个消息的 lParam的参数指向CREATESTRUCT结构的指针。而后,CREATESTRUCT结构包含的指针传入CreateWindowEx。
图示CREATESTRUCT结构布局
 
以下是如何提取数据结构的指针,首先,通过计算lParam参数获得CREATESTRUCT结构。
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);

CREATESTRUCT
结构 lpCreateParams 成员 CreateWindowEx 指定原始 void 指针 。计算lpCreateParams 获取数据结构的指针。
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);

下一步,调用 SetWindowLoingPtr函数并把指针传入你的数据结构。
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);

最后一个函数的调用的目的是将StateInfo指针储存在窗口的实例数据中。一旦你这样做,你总是可以从窗口调用 GetWindowLongPtr得到指针。
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);

每个窗口都有自己的实例数据,所以你可以创建多个窗口,每个窗口都有自己的数据结构的实例。这种方法是特别有用的,如果你定义一个窗口类和创建该类的多个窗口。比如你创建了一个自定义控件类。GetWindowsLongPtr调用可以很方便的封装在一个小的辅助函数。
inline StateInfo* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
    return pState;
}

现在你可以写你的窗口过程如下:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    StateInfo *pState;
    if (uMsg == WM_CREATE)
    {
        CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
        pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
    }
    else
    {
        pState = GetAppState(hwnd);
    }

    switch (uMsg)
    {

    // Remainder of the window procedure not shown ...

    }
    return TRUE;
}

面向对象的方法

 
我们可以将这种方法进一步。我们已经定义了一个数据结构来保持窗口状态信息。 提供 数据 结构成员 操作函数 (方法) 数据是意义的。这自然会使得一个结构(或类)的设计负责窗口的所有操作。窗口过程会成为类的一部分。
换句话说,我们要从这走:
// 伪代码

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    StateInfo *pState;

    /* Get pState from the HWND. */

    switch (uMsg)
    {
        case WM_SIZE:
            HandleResize(pState, ...);
            break;

        case WM_PAINT:
            HandlePaint(pState, ...);
            break;

       // And so forth.
    }
}

到这:
// 伪代码

LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            this->HandleResize(...);
            break;

        case WM_PAINT:
            this->HandlePaint(...);
            break;
    }
}

唯一的问题是用什么方法连接MyWindow::WindowProc。RegisterClass函数需要的窗口过程是一个函数指针,在此上下文中,你不能将指针传递给(非静态)成员函数。然而,你可以将指针传递给一个静态成员函数,然后委托给成员函数。以下是一个类模板,显示这种方法:
template <class DERIVED_TYPE> 
class BaseWindow
{
public:
    static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        DERIVED_TYPE *pThis = NULL;

        if (uMsg == WM_NCCREATE)
        {
            CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam;
            pThis = (DERIVED_TYPE*)pCreate->lpCreateParams;
            SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);

            pThis->m_hwnd = hwnd;
        }
        else
        {
            pThis = (DERIVED_TYPE*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
        }
        if (pThis)
        {
            return pThis->HandleMessage(uMsg, wParam, lParam);
        }
        else
        {
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
    }

    BaseWindow() : m_hwnd(NULL) { }

    BOOL Create(
        PCWSTR lpWindowName,
        DWORD dwStyle,
        DWORD dwExStyle = 0,
        int x = CW_USEDEFAULT,
        int y = CW_USEDEFAULT,
        int nWidth = CW_USEDEFAULT,
        int nHeight = CW_USEDEFAULT,
        HWND hWndParent = 0,
        HMENU hMenu = 0
        )
    {
        WNDCLASS wc = {0};

        wc.lpfnWndProc   = DERIVED_TYPE::WindowProc;
        wc.hInstance     = GetModuleHandle(NULL);
        wc.lpszClassName = ClassName();

        RegisterClass(&wc);

        m_hwnd = CreateWindowEx(
            dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
            nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this
            );

        return (m_hwnd ? TRUE : FALSE);
    }

    HWND Window() const { return m_hwnd; }

protected:

    virtual PCWSTR  ClassName() const = 0;
    virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;

    HWND m_hwnd;
};

BaseWindow是一个抽象类,从特定的窗口了派生。例如,以下是一个简单的派生类的basewindow的声明:

class MainWindow : public BaseWindow<MainWindow>
{
public:
    PCWSTR  ClassName() const { return L"Sample Window Class"; }
    LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};


要创建一个窗口,调用BaseWindow::Create:

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    MainWindow win;

    if (!win.Create(L"Learn to Program Windows", WS_OVERLAPPEDWINDOW))
    {
        return 0;
    }

    ShowWindow(win.Window(), nCmdShow);

    // Run the message loop.

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}


纯虚拟BaseWindow::HandleMessage 方法用于执行窗口过程。下面的执行是相当于开始显示窗口过程:

LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(m_hwnd, &ps);
            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
            EndPaint(m_hwnd, &ps);
        }
        return 0;

    default:
        return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
    }
    return TRUE;
}

注意,窗口句柄存储在成员变量(m_hwnd)所以我们不需要将它作为一个参数传递给HandleMessage。

许多现有的Windows编程框架,如Microsoft基础类(MFC)和活动模板库(ATL)使用的基本上类似于此处所示的方法。当然,一个完全广义的框架(如MFC)比这种相对简单的例子更复杂。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值