4.1 窗口解析

你当然大略的知道一个窗口指的是什么,但是为了更好的理解wxWidgets的API,你应该更精通wxWidgets使用的窗口模型的细节。它可能和你在某个特定平台上的窗口概念有些许的不同。下图演示了一个窗口中的各个基本元素:

窗口的概念

一个窗口指的是屏幕上的任何一个拥有以下特征的规则区域:它可以被改变大小,可以自我刷新,可以被显示和隐藏等等。它可以包含别的窗口(比如frame窗口就可以包含菜单条窗口,工具条窗口以及状态条窗口),也可以子窗口(比如一个静态的文本或者一副静态图片)。通常你在使用wxWidgets编写的程序运行的屏幕上看到的窗口,都和一个wxWindow类或者它的派生类对应,当然也不总是这样。比如本地原生下拉框就不总是用wxWindow建模的。

客户区和非客户区

当我们谈到窗口的大小,我们通常指的是它整个的大小,包括一些用于修饰的边框和标题栏等。而当我们谈到一个窗口的客户区大小,通常都只意味着窗口里面那些能被绘制或者它的子窗口能被放置的位置的大小。例如一个frame窗口的客户区大小就不包括那些菜单栏,状态栏和工具栏所占用的地方。

滚动条

大多数窗口都有显示滚动条的能力,这些滚动条通常是窗口自己增加的而不是有应用程序手动增加的。在这种情况下,客户区的大小还应该减去滚动条所占用的空间。为了优化性能,只有那些拥有wxHSCROLL和wxVSCROLL类型的窗口才会自动生成它们自己的滚动条。关于滚动条更多的情形我们会在本章稍候讨论wxScrolledWindow的时候讨论。

光标和鼠标指针

一个窗口可以拥有一个光标(wxCaret,用来显示当前的文本位置)和一个鼠标指针(wxCursor,用来显示当前鼠标指针的位置).当鼠标移入某个窗口时,wxWidgets会自动显示这个窗口的鼠标指针。当一个窗口变为当前焦点窗口时,如果可以的话,光标将会显示在当前文本的插入位置,或者如果这个焦点是由于鼠标点击造成的,光标将会显示在鼠标对应的位置。

顶层窗口

窗口通常分为象wxFrame,wxDialog,wxPopup这样的顶层窗口和其它窗口。只有顶层窗口创建的时候可以使用NULL作为其父窗口,也指有顶层窗口是延迟删除的(所谓延迟删除的意思是说,它们只有在系统空闲的时候才会被删除,也就是说只有当所有的事件都被处理完以后才会被删除)。而且出了Popup窗口以外,顶层窗口通常都有一个标题栏和一个关闭按钮,只要应用程序允许,它们就可以被拽着满屏幕的跑或者被改变大小。

座标体系

窗口的座标体系通常是左上角为原点(0,0),单位是象素。在某个用于窗口绘制的特定的上下文中,原点和比例允许被改变。这方面详细的可以参考第5章,"窗口绘制和打印".

窗口绘制

当一个窗口需要重绘的时候,它将收到两个事件,wxEVT_ERASE_BACKGROUND事件用于通知应用程序重新绘制背景, wxEVT_PAINT则用于通知重新绘制前景。那些已经准备好使用的窗口空间比如wxButton(按钮)通常知道怎么处理这两个事件,但是如果你是要创建自己的窗口控件,你就需要自己处理这两个事件了。通过获取窗口的变动区域你可以优化你的绘制代码。

颜色和字体

每一个窗口都有一个前景色和一个背景色。默认的背景擦除函数会使用背景色来清除窗口背景,如果没有设置背景色,则会使用当前的系统皮肤推荐的颜色进行背景的清除。前景色则相对来说很少被用到。每一个窗口也拥有一个字体设置,是否用到这个字体设置要取决于这个窗口本身的类型。

窗口变体

在Mac OS X上,一个窗口有一个窗口变体的概念,通过这个概念窗口可以被以不同级别的大小显示:wxWINDOW_VARIANT_NORMAL(默认显示级别), wxWINDOW_VARIANT_SMALL, wxWINDOW_VARIANT_MINI, 或者wxWINDOW_VARIANT_LARGE.当你有很多信息要展示而屏幕空间不够的时候,你可以使用相对较小的级别,但是这个显示级别的使用应该适可而止。有些程序总是喜欢使用小的显示级别。

改变大小

当一个窗口的大小,无论是来自用户还是应用程序本身的原因,发生变化时,它将收到一个wxEVT_SIZE事件。如果这个窗口拥有子窗口,它们可能需要被重新放置和重新计算大小。处理这种情况推荐的方法是使用sizer类,关于这个类的细节将在第7章,"使用Sizer确定窗口的布局" 这一章详细介绍。大多数已经确定的窗口类都有一个默认的大小和位置,这需要你在创建这些窗口的时候使用wxDefaultSize和 wxDefaultPosition这两个特殊的值。到目前为止,每一个控件都实现了DoGetBestSize函数,这个函数会返回一个基于控件的内容,当前字体以及其它各方面来说最合理的大小。

输入

任何窗口在任何时候都可以接收到鼠标事件,除非某个窗口已经临时捕获了鼠标或者这个窗口已经被禁止使用了,而对于键盘事件来说,只有当前处于活动状态的窗口才可以收到。应用程序自己可以设置自己为活动状态,wxWidgets也会在用户点击某个窗口的时候将其设置为活动状态。正变成活动状态的窗口会收到wxEVT_SET_FOCUS事件,而正失去焦点的窗口会收到wxEVT_KILL_FOCUS事件。请参考第6章:处理输入。

空闲事件处理和用户界面更新

所有的窗口(除非特殊声明)都将收到空闲事件wxEVT_IDLE,这个事件是在所有其它的事件都已经被处理完以后发出的。使用EVT_IDLE事件映射宏来处理。更多的信息请参卡第17章,"多线程编程"中的"空闲时间处理"小节.

其中一个特殊的空闲时间操作就是进行用户界面更新,在这个操作中所有的窗口都可以定义一个函数来更新自己的状态。这个函数将会被周期性的在系统空闲时调用。而EVT_UPDATE_UI(id, func)这个宏则通常不需要作什么事情。更多关于用户界面更新的细节请参考第9章,"创建自定义对话框".

窗口的创建和删除

一般来说,窗口都是在堆上使用new方法创建的,第15章,"内存管理,调试和错误检查"这一章对此有详细的描述,也会提到一些例外情况。大多数的窗口类都可以通过两种方法被创建:单步创建和两步创建。比如wxButton的两种构造函数如下:

wxButton();
wxButton(wxWindow* parent,
    wxWindowID id,
    const wxString& label = wxEmptyString,
    const wxPoint& pos = wxDefaultPosition,
    const wxSize& size = wxDefaultSize,
    long style = 0,
    const wxValidator& validator = wxDefaultValidator,
    const wxString& name = wxT("button"));

下面演示了使用一步创建的方法创建一个wxButton的实例(其中多数参数采用默认值):

wxButton* button  = new wxButton(parent, wxID_OK);

除非是frame或者dialog窗口,对于别的窗口,都必须在构造函数中传入一个非空的父窗口。这会自动把这个新窗口作为这个父窗口的子窗口。当父窗口被释放的时候,它的所有的子窗口也将被释放。正象我们前面提到的那样,你必须输入一个自定义的或者系统内建的标识符给这个窗口以便唯一标识这个窗口。你也可以使用wxID_ANY宏让wxWidgets帮你生成一个。你可以传递位置和大小参数给这个窗口,一个校验类(参考第9章),一个类型 (接下来会提到),和一个字符串的名字。这个字符串的名字可以是任意的值或者干脆不填也可以。只有在Xt和Motif系统上这个参数才有意义,因为在这些系统上控件是通过它们的名字来区分的,平常情况下则很少用到。

两步创建的意思是说,你先使用默认的构造函数创建一个实例,然后再使用这个实例Create方法实际创建这个对象。Create的参数和前面使用的构造函数的参数完全相同,还是用wxButton作为例子:

wxButton* button  = new wxButton;
button->Create(parent, wxID_OK);

窗口在你调用Create函数的时候会收到wxEVT_CREATE事件,你可以对这个事件进行进一步的处理。

使用两步创建的原因是什么呢?第一个原因是有时侯你可能想在晚些时候,在真正需要的时候才完整的创建窗口。另外一个原因是你希望在调用 Create函数之前设置窗口的某些属性值。尤其是这些属性值被Create函数使用的时候就显的更有意义。例如你可能想在窗口被创建之前设置 wxWS_EX_VALIDATE_RECURSIVELY扩展类型,而这个类型必须通过SetExtraStyle函数才可以设置。在这种情况下,对某些对话框类而言,validation必须在Create函数被调用之前被初始化。所以如果你需要这个功能,就必须在调用Create之前初始化这个值。

当你创建一个窗口类,或者其它任何非顶层窗口的派生类的时候,如果它的父窗口是可见的,那么它也总是可见的,你可以通过Show(false)来使它不可见。这和wxDialog或者wxFrame这样的顶层窗口是不一样的。顶层窗口在创建时通常是不可见的,这样可以避免绘制那些子窗口和排列子控件的时候发生闪烁。你需要通过Show或者(对于模式对话框来说)ShowModal的调用让它可见。

窗口是通过调用其Destroy函数(对于顶层窗口来说)或者delete函数(对于其子窗口来说)来释放的。wxEVT_DESTROY事件会在窗口刚刚要被释放之前被调用。实际上,子窗口是被自动释放的,所以delete函数是很少直接被手动调用的。

窗口类型

窗口拥有一个类型和一个扩展类型。窗口类型是设置窗口创建时的行为和外观的一种简洁的方法。这些类型的值被设置成可以使用类似比特位的方法操作,例如下面的例子:

wxCAPTION | wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxTHICK_FRAME

wxWindow类有一组基本的类型值,例如边框的类型等,每一个派生类可以增加它们自己的类型。需要特别指出的是,扩展类型的值是不可以拿来给类型用的。