为什么c++发送WM_DRAWITEM给父进程?

Why does c++ send WM_DRAWITEM to the parent process?

我最近开始学习 C++ 和 WinAPI,我希望能够创建自己的程序

我正在子class一个按钮(放到单独的文件中,因为我喜欢整洁有序的东西 - 这是个坏主意吗?),因为我希望有几个具有相同参数的按钮。我也想画,突然想到的问题是:把它都放在class文件里不是更好吗?表示所有参数,包括自定义绘制。转到主文件更改外观并在 class 文件中设置所有其他参数很烦人。我使用代码块。

编辑(解释):

#include <Windows.h>
#include <Winuser.h>
#include "CustomButton.h"

/*global vars*/
WNDPROC CustomButton::CustomButtonLongPtr;

/*functions*/
LRESULT CALLBACK CustomButtonProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

CustomButton * CustomButton::CreateCustomButton(HINSTANCE hInstance, HWND hwnd, int pos_x, int pos_y, int width, int height)
{
    CustomButton * p_CustomButton = new CustomButton;
    HWND customButton = CreateWindowEx(0, "BUTTON", "OK", WS_VISIBLE | WS_CHILD | BS_OWNERDRAW, pos_x, pos_y, width, height, hwnd, (HMENU)200, (HINSTANCE)GetWindowLong(hwnd, GWLP_HINSTANCE), p_CustomButton);
    if(customButton == NULL)
    {
        delete p_CustomButton;
        MessageBox(NULL, "Problem creating the Search box.", "Error", 0);
        return 0;
    }
    CustomButton::CustomButtonLongPtr = (WNDPROC)SetWindowLongPtr(customButton, GWLP_WNDPROC, (LONG_PTR)&CustomButton::CustomButtonProc);
    return p_CustomButton;
}

我想在 CustomButtonProc 中为这个按钮使用 WM_DRAWITEM,我想知道为什么开发人员认为只允许在父 WinProc 中使用它会很聪明.

这个解释起来有点复杂。

您可能来自将函数插入插座以处理事件的背景,例如

extern void onClicked(void);
button->OnClicked = onClicked;

虽然 Windows 完全有可能从一开始就做到这一点,但您必须记住 Windows 最初是为 运行 在内存严重受限的系统上设计的,所以重要的是控制不要浪费内存。您可以通过一个按钮获得很多事件:

void (*OnClicked)(void);
void (*OnDoubleClicked)(void);
void (*OnDisabled)(void);
void (*OnHighlight)(void);
void (*OnKillFocus)(void);
void (*OnPaint)(void);
void (*OnSetFocus)(void);
void (*OnUnhighlight)(void);
void (*OnUnpushed)(void);
HBRUSH (*OnCtlColorButton)(void);

为程序中的每个按钮(即普通按钮、复选框、单选按钮和组框)都设置这些按钮,其中大部分可能未被使用,这只会浪费大量内存。

因为 Windows 需要一种在系统和 window 之间以及 windows 之间进行通信的方式,Microsoft 决定创建一个 message-passing 接口,其中每条消息都有一个16 位代码和两个 pointer-sized(最初是一个 32 位和一个 16 位)参数和一个 pointer-sized return 值。而且 Windows 本身不需要很多消息,从而为 window 类 和应用程序提供了大量空间来使用消息进行通信。那么为什么不使用消息来表示事件呢?

使用消息避免了内存浪费,同时仍然允许按钮将数据传递到目标 window 和从其目标 window 传递数据。所以逻辑遵循一个按钮需要做的就是

/* this is not the correct syntax but let's use it for expository purposes */
#define BN_CLICKED someNumberHere
case WM_LBUTTONUP:
    SendMessage(GetParent(hwnd), BN_CLICKED, hwnd);
    break;

而 parent 会处理:

case BN_CLICKED:
    if (whichButton == button1)
        doButton1Stuff();
    break;

没有浪费内存,但仍然灵活且可扩展。更重要的是,还有 binary-compatible:如果以后添加更多事件,函数指针的大小 table 将需要更改,并且尝试在旧系统上使用新事件的新程序会破坏随机性记忆。有了消息,这些程序只会有死代码。

现在为什么要把消息发给parent?如果我们将 windows 视为通信端点,那么这很明显:您希望按钮告诉它的 parent 它已被单击,因为您正在传达按钮已被单击!

但更重要的是,您没有编写按钮的 window 程序。微软做到了,他们为每个程序提供了相同的。如果您可以在按钮过程中处理消息,您会把它放在哪里?毕竟你不能改变按钮程序。

(现在我们有一个叫做 "subclassing" 的东西,它允许您覆盖单个 window 的 window 过程来进行自定义处理。它不用于事件处理,因为它更工作不仅仅是发送到 parent.)

所有这些都扩展到自定义绘制;只需将 "custom draw" 替换为 "clicked",它应该仍然有意义。希望这个解释是清楚的,即使有那种心理替代。


如果需要,您可以编写自己的工具以函数指针方式处理事件。将 window 句柄映射到事件函数,并在所有 window 过程中调用全局调度函数来处理事件消息 WM_COMMANDWM_NOTIFY 和(对于轨迹栏) WM_HSCROLLWM_VSCROLL。你如何做到这一点取决于你,但想想你是否真的想这样做;有时它是必要的,但有时它不是。如果这样做,请记住提供一种方法将任意数据传递给在事件连接时决定的事件函数,这样事件处理程序就可以在不依赖全局状态的情况下做一些合理的事情。

感谢 RemyLebeau 和 IInspectable 的评论,我也能够找到解决我的挫败感的方法,我将在这里向其他正在为这个问题摸不着头脑的人解释一下。 此解决方案不需要 VCL,也不需要来自 Visual Studio 等的任何组件。

首先定义您自己的自定义消息,以您可以在 WndProc 中访问它的方式:

#define MY_DRAWITEM (WM_APP+1)
UINT uDrawButtonMsg = RegisterWindowMessage(_T("MY_DRAWITEM"));

然后找出分配给它的号码:

std::cout << uDrawButtonMsg; //for example 49648

然后将此消息从 WndProc 发送到您希望的任何消息的子类控件,例如 WM_DRAWITEM:

case WM_DRAWITEM:
{
    ::SendMessage(p_CustomButton->customButton, uDrawButtonMsg, wParam, lParam);
    break;
}

然后在您的子类中,只需通过您刚才查找的 5 位数字接收消息:

if(49648 == uMsg)
{
    //do your DRAWITEM stuff here
}

感谢为本文做出贡献并提供解释、提示和历史背景的每一个人!