Windows桌面应用程序(1-2-2-6th) 管理应用程序状态

窗口过程只是一个函数,每个消息都被调用,所以它本质上是无状态的。因此,您需要一种方法来跟踪您的应用程序的状态,从一个函数调用到下一个函数调用。
最简单的方法就是把所有东西放在全局变量中。这对于小程序来说已经足够了,许多SDK样本都使用这种方法。然而,在一个大型项目中,这导致了全局变量的扩散。另外,你可能有几个窗口,每个窗口都有自己的窗口过程。跟踪哪个窗口应该访问哪些变量变得混乱和容易出错。
CreateWindowEx函数提供了一种将任何数据结构传递给窗口的方法。当这个函数被调用时,它将下面的两个消息发送到你的窗口过程:

这些消息按照列出的顺序发送。(这些不是在CreateWindowEx期间发送的唯一两条消息,但是我们可以忽略其他讨论。)
窗口变得可见之前,WM_NCCREATEWM_CREATE消息被发送。这使得它们成为初始化UI的好地方——例如,确定窗口的初始布局。
CreateWindowEx的最后一个参数是void*类型的指针。你可以在这个参数中传递你想要的任何指针值。当窗口过程处理WM_NCCREATEWM_CREATE消息时,它可以从消息数据中提取此值。
让我们看看你将如何使用这个参数来传递应用程序数据到你的窗口。首先定义一个保存状态信息的类或结构。

// Define a structure to hold some state information.
struct StateInfo{
    // ... (struct members not shown)
};

当你调用CreateWindowEx时,在最终的void*参数中传递一个指向这个结构体的指针。

StateInfo *pState=new (std::nothrow)StateInfo;
if(pState==NULL)
    return 0;
// Initialize the structure members (not shown).
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 // Additional application data
);

当您收到WM_NCCREATEWM_CREATE消息时,每个消息的lParam参数是一个指向CREATESTRUCT结构的指针。CREATESTRUCT结构又包含您传递给CreateWindowEx的指针。
这里写图片描述
显示CREATESTRUCT结构布局的图表

这里是如何提取指向你的数据结构的指针。首先,通过转换lParam参数来获取CREATESTRUCT结构。

CREATESTRUCT *pCreate=reinterpret_cast<CREATESTRUCT*>(lParam);

CREATESTRUCT结构的lpCreateParams成员是您在CreateWindowEx中指定的原始无效指针。通过转换lpCreateParams获取指向自己的数据结构的指针。

pState=reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);

接下来,调用SetWindowLongPtr函数,并将指针传递给您的数据结构。

SetWindowLongPtr(hwnd,GWLP_USERDATA,(LONG_PTR)pState);

这最后一个函数调用的目的是将StateInfo指针存储在窗口的实例数据中。一旦你这样做,你总是可以通过调用GetWindowLongPtr从窗口中获取指针:

LONG_PTR ptr=GetWindowLongPtr(hwnd,GWLP_USERDATA);
StateInfo *pState=reinterpret_cast<StateInfo*>(ptr);

每个窗口都有自己的实例数据,因此您可以创建多个窗口,并为每个窗口提供自己的数据结构实例。如果您定义一个窗口类并创建该类的多个窗口(例如,如果创建了自定义控件类),则此方法特别有用。将GetWindowLongPtr调用包装在一个小的帮助函数中很方便。

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;
}

面向对象的方法
我们可以进一步扩展这个方法。我们已经定义了一个数据结构来保存窗口的状态信息。为数据结构提供对数据进行操作的成员函数(方法)是有意义的。这自然会导致一个设计,其中结构(或类)负责窗口上的所有操作。窗口过程将成为类的一部分。
换句话说,我们想把这个:

// pseudocode
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.
    }
}

变成这样:

// pseudocode
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用于实现窗口过程。例如,以下实现等同于模块1开始处显示的窗口过程。

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这样的完全泛化的框架比这个相对简单的例子要复杂得多。
下一个
第2单元:在Windows程序中使用COM
相关话题
BaseWindow示例


原文链接:Managing Application State

返回目录

猜你喜欢

转载自blog.csdn.net/qq_37422196/article/details/78947513