你的第一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)比這種相對簡單的例子更復雜。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章