不再需要在子类化之后恢复GWLP_USERDATA

Restoring GWLP_USERDATA after subclassing is no longer necessary

本文关键字:恢复 GWLP USERDATA 之后 子类 不再      更新时间:2023-10-16

我创建了一个Subclass类,并在其中使用SetWindowLongPtr()GWLP_USERDATA设置为包含指向派生类的指针,我使用该派生类来分发消息。

因此,如果我使用SetWindowLongPtr(CurrentWindow, GWLP_USERDATA, (LONG_PTR)Data);,我该如何恢复当我不再想子类化时,旧的数据?MSDN说初始值为零,所以我应该使用SetWindowLongPtr(CurrentWindow, GWLP_USERDATA, (LONG_PTR)NULL);函数吗?

编辑:实际上,我使用SetProp、GetProp、RemoveProp找到了解决这个问题的方法,现在我不需要担心其他函数的问题。Bellow是我为那些可能需要它的人准备的代码:

#ifndef WIN32_SUBCLASS_CLASS_H
#define WIN32_SUBCLASS_CLASS_H
#include <Windows.h>
#include <tchar.h>
class SubclassWindow
{
public:
    static LRESULT CALLBACK stWinSubclassHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        SubclassWindow* pWnd;
        pWnd = (SubclassWindow*)GetProp(hwnd, TEXT("Subclass"));
        if (pWnd)
            return pWnd->WinSubclassHandler(hwnd, uMsg, wParam, lParam);
        else
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    BOOL Subclass(SubclassWindow* Data, HWND hwnd)
    {
        if (Subclassed == TRUE || CurrentWindow != NULL)
            return 0;
        CurrentWindow = hwnd;
        Subclassed = TRUE;
        OriginalProc = (WNDPROC)GetWindowLongPtr(hwnd, GWL_WNDPROC);
        SetProp(CurrentWindow, TEXT("Subclass"), (HANDLE)Data);
        SetWindowLongPtr(CurrentWindow, GWL_WNDPROC, (LONG_PTR)SubclassWindow::stWinSubclassHandler);
        return 1;
    }
    BOOL RemoveSubclass()
    {
        if (OriginalProc == NULL || CurrentWindow == NULL)
            return 0;
        Subclassed = FALSE;
        RemoveProp(CurrentWindow, TEXT("Subclass"));
        SetWindowLongPtr(CurrentWindow, GWL_WNDPROC, (LONG_PTR)OriginalProc);
        return 1;
    }
    BOOL IsSubclassed() { return Subclassed; }
protected:
    HWND CurrentWindow;
    WNDPROC OriginalProc;
    BOOL Subclassed;
    SubclassWindow()
    {
        CurrentWindow = NULL;
        OriginalProc = NULL;
        Subclassed = FALSE;
    }
    virtual LRESULT CALLBACK WinSubclassHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
};
#endif

SetWindowLongPtr()返回旧值,因此保存它,然后在稍后调用SetWindowLongPtr()删除子类时恢复它。

话虽如此,将GWLP_USERDATA替换为不是您自己创建的窗口是危险的。您不知道该窗口是否已将GWLP_USERDATA用于其自身目的。改为使用SetWindowSubclass(),它旨在解决这个问题:

class SubclassWindow
{
public:
    static LRESULT CALLBACK stWinSubclassHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
    {
        SubclassWindow* pWnd = (SubclassWindow*) dwRefData;    
        if ((pWnd) && (uIdSubclass == 1))
            return pWnd->WinSubclassHandler(hwnd, uMsg, wParam, lParam);
        else
            return DefSubclassProc(hWnd, uMsg, wParam, lParam);
    }
    BOOL Subclass(SubclassWindow* Data, HWND hwnd)
    {
        if (CurrentWindow != NULL)
            return FALSE;
        if (!SetWindowSubclass(hwnd, &stWinSubclassHandler, 1, (DWORD_PTR)Data))
            return FALSE;
        CurrentWindow = hwnd;
        return TRUE;
    }
    BOOL RemoveSubclass()
    {
        if (CurrentWindow == NULL)
            return FALSE;
        if (!RemoveWindowSubclass(CurrentWindow, &stWinSubclassHandler, 1))
            return FALSE;
        CurrentWindow = NULL;
        return TRUE;
    }
    BOOL IsSubclassed() { return (CurrentWindow != NULL); }
protected:
    HWND CurrentWindow;
    SubclassWindow()
    {
        CurrentWindow = NULL;
    }
    virtual LRESULT CALLBACK WinSubclassHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
};

如果派生类需要默认处理任何给定的消息,那么您需要做的唯一更改是让它们的WinSubclassHandler()实现调用DefSubclassProc()而不是DefWindowProc()(BTW调用的API错误-您应该调用CallWindowProc(OriginalProc, ...),这样子类替换的上一个窗口过程就有机会处理消息)。为此,我建议将该调用封装在SubclassWindow类内部,以向后代隐藏该细节,例如:

class SubclassWindow
{
...
protected:
    ...
    static LRESULT DefaultHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        return DefSubclassProc(hWnd, uMsg, wParam, lParam);
    }
    ...
};

class MySubclass : public SubclassWindow
{
protected:
    virtual LRESULT CALLBACK WinSubclassHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        ...
        return DefaultHandler(hWnd, uMsg, wParam, lParam);
    }
};

或者(这将允许后代更好地从后代派生):

class SubclassWindow
{
...
protected:
    ...
    virtual LRESULT CALLBACK WinSubclassHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        return DefSubclassProc(hWnd, uMsg, wParam, lParam);
    }
    ...
};

class MySubclass1 : public SubclassWindow
{
protected:
    virtual LRESULT CALLBACK WinSubclassHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        ...
        return SubclassWindow::WinSubclassHandler(hWnd, uMsg, wParam, lParam);
    }
};
class MySubclass2 : public MySubclass1
{
protected:
    virtual LRESULT CALLBACK WinSubclassHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        ...
        return MySubclass1::WinSubclassHandler(hWnd, uMsg, wParam, lParam);
    }
};
class MySubclass3 : public MySubclass2
{
protected:
    virtual LRESULT CALLBACK WinSubclassHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        ...
        return MySubclass2::WinSubclassHandler(hWnd, uMsg, wParam, lParam);
    }
};

正如Hans所提到的,如果有东西需要恢复,你的状态就不好了。这意味着原始的WNDPROC,它仍然被用于任何你不会处理的东西,将在那里期待另一个值。

也就是说,SetWindowLongPtr返回旧值,所以您总是可以先调用GetWindowLongPtr来获得原始值。这个值可能仍然是零,但至少你会确信。