自定义 GUI 系统 - 第二部分:对话框和控件






2.77/5 (10投票s)
2005年10月17日
4分钟阅读

69004

585
本文解释了自定义 GUI 系统中对话框和各种控件的支持是如何实现的。
前言
在我之前的教程中,我尝试解释了一个自定义 GUI 系统的基本框架布局,并假设您已经熟悉了我介绍的解决方案。如果您不知道我在说什么,或者觉得需要回顾一下,请点击链接:自定义 GUI 系统 - 第一部分:基本框架。(注意:与本教程的第一部分一样,所有类都在源文件中都有或多或少详细的文档。它们是从一个更大的项目中提取出来的,并且为了本教程的目的,它们已被简化为展示其基本原理所需的实质性元素。)
对话框
让我们从今天主题的比较容易的部分开始:windialog
类。它继承自 winbase
类,并且是该类在对话框语言中的直接翻译。主要区别在于标准的创建方法 create()
被重写,使其不做任何事情。它只是返回 false。原因是 windialog
类有自己的创建方法 create_dialog()
和 create_modal()
。第一个创建无模式对话框,第二个创建有模式对话框。这两种方法都接受三个参数:应用程序实例句柄、父窗口句柄以及对话框的资源 ID。所有初始化都在这些方法中进行,因此只需调用它们即可创建一个对话框对象,并使用资源编辑器中设计的模板打开对话框窗口(在我看来,视觉资源编辑器,即使是 VC++ 中的那个,也是非常有用的并且生成代码相对干净的工具)。接下来是代码中显示的 dialog_proc()
和 window_proc()
之间的区别(两种过程相同的代码已被省略 - 请参考下面“控件”部分更新的 window_proc()
)
//different procedure type BOOL CALLBACK dialog_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { winbase* wb = 0; //we process WM_INITDIALOG instead of WM_CREATE if(msg == WM_INITDIALOG) { //exactly the same code as in window_proc() } //get the window this message belongs to wb = winpool::get_window_from_hwnd(hwnd); //call message specific handlers switch(msg) { //exactly the same code as in window_proc() // . // . // . //up to call to generic handler default: if(wb) //we have the pointer to the window return wb->on_message(msg, wp, lp); } //and for unprocessed messages return false; }
控件
本节可以从简要查看更新的 window_proc()
开始。您可能还记得,这是在本教程第一部分为我们的框架窗口注册的过程。它也被自定义控件使用。正如您将看到的,过程中有新的处理程序调用。这些处理程序(在 winbase
类中定义)允许我们分别处理相应的消息(而不是通过 on_message()
处理程序)。
LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { winbase* wb = 0; if(msg == WM_CREATE) { wb = winpool::get_created_window(); wb->post_create_window(hwnd); return wb->on_create(wp, lp); } wb = winpool::get_window_from_hwnd(hwnd); switch(msg) { ////////////////////////////////////////////// //NEW - added new handlers to support custom controls case WM_COMMAND: return wb->on_command(wp, lp); case WM_PAINT: return wb->on_paint(wp, lp); case WM_NCPAINT: return wb->on_ncpaint(wp, lp); case WM_MOUSEMOVE: return wb->on_mousemove(wp, lp); case WM_LBUTTONDOWN: return wb->on_lbuttondown(wp, lp); case WM_KILLFOCUS: return wb->on_killfocus(wp, lp); case WM_SETFOCUS: return wb->on_setfocus(wp, lp); ////////////////////////////////////////////// case WM_LBUTTONUP: return wb->on_lbuttonup(wp, lp); case WM_RBUTTONUP: return wb->on_rbuttonup(wp, lp); case WM_CLOSE: return wb->on_close(wp, lp); case WM_DESTROY: return wb->on_destroy(wp, lp); case WM_NCDESTROY: return wb->on_ncdestroy(wp, lp); default: if(wb) return wb->on_message(msg, wp, lp); else return DefWindowProc(hwnd, msg, wp, lp); } }
下一步,让我们声明 winctrl
类,它是自定义控件的基类。该类继承自 winbase
类,并实现了对自定义控件状态的访问,支持控件外观的操作,支持消息路由,以及我认为最重要的部分,即 attach()
方法,该方法允许将类对象附加到资源编辑器中创建的任何对话框控件。最后,可以介绍一个用于子类化控件的基类,即 wintruectrl
类。该类继承自 winctrl
类,因此支持自定义控件的所有方面。然而,子类化控件有一个重要的区别:它们没有 window_proc()
,而是有自己的过程 subcls_proc()
,该过程不调用 on_create()
,因为这些控件在处理完 WM_CREATE
后被子类化。子类过程 subcls_proc()
会为所有未处理的消息调用 on_message()
处理程序。此处理程序的默认实现会调用子类控件的原始窗口过程。另请注意,wintruectrl
类中所有处理程序的默认实现都必须调用子类控件的原始窗口过程。为了实现这一点,winbase
类包含用于子类化/取消子类化窗口的新方法,以及一个用于调用子类窗口原始窗口过程的方法。
使用系统
本教程中,我们将创建一个
- 一个包含单个选择列表框(将被子类化)和两个按钮(将是自定义的)的测试对话框模板。
- 一个继承自
winctrl
类的MyButton
类,它将创建一个可点击的平面按钮。 - 一个继承自
wintruectrl
类的MyListbox
类,它将子类化通用列表框类,并允许我们在列表末尾追加字符串。 - 一个继承自
windialog
的MyDialog
类,它将封装我们的测试对话框模板。
我们还将使用第一部分教程中的通用窗口的子类,称为 MyWindow
。该窗口将自己居中显示在屏幕上,还将加载并显示一个十字准星光标。我们将重新实现 on_rbuttonup()
处理程序,以便它打开我们的测试对话框。最后,我们添加两行代码来注册/取消注册我们的自定义 MyButton
窗口类。
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int showcmd) { //initialize application manager winpool::open_session(hinst); //register our window class winpool::register_class(hinst, "MyWindow"); /////////////////////////////////////////////////// //NEW - registering MyButton class //could be put into winpool::open_session() winpool::register_class(hinst, "MyButton"); /////////////////////////////////////////////////// //window instance MyWindow wnd; //position and dimensions array (dims={x, y, w, h}) //we don`t need to specify position since the window centers itself int dims[] = {0, 0, 640, 480}; //initialize window data members needed for creating the window //(e.g hinstance, classname and display state to WDS_INITIAL) wnd.pre_create_window(hinst, "MyWindow"); //create the window - we specify only the owner //(NULL=desktop), caption and dimensions wnd.create(NULL, "Tutorial 1 - Use mouse buttons and keyboard keys", dims); //finally show the window on the screen wnd.show_window(true); //run message loop until WM_QUIT is encountered while(winpool::dispatch_message()){ /* run */} /////////////////////////////////////////////////// //NEW - unregistering MyButton class, //could be put into winpool::close_session() winpool::unregister_class(hinst, "MyButton"); /////////////////////////////////////////////////// //clean up winpool::unregister_class(hinst, "MyWindow"); winpool::close_session(); return 1; }
最后的话
恭喜您,您已经完成了!我们已经将我们的小 GUI 系统扩展到类似这样的程度。
winbase -> MyWindow
|- windialog -> MyDialog
'- winctrl -> MyButton
'- wintruectrl -> MyListbox
现在您应该能够开发自己的窗口类型、控件等。作为灵感,您可以查看 Win32 Pro 包(您可以在 这里 找到该包 - 随意下载并进行实验)。如有疑问,请随时给我发邮件。