GUI库:使本机应用措施具备Windows窗体的浅易性
副标题#e#
本文将先容以下内容:
有关 GUI 编程的问题
建设窗口工具
处理惩罚事件和通知
窗体和控件
本文利用以下技能:
Win32 API、C++
目次
兼有本机和可移植性
无 windows.h
处理惩罚每个窗口
直观的代码
控件与窗体
窗体编程
处理惩罚窗体
离开旧 ID
事件和通知
菜单、快捷方法及雷同项
选项卡控件和窗体
调解巨细
与 Visual Studio 2005 集成
实现行为
利用 C++ 举办 GUI 编程的问题是大大都库的级别太低,给编程人员带来了太多承担。这些库依赖雷同 C 语言的布局,可能它们的包装类不能埋没足够的巨大性。并且,它们不能使事件编程足够简朴,反而迫使您必需相识有关基本 WM_ 动静的常识。
在本文中,我将为您先容 eGUI++,这是我编写的一个 C++ 库,可为您(客户端编程人员)提供一种处理惩罚 GUI 应用措施的高级界面。它可以埋没巨大性,通过完全埋没 WM_ 动静的常识使事件编程变得相当简朴。您不需要处理惩罚任何雷同 C 语言的原始布局;始终只需要处理惩罚类。总之,eGUI++ 客户端代码易于阅读,也易于编写。
eGUI++ 只在 Windows® 中运行。我实在不信任跨平台的 GUI 应用措施,除非是在不太重要(只是较量简朴的测试框架、原型)可能仅供解说的场所利用这样的应用措施。更重要的是,我真的认为应该善用基本操纵系统提供的所有成果。而 Windows XP 和 Windows Vista® 简直提供了不少成果。
兼有本机和可移植性
那些期望利用 CLR 代码的人,你们已把握了 C++。那是一个很好的平台,所以无需对其举办改造。其余盼愿利用精采的库来为 Windows 2000 及更新的操纵系统生本钱机 Windows 代码的人,请继承阅读。您会对功效满足的;该库操作您的方针操纵系统,利用起来很直观。而且您基础不需要利用 Microsoft® .NET Framework。您编写的代码利用起来就像 C++ 代码。另外,您将编写的代码不是特定于 Visual C++ 编译器的。假如您愿意,可利用 g++(GNU C++ 编译器)4.1 编译本身写的代码。根基上,假如您封装了 Win32® API,就没什么能阻止您编写可移植代码了。
也就是说,对付重要的 GUI,您需要精采的 IDE,如 Visual Studio® 2005 或 Visual Studio 2008 速成版。我已调解了我的库,以与 Visual Studio 2005 Express 及更新的版本集成,为您提供更好的 GUI 体验。我真正重视的是代码补全成果,以便确保当您建设新的 GUI 类或扩揭示有 GUI 类时 IDE 会极力提供辅佐。
我但愿享受编写 GUI 应用措施的进程。因此,我建设 eGUI++ 的目标是使 GUI 代码易于阅读和编写。譬喻,我已在所有大概的处所实现了代码补全成果。这样,GUI 编程便安详了(假如存在错误,只要有大概,我就会在编译时将其捕捉;不然,将激发运行时异常)。eGUI++ 适合伙源编辑器(它与 Visual Studio 2005 和资源编辑器的更新版本举办了集成)。
#p#副标题#e#
无 windows.h
包罗 windows.h 的主要问题是它太容易发生错误。什么能让您遏制监督永远不会产生的事件?假设您是 button 类,而在期待键盘事件;您将永远不行能比及。
那么,您为什么还需要 windows.h 呢?您应该可以或许利用通例 C++ 类用 C++ 编写 Windows 应用措施。因此,您无需知道 windows.h 的内部信息:无需知道 WM_LBUTTONDBLCLK、WM_LBUTTONUP 以及其他较长的事件名称。无需知道 LPNMITEMACTIVATE、NMHDR 或其他任何可疑的 C 布局。也无需知道更多的 C 样式转换。精采的 C++ GUI 库的主要成果应该是抽象出 Win32 API 并答允您处理惩罚类。
您是否对原始 C 布局(如图 1 所示)做了充实的处理惩罚?我知道我做了。因此,我此刻可以按如下方法编写代码:
wnd<rebar> w = new_(parent);
rebar::item i(rebar::item::color | rebar::item::text);
w->add(i);
图 1 补全旧窗口代码
// The old way
hwndRB = CreateWindowEx(WS_EX_TOOLWINDOW,
REBARCLASSNAME, NULL,
WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|
WS_CLIPCHILDREN|RBS_VARHEIGHT|
CCS_NODIVIDER,
0,0,0,0, hwndOwner, NULL, g_hinst, NULL);
...
rbi.cbSize = sizeof(REBARINFO);
rbBand.cbSize = sizeof(REBARBANDINFO);
rbBand.fMask = RBBIM_COLORS | RBBIM_TEXT |
RBBIM_BACKGROUND;
rbBand.fStyle = RBBS_CHILDEDGE;
您可以看到,eGUI++ 埋没了巨大性:您只需处理惩罚 C++ 类,而无需记着巨大的 API 函数(如 CreateWindowEx)、常数名称(WS_* 常数)或雷同 REBARPARAMINFO 的巨大 C 布局。
eGUI++ 面向 Windows 2000 及更新版本。默认环境下,它的方针操纵系统是 Windows XP SP2。可是,您可以选择让它面向其他操纵系统,以便淘汰或增加可用成果。假如但愿使其面向其他操纵系统,只需在包括任何 eGUI++ 标头之前将 #define EGUI_OS 指定为其他操纵系统常数(拜见图 2)。
图 2 指定操纵系统
#p#分页标题#e#
// code from eGUI++
struct os {
typedef enum type {
win_2k,
win_2k_sp4,
win_xp,
win_xp_sp2,
win_vista
};
};
#ifndef EGUI_OS
#define EGUI_OS os::win_xp_sp2
#endif
当知道此代码中的 #ifdefs 很少时,您会很是兴奋,因为这样阅读起来就容易多了。而您也会留意到某些属性是特定于操纵系统的:
property<int,os::win_xp> some_prop;
譬喻,当您面向早期版本的操纵系统并实验利用上面的属性时,会产生编译时错误。
处理惩罚每个窗口
凡是是您(编程人员)抉择工具存在多长时间。可是,GUI 编程中一个很大的问题是可视窗口和窗口工具之间存在区别 — 抉择何时封锁窗口的是用户。因此,在您的代码中,您可以配置指向窗口已被用户销毁的窗口工具的有效指针或对它的引用。由于这使维护一对一对应干系(也就是说屏幕上的一个窗口代表一个工具实例,反之亦然)变得坚苦,所以由此方案发生的一个明晰限制就是您无法拥有当地窗话柄例(看成用域存在时将被销毁):
{
form f(...);
f.show();
...
}
假定您的窗体 f 显示在屏幕上。当您退出 f 的浸染域时,会产生什么?您有两种选择:在销毁与该窗体对应的 C++ 实例时,销毁该(屏幕上)窗体或将其留在屏幕上。两个选择都是不公道的。在第一种环境下,用户大概会感想疑惑,不知道该窗体那边去了。在第二种环境下,固然您将窗体留在屏幕上,但因为其对应的 C++ 实例不在了,所以它不会响应任何事件。另外,用户大概已在当 f 位于浸染域中时封锁了此屏幕上窗体。因此您应中止对不存在于屏幕上的工具的处理惩罚。
办理方案:始终通过(间接)指针会见窗口。然后,当此用户封锁屏幕上窗口时,相应的 C++ 实例会标志为无效;假如您实验会见它,将激发异常。您老是可以相识某窗口是否有效,并可以自行销毁该窗口,如下所示:
wnd<> w = ...;
// is window valid?
if ( is_valid(w) ) w->do_something();
// is window valid?
if ( w) w->do_something();
// destroy the window
delete_(w);
由于屏幕上的每个窗口都有一个相应的 C++ 实例,您将利用 wnd<> 模板类处理惩罚窗口,该类代表指向窗口的共享(引用计数)指针。wnd<> 类具有一个可选参数:窗口范例。这根基上在您预想之中。默认环境下,此参数为 window_base。它也可以是文本、标签、rebar、编辑等窗口类。图 3 显示了几个利用多种窗口工具并在它们之间举办转换的示例。
图 3 建设窗口工具并转换
// when constructing a window, you can specify its type
wnd<> w = new_<form>(parent);
w->bg_color( rgb(0,0,0));
// when constructing a window, if you don't specify its type,
// it will guess it, based on who you assign it to
wnd<button> b = new_(w, rect(10,10,200,20) );
b->events.click += &my_func;
// destroying a window
delete_(b);
// casting - if it fails, it throws
wnd<form> f = wnd_cast(w);
// casting - if it fails, returns null
if ( wnd<edit> e = try_wnd_cast(e) )
e->text = "not nullio";
请留意,利用 new_ 函数建设窗口,利用 delete_ 函数删除窗口(同样,凡是是由用户封锁窗口)。别的,假如您拥有一个范例为 X(默认为 window_base)的窗口,并但愿知道它是否也属于范例 Y,就可以利用转换。转换始终是显式的。转换可以分为两类:wnd_cast,假如失败,将激发异常;try_wnd_cast,假如失败,将返回空窗口。
开拓 eGUI++ 类时,除从其基类派生的行为之外,您有时但愿担任一些其他行为,譬喻调解巨细、改换外观等。在这种环境下,您可以建设多个可再用的行为类,然后从这些行为类派生其他行为类。
直观的代码
看到 GUI 代码易于编写和阅读是很让人欢快的。我在此书顶用尽各类步伐来确保代码补全成果提供尽大概多的辅佐。处理惩罚大型 GUI 库时,总会有一些容易遗忘的内容:属性名、事件名称、符号等。我已对其一一举办处理惩罚。我曾经利用 doxygen 处理惩罚文档;结果很好。我越常利用,就越喜欢它。
#p#分页标题#e#
欣赏文档变得很是简朴。要查察属性名,只需键入 w->,便可以看到要领和属性,如图 4 所示(属性显示为成员变量,以便于区分)。对付事件名称,记着类可以处理惩罚的事件是很容易的;只需键入 class_name::ev:: 和范畴运算符,之儿女码补全成果便会启动并向您显示事件。但处理惩罚符号才是 eGUI++ 真正的亮点地址。对付每个可由符号构成的属性,要找出可用的符号选项,只需向该符号属性添加“.”,这样代码补全成果就再次派上用场了。作为增补,我还为属性添加了运算符重载。因此,以下代码是有效的:
w->text = "hello";
w->text += " world";
w->style |= w->style.tiled;
图 4 代码补全成果
为了防备健忘您可以自由支配的控件列表,我已专门为该列表添加了名为 egui::ctrl 的定名空间。
控件与窗体
假如您以前举办过 Win32 GUI 编程,应该对对话框很熟悉。您也应该很相识 API 处理惩罚对话框建设 (::CreateDialog) 的方法与处理惩罚窗口建设的方法 (::CreateWindow[Ex]) 有很大差异。作为编程人员,您无需记着两个签名差别很大的巨大函数。它们均属于窗口范例。eGUI++ 只有一种建设窗口的方法:new_ 函数。
对付差异范例的窗口,窗体这个名称要比对话框这个名称更有表示力。它描写显示其他包括数据的控件的窗口。这两个名称我都接管,可是我更喜欢用窗体这个名称。实际上,从代码中您会看到:
typedef form dialog;
从观念上讲,只有两种窗口范例:控件和窗体。控件是显示一些数据的窗口,它大概答允用户举办修改。每个控件类都是从“控件”类派生的。窗体是承载一个或多个控件以及它们的一些自身逻辑(譬喻,答允对某些数据举办操纵的逻辑)的窗口。
每个窗口范例的实际成果按照该窗口的用途而变革。譬喻,您需要留意,窗体答允您列举其子控件,而控件不答允;这样,您的代码就不易堕落了。同时,您也很少需要建设控件;凡是它们已经存在于窗体上了 — 因为您已利用资源编辑器将它们安排在哪里。
窗体自己分别为两种范例:模式对话框和动静框。要建设模式对话框,只需在建设窗体时添加 form::style::modal。要建设动静框,请利用 msg_box<> 函数将按钮指定为模板参数:
if ( msg_box<mb::ok | mb::cancel>("q") == mb::ok)
std::cout << "ok pressed";
另外,msg_box<> 知道按钮组合在编译时是否有效:
// ok
msg_box<mb::yes | mb::no>("q");
// compile-time error
msg_box<mb::ok | mb::yes>("q");
窗体编程
重申一下,窗体在 Win32 API 中称为“对话框”。对付 Windows 窗体而言,已证明窗体编程是一项乐成的计策。每个窗体上均有一些控件,而每个窗体只办理一项任务。您可以利用能承载控件或其他窗体的选项卡,而无需利用又旧又巨大的单文档界面 (SDI) 或多文档界面 (MDI)。因此,您不会看到任何 CFrameWnd、CMDIChildWnd 或雷同内容;它们没有存在的须要。假如您但愿在一个窗体上承载多个窗体,只需利用 tab_form 类。利用该类,您可添加子窗体,每个子窗体都位于自已的选项卡上。
处理惩罚窗体
尽量我讨厌领导,但我知道有时一些领导确实可以使编程任务变得简朴。因此,我在建设窗体之前建设了“新建类”领导。在类视图中,选择“添加类”,然后在“种别”中,选择“eGUI”。在左侧,选择“eGUI 窗体”,单击“添加”。指定类名称,便完成了建设(图 5)。该领导将建设一个名为“<dlgname>.h”的头文件,一个名为“<dlgname>.cpp”的源文件以及一个名为“<dlgname>_form_resource.h”的附加头文件,eGUI++ 会在内部维护这些文件。
图 5 添加类
最后一个头文件包括您在窗体中利用的所有控件名称。因此,您无需建设特另外控件变量和利用数据互换(像在 MFC 中一样),而直接利用控件。假设您拥有一个登录对话框,该对话框具有两个编辑框(“用户名称”和“暗码”)和两个按钮(“确定”和“打消”),如图 6 所示。
图 6 编辑框和按钮
将为您生成下列文件:
#p#分页标题#e#
// login.h
#pragma once
#include "login_form_resource.h"
struct login : form,
private form_resource::login {};
// login.cpp
#include "stdafx.h"
#include "login.h"
请留意,此代码相当简朴;没有雷同 "enum {IDD = …}" 的领导样式代码或动静映射。假如您不需要自界说结构函数则不需要提供,利用默认的结构函数即可。
登录类只从 form_resource::login 派生,而 form_resource::login 是在 login_form_resource.h(此文件由 eGUI++ 库维护)中实现的;form_resource::login 类包括有关窗体的控件的信息(它们的名称和范例,以及从控件捕获通知的本领);您可以选择变动派生的可会见性范例,不外我阻挡这样做。像您的类成员数据凡是是私有的一样,窗体同样如此 — 其控件应是私有的。
这样,生成的 form_resource::login 看起来雷同于以下代码:
// login_form_resource.h
#pragma once
struct form_resource::login {
// ... (code to allow
// handling of notifications)
wnd<edit> username;
wnd<edit> passw;
wnd<button> ok, cancel;
};
这使您可以轻松地处理惩罚窗体的控件。假设您但愿确保暗码是“secretword”:
void login::on_button_click(ev::button_click &, ok_) {
if ( passw->text == "secretword")
{ pass_ok = true; visible = false; }
}
您可以看到,像利用 Visual Basic® 一样,要埋没窗体,只需将其 visible 属性配置为 false。
离开旧 ID
您以前大概处理惩罚过资源编辑器,而且碰着过很多资源前缀范例,譬喻 ID_、IDD_、IDC_、IDR_、IDS_ 等。前缀合用于资源编辑器。可是,在代码中,它们只是特别信息,您不需要记着或思量它们。在 eGUI++ 应用措施中,因为这些前缀会被彻底忽略,所以您基础不需要记。
譬喻,以前的名称(username、passw、ok、cancel)是资源编辑器的快捷方法。eGUI++ 库自动去除了它们的 ID* 前缀。而原始名称本应为 IDC_username、IDC_passw、IDOK 和 IDCANCEL。
事件和通知
我曾提到,您无需记着单个 WM_ 动静。也就是说,事件是很难驯服的。事件实在太多了,所以您需要一种轻便要领来找出您可以响应的事件并轻松地响应它们。您需要找到轻便的要领,以便在窗体控件上产闹事件时您可以或许获得通知,从而可以扩展控件和添加本身的事件。
每个窗口类(控件或窗体)都可以生成事件。对付每个窗口类,都有一个可捕获所有事件的事件处理惩罚措施。对付每个事件,城市界说一个函数来处理惩罚该事件;该函数是虚拟的,其实现不会起任何浸染。每个事件处理惩罚措施函数都具有一个参数:事件数据。
对付现有控件,对应的事件类称为 handle_events::control_name。每个现有 eGUI++ 窗口类 wnd_name 都已从 handle_events::wnd_name 派生。假如扩揭示有的窗口类,则您始终可以处理惩罚其事件。(简而言之,所有事件处理惩罚措施函数均以“on_”开始。)譬喻:
struct my_btn : button {
void on_char(ev::char& e) {
cout << "typed " << e.ch;
}
};
假如您曾处理惩罚过其他 GUI 库,就会知道目睹不必然为凭,工作没有您想的那么简朴。现有的控件不发送事件,而是发送通知。通知以 WM_COMMAND/WM_NOTIFY 动静的形式发送,而且发送到该控件的父级,而不发送到该控件自身。最初,这好像很合乎情理:确实是控件的父级(窗体)需要通知。可是,这使扩展控件类变得相当坚苦。假如您但愿构建立来使当前文件系统可视化,该如何做呢?您需要捕捉将要发送到控件的父级的事件,如项目扩展 (TVN_ITEMEXPANDING)。接着,您需要一种要领将通知向下通报到控件自身。
对付 eGUI++ 来说,通知即是事件。因此,这些通知老是发送到控件,然后发送到控件的父级。当通过担任扩展控件类时,每个通知都将转换成其他事件。譬喻,假如您但愿在用户编辑第一个列时建设显示复合框的列表控件而不是编辑控件,代码应该如下所示:
struct list_with_combo : list {
...
void on_begin_label_edit(
ev::begin_label_edit & e) {
e.allow_default = false;
combo->rect(...);
combo->visible = true;
}
wnd<combo_box> combo;
};
要处理惩罚事件,需重载事件处理惩罚措施函数,如下所示:
struct my_btn : button {
void on_char(ev::char& e);
};
此处您是响应字符按下事件;假如您喜欢利用 Win32 API,则是响应 WM_CHAR 动静。
#p#分页标题#e#
请记着,对付 on_my_event 事件处理惩罚措施函数,事件参数始终为 ev::my_event 范例。您的类可以处理惩罚的所有事件都是 ev:: 布局。只需键入 ev::,代码补全成果便会向您显示您的类可以处理惩罚的所有事件(拜见图 7)。请留意,查谋事件信息的最轻便要领是键入 e.,从而使代码补全成果显示所有与此事件有关的数据(拜见图 8)。
图 7 代码补全显示事件
图 8 得到事件信息
您可以通过欣赏文档来查察控件的事件:只需在选择该控件后选择其 ev 类,就会看到它的所有事件。此库可以将同一事件发送到多个事件处理惩罚措施(譬喻,将通知发送到控件,然后发送到此控件的父级)。
所有事件都具有 .sender 属性;它代表发送事件的控件(此属性对付通知很有用,尤其是在查找通知的发送人方面)。所有事件都具有 .handled 属性;此属性大概具有两个值:handled_partially(默认)和 handled_fully。通过将此属性配置为 handled_fully,您可以遏制事件处理惩罚;纵然有更多事件处理惩罚措施,也不会挪用它们。譬喻,假如您正在扩展编辑类,并但愿阻止通知父级文本变动,您应编写以下代码:
struct independent_edit : edit {
void on_change(ev::change &e) {
e.handled = handled_fully;
}
};
我在上面先容过,扩展控件很简朴。不外,在窗体上处理惩罚通知也应该很简朴。当处理惩罚通知时,您需要知道发送方 (e.sender)。除此之外,您还需要可以或许从特定控件处理惩罚通知。因此,事件处理惩罚措施函数还有一个特别参数:控件名称,后头加下划线 (_)。譬喻,要查找用户在用户名编辑框键入的内容,应运行以下代码:
void login::on_change(
edit::ev::change &e, username_) {
cout << "name=" << e.sender->text;
}
譬喻,假设您但愿在美元和欧元之间转换钱币。当您在“EUR”框输入值并键入内容时,“USD”框会更新。当您在“USD”框输入值并键入内容时,“EUR”框会更新,如图 9 所示。以下是执行此操纵的代码:
struct convert : form, form_resource::convert {
double rate;
convert() : rate(1.5) {}
int mul_str(const string& a, double b) { ... }
void on_change(edit::ev::change&, eur_) {
usd->text = mul_str ( eur->text, rate); }
void on_change(edit::ev::change&, usd_) {
eur->text = mul_str ( usd->text, 1/rate); }
};
图 9 钱币互换器
此代码不问可知;mul_str 通过将字符串转换成双精度型并将其与转换率相乘,使双精度型与字符串相乘。
要像上面那样处理惩罚事件,我必需做大量事情。假设您拥有一个具有三个编辑框的窗体。个中每个编辑框都将生成某一组事件。对付每个这样的事件(譬喻,on_change),我可觉得每个控件生成一个可包围的函数
void on_change(edit::ev::change& e, ctrlname_);
可能只生成一个可包围的函数:
void on_change(edit::ev::change& e);
我更喜欢前一种办理方案 — 客户端代码较量简朴(与 Visual Basic 要领越发雷同)。您可以轻松地看处处理惩罚的内容(后一种办理方案与其差异,在事件的实施进程中,您必需手动查询通过 e.sender 生成事件的控件)。
这就是我实施第一种办理方案的原因。可是,实际上这个中也包括大量事情。eGUI++ 会监督资源编辑器。当添加新的控件或重定名控件时,会更新所有 <dlgname>_form_resource.h 文件。请留意,对付 form_resource::<dlgname> 类中的每个 <dlgname>_form_resource.h 文件,您必需从现有控件包围所有通知,而对付每个这样的已包围通知,则查找可以发送它的控件。下一步是生成将转发到每个控件的另一个可包围函数的实现。譬喻,图 10 显示了合用于具有两个编辑框和两个按钮的登录窗体的代码。
图 10 登录窗体代码
struct form_resource::login {
wnd<edit> name;
wnd<edit> passw;
wnd<button> ok, cancel;
typedef ... ok_;
typedef ... cancel_;
typedef ... name_;
typedef ... passw_;
virtual void on_change(edit::ev::change& e, name__) {}
virtual void on_change(edit::ev::change& e, passw__) {}
virtual void on_change(edit::ev::change& e) {
if ( e.sender == name) on_change(e, name__());
else if ( e.sender == passw) on_change(e, passw__());
}
// ... same for other edit notifications
virtual void on_click(button::ev::click & e, ok__) {}
virtual void on_click(button::ev::click & e, cancel__) {}
virtual void on_click(button::ev::click & e) {
if ( e.sender == ok) on_click(e, ok__());
else if ( e.sender == cancel) on_click(e, cancel__() );
}
// ... same for other button notifications
};
最后,您可以通过从 new_event<> 派闹事件来建设本身的事件。无论您是发送现有事件照旧您本身的事件,进程是沟通的:利用 send_event 函数:
struct hover : new_event<hover> {
int x,y; // position
hover(int x,int y) : x(x),y(y) {}
};
w->send_event( hover(x,y) );
#p#分页标题#e#
此库是线程安详的。别的,每个窗口都具有 m_cs 互斥变量(根基上是 CRITICAL_SECTION),我用该变量来确保每个要了解见都是线程安详的。在扩展窗口类时,您可以重用 m_cs 变量或建设本身的变量 — 这由您抉择。
菜单、快捷方法以及雷同项
假如以前举办过 GUI 编程,您应该知道按下菜单呼吁和按下键城市导致发送 WM_COMMAND。因此,当您收到 WM_COMMAND 时,很难知道该事件是来自控件照旧菜单(可能说键盘快捷方法)。eGUI++ 通过直接将菜单安排在窗体(对话框)上来办理此问题的第一方面。假如发送到窗体的呼吁不是来自按钮,即是来自菜单。
这样可掩护菜单呼吁。此刻让我们看一下快捷方法。快捷方法的问题是可以随时键入(譬喻,在编辑框中时)。键盘快捷方法(加快器)首先路由到即时窗口,然后路由到承载该窗口的窗体,接着路由到窗体的父级,并沿条理布局向上路由直至达到最上面的窗口。您第一次找到该快捷方法的事件处理惩罚措施时,处理惩罚便遏制(给定快捷方法不会由两个或多个窗口处理惩罚)。
剩下的就是东西栏 — 它们与菜单和快捷方法细密相关。当按下东西栏按钮时,该事件会转换成菜单呼吁并直接路由到承载它的窗体(无论该呼吁是来自菜单、快捷方法可能东西栏按钮,都无关紧急)。
假设您正在实现一个窗体以处理惩罚菜单呼吁,如下所示:
void on_menu_command( ev::menu&,
menu::some_menu_id) { ... }
要处理惩罚 new_file 和 open_file 这两个菜单呼吁,您需要建设以下处理惩罚措施:
void on_menu_command( ev::menu&,
menu::new_file) { ... }
void on_menu_command( ev::menu&,
menu::open_file) { ... }
选项卡控件和窗体
选项卡是极为常用的 GUI 模式。我已扩展了选项卡控件,以答允 tab_type 属性利用尺度值或 one_dialog_per_tab 值(在这种环境下,该控件可承载其他窗体)。在后一种环境下,您可以添加新的窗体,如下所示:
tab->add_form<form_type>( new_([args]) );
要添加我前面提到的登录窗体,您应编写:
tab->add_form<login>( new_() );
当添加至少一个窗体后,您便可以指定此选项卡窗体可承载的选项卡的数目:
tab->count = 5;
这里,我需要五个选项卡。这会选用最后添加的窗体,并按照需要多次举办复制。假设以前只有一个选项卡,则需要将第一个选项卡上的窗体别的克隆四次。没错。您可以克隆现有的任何窗口!
到今朝为止,我已向您先容了事件的侵入式处理惩罚。换句话说,就是扩展窗口类并最终响应其事件(可能说,在实现窗体时,响应其通知)。可是,您有时需要实现应用于多个窗口(互相有些不相关)的行为。
譬喻,调解巨细和改变外观。您可以以侵入的方法(建设实现该行为的那些类,然后从那些类派生 GUI 类)实现这类行为。可是,这会使代码巨大化,并且这并非始终可行(以改变外观为例)。通过非侵入方法实现行为,可以在其他应用措施中重用这种行为,也可以轻松封锁它。
譬喻,建设非侵入式事件处理惩罚措施类,再建设其实例,然后注册它。建设完新窗口后,会通知您的处理惩罚措施实例,而您可以选择是否监督此实例。假如选择监督,则需要手动指定需要监督的事件,如下所示:
// monitor button clicks
struct btn_handler : non_intrusive_handler {
void on_new_window_create(wnd<> w) {
if ( wnd<button> b = try_cast(w)) {
b->events.on_click += mem_fn(&on_click,this);
}
}
void on_click(button::ev::click&) { ... }
};
注册事件处理惩罚措施很容易。只需执行以下代码:
btn_handler bh;
window_base::add_non_intrusive_handler(bh);
#p#分页标题#e#
可以利用多种方法实现可调解巨细行为,详细取决于您的应用措施。譬喻,您可以在每个窗体上包围 on_size 事件并基于新窗体巨细更新控件的位置(很笨的要领,事情量大)。可能,可在每个窗体上建设控件间的干系,如“a.x = b.x + b.width + 4;”(此要领很是机动,但事情量也很大)。
可能,可在每个窗体大将控件标志为在每个轴上可调解巨细或可移动。假如将控件标志为在某轴上可调解巨细,则当窗体巨细变动时,控件巨细将更新;假如将控件标志为在某轴上可移动,则当窗体巨细变动时,控件将移动。这对付大大都应用措施已经足够了。我从 WTL 的 CResizeWindow 借用了这一理念,并利用非侵入式处理惩罚措施实现了它。假设您有一个雷同图 11 的对话框。假如您但愿在调解巨细后使其外观雷同图 12 中的样子,则需要利用下面的代码:
resize(name, axis::x, sizeable);
resize(desc, axis::x | axis::y, sizeable);
resize(ok, axis::x | axis::y, moveable);
resize(cancel, axis::x | axis::y, moveable);
图 11 对话框
图 12 调解巨细后的对话框
每个失败的 GUI 操纵都将触发异常。这样,您就知道堕落了。在调试模式中,这将生成失败的声明,而且措施会间断调试模式。这比无提示地忽略错误好得多,因为这可以相识到有对象堕落了(以可视的方法),然后可以查找该错误。
与 Visual Studio 2005 集成
Visual Studio 是精彩的 IDE,它的一个主要利益是可以扩展。eGUI++ 操作了这个利益;它附带提供新建窗体类领导的加载项。它还提供了雷同 Visual Basic 的栏,该栏使您可以在窗体上处理惩罚控件通知。要执行此操纵,只需选择一个控件,然后查察该控件可以生成的通知的列表。请留意,已经处理惩罚的通知会以粗体显示;单击某事件,将添加一个处理惩罚措施(假如以前不存在此处理惩罚措施)— 请拜见图 13 中的示例。
图 13 在窗体上处理惩罚控件通知
实质上,eGUI++ 会监督资源编辑器,以便在内容产生变动时可以更新 _form_resource.h 文件(假如需要)。它利用代码补全成果完成此任务,对此我已经举办了具体先容。
实现行为
构建 GUI 后,下一步是实现行为和思量数据绑定。许多窗体只用于收集数据。对付初学者来说,可以实现一个通用窗体类,该类在构建进程中获取要处理惩罚的数据并将其绑定到窗体的控件。然后,您可以指定一组用于验证数据的法则。在析构进程中,假如验证法则乐成,则利用来自控件的值更新原始数据;不然,原始数据将保持稳定。因此,对付每种新窗体,只需在资源编辑器中建设该窗体,然后指定用于验证数据的一组法则(与建设新的窗体类并复制逻辑的进程相对)。
展望将来,您可以将尺度模板库 (STL) 数组和荟萃、列表控件和树控件接洽起来。假设我拥有一个员工数组和一个列表控件。那么我可以将此数组绑定到该控件,如下所示:
list_ctrl->bind(employees);
您大概预推测了,这将更新列表控件。并且,列表控件单位格上的任何变动城市自动与员工数组同步。
我构建 eGUI++ 的目标是建设一个优良的库,以使 GUI 编程体验变得愉快。假如您是 C++ 编程人员,我很是但愿您同意我这么说。您可以到 torjo.com 下载源文件和二进制文件。