You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在独立类中创建Win32控件:控件ID定义的更优方案问询

避免重复定义控件ID的最优实现方式

问题描述

我拥有一个Window类负责创建主窗口,MainMenu类负责为主窗口创建控件(类似C#中窗体与用户控件的模式)。目前我需要在Window.cpp和MainMenu.cpp中都定义#define EXIT_BUTTON 1才能让按钮事件正常工作,想知道是否存在更优的实现方式,不用重复定义这类常量?

示例代码片段:

exit = CreateWindow(L"Button", L"Exit", style, monitor.right / 2 - 100, 150, 200, 100, m_hWnd, HMENU(EXIT_BUTTON), NULL, NULL);

Window.cpp

#include "Window.h"

#define EXIT_BUTTON 1

MainMenu* mainMenu1;

Window::Window() : m_hInst(GetModuleHandle(nullptr)) //creates the window
{
    WNDCLASS wc = {};

    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hInstance = m_hInst;
    wc.lpszClassName = ClassName;
    wc.lpfnWndProc = WindProc;

    RegisterClass(&wc);
    
    DWORD style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;

    GetWindowRect(GetDesktopWindow(), &monitor);

    m_hWnd = CreateWindow(ClassName, WindowTitle, style, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
    mainMenu1 = new MainMenu(m_hWnd, monitor);

    ShowWindow(m_hWnd, SW_MAXIMIZE);
}

Window::~Window()
{
    UnregisterClass(ClassName, m_hInst);
    delete mainMenu1;
}

LRESULT CALLBACK WindProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) // gets input from user
{
    switch (msg) {
    case WM_CREATE:
        AddControls();
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_COMMAND:
        switch (wp) {
        case EXIT_BUTTON:
            PostQuitMessage(0);
            break;
        }
    case WM_WINDOWPOSCHANGED:
        std::cout << "1";
        break;
    default:
        return DefWindowProcW(hwnd, msg, wp, lp);
    }
    return 1;
}

bool Window::ProcessMessage()
{
    MSG msg = {};
    while (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) {
        if (msg.message == WM_QUIT)
            return false;

        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
    
    return true;
}

void AddControls()
{
    mainMenu1->Initialize();
}
#include "MainMenu.h"

#define EXIT_BUTTON 1

MainMenu::MainMenu(HWND hWnd, RECT monitor)
{
    this->monitor = monitor;
    m_hWnd = hWnd;
}

MainMenu::~MainMenu()
{
    
}

void MainMenu::Initialize()
{
    DWORD style = WS_VISIBLE | WS_CHILD | SS_CENTER;

    title = CreateWindow(L"static", L"Welcome", style, monitor.right / 2 - 100, 100, 200, 100, m_hWnd, NULL, NULL, NULL);
    SendMessage(title, WM_SETFONT, WPARAM(CreateFont(50, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET,
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Arial")), true);
    exit = CreateWindow(L"Button", L"Exit", style, monitor.right / 2 - 100, 150, 200, 100, m_hWnd, HMENU(EXIT_BUTTON), NULL, NULL);
}

void MainMenu::Hide()
{
}

void MainMenu::Show()
{
}

解决方案

不需要在两个cpp文件中重复定义EXIT_BUTTON,以下是几种更优的实现方式:

1. 公共头文件统一定义ID

创建一个全局共享的头文件(比如ControlIDs.h),把所有控件ID集中在这里定义:

#pragma once
// 统一管理所有控件ID
#define EXIT_BUTTON 1
// 后续新增控件ID直接在这里添加,比如:
// #define START_BUTTON 2
// #define SETTINGS_BUTTON 3

然后在Window.cpp和MainMenu.cpp中替换掉各自的#define,改为包含这个头文件:

#include "ControlIDs.h"

这样所有用到控件ID的地方都引用同一个定义,避免重复编写,也能防止因不同文件定义值不一致导致的错误。

2. 使用枚举类型(类型更安全)

相比#define,枚举类型具备类型检查能力,能避免一些不小心的赋值错误。同样在公共头文件中定义:

#pragma once
enum class ControlID {
    ExitButton = 1,
    // 其他控件ID依次添加
    StartButton = 2,
    SettingsButton = 3
};

使用时需要做简单的类型转换:

  • 创建按钮时:
    exit = CreateWindow(L"Button", L"Exit", style, monitor.right / 2 - 100, 150, 200, 100, m_hWnd, 
                        HMENU(static_cast<int>(ControlID::ExitButton)), NULL, NULL);
    
  • 在窗口过程处理WM_COMMAND时:
    case WM_COMMAND:
        switch (static_cast<ControlID>(wp)) {
        case ControlID::ExitButton:
            PostQuitMessage(0);
            break;
        }
    

3. 回调函数解耦ID依赖(模块化最优)

如果想彻底消除窗口和控件之间的ID依赖,可以让MainMenu类通过回调函数将按钮点击事件通知给Window类,完全不需要硬编码ID:

修改MainMenu.h

#pragma once
#include <windows.h>

class MainMenu {
public:
    // 定义回调类型
    using ExitClickedCallback = void(*)();

    // 构造函数接收回调函数
    MainMenu(HWND hWnd, RECT monitor, ExitClickedCallback exitCallback);
    void Initialize();
    void Hide();
    void Show();

private:
    HWND m_hWnd;
    RECT monitor;
    HWND exit;
    ExitClickedCallback m_exitCallback;

    // 静态窗口过程用于子类化按钮
    static LRESULT CALLBACK ControlProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
};

修改MainMenu.cpp

#include "MainMenu.h"

MainMenu::MainMenu(HWND hWnd, RECT monitor, ExitClickedCallback exitCallback)
    : m_hWnd(hWnd), monitor(monitor), m_exitCallback(exitCallback) {}

void MainMenu::Initialize() {
    DWORD style = WS_VISIBLE | WS_CHILD | SS_CENTER;

    title = CreateWindow(L"static", L"Welcome", style, monitor.right / 2 - 100, 100, 200, 100, m_hWnd, NULL, NULL, NULL);
    SendMessage(title, WM_SETFONT, WPARAM(CreateFont(50, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET,
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Arial")), true);
    
    // 创建按钮时不需要指定硬编码ID
    exit = CreateWindow(L"Button", L"Exit", style, monitor.right / 2 - 100, 150, 200, 100, m_hWnd, NULL, NULL, NULL);
    // 子类化按钮,接管消息处理
    SetWindowLongPtr(exit, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(ControlProc));
    // 将当前MainMenu对象指针关联到按钮窗口
    SetWindowLongPtr(exit, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
}

LRESULT CALLBACK MainMenu::ControlProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
    MainMenu* menu = reinterpret_cast<MainMenu*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
    if (menu && msg == WM_COMMAND && HIWORD(wp) == BN_CLICKED) {
        // 判断点击的是退出按钮,触发回调
        if (hwnd == menu->exit) {
            menu->m_exitCallback();
        }
    }
    // 调用原窗口过程
    return CallWindowProc(reinterpret_cast<WNDPROC>(GetWindowLongPtr(hwnd, GWLP_WNDPROC)), hwnd, msg, wp, lp);
}

// 其他方法实现不变
void MainMenu::Hide() {}
void MainMenu::Show() {}

修改Window.cpp中的MainMenu初始化

// 创建MainMenu时传入退出回调
mainMenu1 = new MainMenu(m_hWnd, monitor, []() { 
    PostQuitMessage(0); 
});

这种方式下,Window类完全不需要知道MainMenu内部的控件ID,MainMenu自己管理控件的事件逻辑,通过回调通知上层窗口,更符合模块化和低耦合的设计原则。

内容的提问来源于stack exchange,提问作者RazzelDazzel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 16:50:43