在独立类中创建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(); }
MainMenu.cpp
#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
相关产品推荐
相关产品推荐

