【问题标题】:How does a single wndproc let each window know its serial number?单个 wndproc 如何让每个窗口知道它的序列号?
【发布时间】:2022-01-05 15:08:19
【问题描述】:
int Num = 0;
LRESULT CALLBACK TestWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    RECT rc;
    GetClientRect(hWnd, &rc);
    RECT Winrc;
    GetWindowRect(hWnd, &Winrc);
    SYSTEMTIME time;
    GetLocalTime(&time);
    static const wchar_t* BoxTxt = L"";
    static int MeIs = Num;
    switch (message)
    {

    case WM_CREATE:
    {
        SetWindowLong(hWnd, GWL_EXSTYLE,
            GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
        SetLayeredWindowAttributes(hWnd, RGB(255, 255, 255), 220, LWA_ALPHA);
        //GhWnd = hWnd;
        break;
    }
    case WM_LBUTTONUP:
    {
            wchar_t meChar[20] = L"";
            _itow(MeIs, meChar, 10);
            MessageBox(0, meChar, meChar, 0);
    }
    case WM_SIZE:
    {
        InvalidateRect(hWnd, &rc, 1);
        break;
    }
    case WM_NCLBUTTONDBLCLK:
    {
        break;
    }
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        switch (wmId)
        {

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_CLOSE:
    {
        Num -= 1;
        DestroyWindow(hWnd);
    }
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
int CreateTestWindow()
{
//Call testwndproc. To reduce the length of the problem description, omit these codes
Num+=1;
return 0;
}

在上面的代码中,当我创建多个窗口并点击它时,应该会弹出“1”、“2”、“3”……但实际上都是弹出“1”。

static int MeIs = 0;
case WM_CREATE:
{
MeIs = Num;
}

改成上面的代码,会弹出最后一个窗口的序列号。比如第四个窗口创建时,所有窗口都会弹出“4”

在实际应用中,每个窗口都有自己的设置,并存储在向量中。每个窗口根据自己的序列号找到自己的设置:

struct Data
{
int x;
int y;
int width;
int height;
const wchar_t* text;
}
std::vector<data>UserData(32);//Max:32
//then read them from file,But the window must know which window it is:UserData[i].

例如,第一个窗口会将它们的坐标设置为UserData[1].x和UserData[1].y,关闭时也需要保存文件。 有什么想法吗?谢谢!

【问题讨论】:

  • static int MeIs - 你在程序中只得到其中一个,而不是每个窗口一个。如果您想要每个窗口的数据,您可以在注册窗口类时添加存储空间。
  • 但是如何增加存储空间呢?我希望每个窗口数据是独立的,但是WndProc只有一个。
  • 你应该阅读 Petzold 的书。像这样学习真的不会有成效。
  • hwnd不是已经表示窗口了吗?

标签: c++ c windows winapi


【解决方案1】:

有几种方法可以使用 Win32 API 维护每个窗口的数据。

最简单的方法是使用可通过GetWindowLongPtr(...)SetWindowLongPtr(...) 访问的GWL_USERDATA 插槽。初始化此用户数据值的典型方法是使用通过CreateWindow 调用传递给WM_CREATE 消息的创建参数。

代码如下:

#include <windows.h>
#include <string>

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

struct SomeData {
    int n;
    std::wstring str;
};

int RegisterWindow(HINSTANCE hInstance, const wchar_t* wnd_class) {
    MSG msg = { 0 };
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hbrBackground = (HBRUSH)(WHITE_BRUSH);
    wc.lpszClassName = wnd_class;
    if (!RegisterClass(&wc)) {
        return 1;
    }
    return 0;
}

int CreateWindowWithUserData(HINSTANCE hInstance, const wchar_t* wnd_class, int n, const std::wstring& str) {
    auto* data_ptr = new SomeData{ n, str };

    if (!CreateWindow(wnd_class,  L"Window text",  WS_OVERLAPPEDWINDOW | WS_VISIBLE,  0, 0, 640, 480, 0, 0, hInstance, data_ptr))
        return 2;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    const auto* wnd_class = L"foobar";
    RegisterWindow(hInstance, wnd_class);

    for (int i = 1; i <= 5; i++) {
        CreateWindowWithUserData(hInstance, wnd_class, i, L"blah");
    }
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE: {
            CREATESTRUCT* create_struct = reinterpret_cast<CREATESTRUCT*>(lParam);
            SomeData* user_data = reinterpret_cast<SomeData*>(create_struct->lpCreateParams);
            SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG>(user_data));
        } 
        return 0;

    case WM_CLOSE:
        PostQuitMessage(0);
        break;

    case WM_PAINT: {
        SomeData* user_data = reinterpret_cast<SomeData*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        RECT r = { 20, 20, 300, 35 };
        auto msg = user_data->str + L" " + std::to_wstring(user_data->n);
        DrawText(hdc, msg.c_str(), -1, &r, DT_SINGLELINE);
        EndPaint(hWnd, &ps);
        }
        return 0;

    case WM_DESTROY: {
        SomeData* user_data = reinterpret_cast<SomeData*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
        delete user_data;
        }
        return 0;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;

}

做类似事情的另一种方法是在注册窗口类时使用cbWndExtra 额外字段as discussed in this answer

【讨论】:

  • 谢谢,但还有一个问题。如果窗口关闭,我如何通知程序重新排序所有窗口?比如我关闭第二个窗口(假设一共有五个),第三个窗口会被排序为“2”,以此类推
  • 我不确定你的意思。它们是独立的窗口。用户可以按照用户想要的任何顺序放置它们。但它们仍会显示创建时使用的正确的每个窗口数据。
  • 对不起,我没有说清楚。表示一共创建了序号为1,2,3,4,5的窗口,但是例如如果序号为3的窗口关闭了,则使用后面的窗口序号补充关闭的窗口,像4变成3,5变成4。有没有办法实现呢?感谢您的辛勤工作。
【解决方案2】:

你可以:

  • 确实在Window中存储数据。 SetProp, SetWindowLong+GWL_USERDATA, SetWindowLong+cbWndExtra
  • 将 HWND 映射到您的数据,例如使用 c++ std::map
  • 使用 thunk 来获得关联对象,例如 ATL,请参阅 ATL thunk header 了解可用 API(对于较旧的操作系统,必须手动完成)

【讨论】:

    猜你喜欢
    • 2012-02-14
    • 2010-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-10
    • 2010-12-15
    相关资源
    最近更新 更多