第3 章 CEGUI 基类的实现

这一章介绍CEGUI 最基本的窗口类Window。CEGUI 中所有的窗口都必须派生自Win
dow(不管直接还是间接)。在介绍Window 类之前,我认为介绍窗口原理比较重要。所以
第1 节介绍窗口的原理。

3.1 窗口设计原理

游戏中的窗口系统,没有Window 窗口系统那么复杂。但他们的结构是类似的。窗口从
外表上看是由一些图片的累加而成。从行为上看,它可以接受用户的输入,并做出合适的响
应。从结构上看是典型的树状层次结构(窗口有父窗口,子窗口,同级的兄弟窗口)。下面
就具体根据这几个方面,详细阐述。
第一,窗口的画面如何组织。CEGUI 提供了LookNFeel 文件来描述窗口的布局,详细
介绍请参考第6 章。CEGUI 窗口最多可以分割成9 个部分,每一部分都有独立的贴图(并
不意味着是一张图片,可能是图片的一部分),为什么不用一张贴图呢?不是不可以,用一
张贴图就需要美工把贴图做的像个窗口的样子。这样做扩展性差,灵活性也不够。最重要的
是不支持窗口的缩放,因为一旦缩放窗口贴图就会被拉伸,图像变的模糊。所以我们在设计
外观文件的时候要注意,如果窗口的大小不是固定的一定要使用支持大小变动的分割方法。
支持大小变动的格式大概有三种,如图3-1 所示一种是支持各向拉升的自适应窗口,它需要
把窗口分割成九块。需要注意的是这九块中只有中间的一块必须是支持各向拉伸都不会产生
马赛克的。就是说它是背景图片,图片样子单一任何方向上都可以任意扩展。最简单的

这种图片的例子就是单色背景图片了。这种情况还要求b 和h 是水平方向可拉伸的。d
和f 是竖直方向可以拉伸的。第二种是水平方向可以拉伸的,这种布局要求中间的部分是可
以水平方向拉伸的。这种布局只有三部分左中右。第三种是竖直方向可拉伸的,它要求中间
部分是竖直方向可拉伸的。这种布局也只有三部分上中下。后两种不能在各个方向上拉伸,
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 39 -
但是也很有用。最普通的一种布局就是一张图片作为窗口的贴图了,这种布局一般不支持拉
伸(除非它是第一种布局中的中间图片)。
第二,窗口的用户响应。窗口需要接收用户的输入,以及Window 消息输入(主要是I
ME 消息,输入法消息),并且要响应这些输入。CEGUI 通过System 类来接收这些消息,并
且找到处理这些消息的窗口,处理这些消息。除了这些外部消息外,窗口也会产生内部消息,
这些消息多是子窗口通知父窗口的通知消息,和父窗口通知子窗口的消息。比如说按钮子窗
口接收到了鼠标单击消息,他要通知父窗口,用户鼠标单击。父窗口不能接收到用户消息(因
为用户的消息,只能给最上层的窗口处理,父窗口一般不是最上层窗口,一般是子窗口处理
消息,然后在通知父窗口)。CEGUI 中通过事件机制来发布子窗口的通知消息,如果父窗口
希望处理子窗口的通知消息,可以注册子窗口的对应消息的处理函数。当然有时候父窗口也
能是最上层窗口,如果鼠标不再所有的子窗口上面,而在父窗口上。比如用户调整窗口大小
的时候。父窗口要通知所有字窗口,以便子窗口调整自己的大小。这一步不是使用事件驱动,
而是父窗口直接调用子窗口的事件响应函数,这种函数一般以on 开头,比如OnParentSize
Change。
第三,窗口的树状层次结构。窗口显然有父子关系,比如说框架窗口有按钮子窗口,编
辑框子窗口等等。这些子窗口是属于父窗口的,他们的位置信息是相对于父窗口来说的。他
们是在父窗口的贴图上层覆盖上自己的贴图。当然也有兄弟关系,比如上面的按钮子窗口和
编辑框子窗口。他们没有父子关系,但他们有共同的父窗口,所以他们是兄弟窗口的关系。
由于窗口有上面的复杂关系所以树形数据结构最为适合描述。CEGUI 中使用STL 中的vect
or 容器保存窗口所有的子窗口,没有使用常用的链表结构。CEGUI 中大量使用了STL 容器,
这样做似乎会浪费一些内存,但现在内存非常便宜,容量也非常大,所以也不是什么太大的
问题了。
3.2 Window 类
Window 类作为CEGUI 的所有窗口的基本类,它在CEGUI 中占有重要的地位。它的设
计非常复杂,这也给清晰的描述它带来的困难。本书打算分以下几个小节来描述Window 类:
第一,Window 类的继承关系以及与其相关的函数。第二,窗口的组织结构。第三,窗
口位置和大小。第四,窗口渲染。第五,事件处理。第六,窗口状态。第七,窗口与输入系
统。第八,其他部分。Window 类成员函数和成员变量非常多,所以我们采用分为七个部分
的方法详细介绍。
3.2.1 Window 类的继承关系以及与其相关的函数。
Window 类继承自PropertySet 和 EventSet。这两个类第2 章已经介绍,分别处理窗口的
属性和窗口的事件。这部分在Window 类中没有显示的定义任何函数和成员变量。但由于继
承的关系,所以Window 类可以设置获取窗口的属性。注册和反注册事件处理函数。如果需
要注册事件处理函数可以直接使用窗口指针作为EventSet 的指针来注册函数,在第2.3 节介
绍的布局文件就是这样处理的。如果需要设置或者获取窗口的某个属性的值也可以直接使用
窗口的指针来调用属性集的对应函数。
3.2.2 窗口的组织结构
简单的说这部分介绍窗口的组织结构相关的函数。这部分涉及到的成员变量有如下几
个:

typedef std::vector<Window*> ChildList;
//子窗口列表,使用vector 来保存子窗口的指针
ChildList d_children;
//保存当前的渲染顺序,用来组织渲染
ChildList d_drawList;
//静态变量,保存当前捕获输入系统输入的窗口(这个和Window 系统捕获窗口是不一样的)
static Window* d_captureWindow;
//先前捕获输入的窗口,用来还原旧的捕获模式
Window* d_oldCapture;
//父窗口的指针
Window* d_parent;

这部分处理窗口之间的关系。处理窗口直接关系的函数有可以分为两种,处理孩子关系
的函数和处理父窗口的函数。
首先,介绍处理孩子的函数。主要有获取一个子窗口和判断一个窗口是不是这个窗口的
子窗口两类函数。isChild 用来判断是否是窗口的子窗口。它有三个函数分别接受不同的参
数,一个接收窗口名称,一个接收窗口ID,窗口ID 在父窗口的所有子窗口之间是唯一的,
最后一个接收Window 的指针。下面是其中一个函数,可以看到它遍历所有子窗口查找是否
有名称和给定名称相同的子窗口,如果有自返回true 否则返回false。getChildCount 用来返
回子窗口的个数,它等于d_children 中有效元素的个数。

bool Window::isChild(const String& name) const
{
size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
if (d_children[i]->getName() == name)
{
return true;
}
}
return false;
}

提示:
我们只介绍相同功能,但参数不同的函数其中之一,其他函数类似,读者可以自己阅读。
下面这个函数和isChild 类似,但它递归查找所有的子窗口。即也查找窗口的间接子窗
口。

bool Window::isChildRecursive(uint ID) const
{
size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
if (d_children[i]->getID() == ID || d_children[i]->isChildRecursive(ID))
{
return true;
}
}
return false;
}

第二类函数是获取子窗口的函数getChild,这个函数接受两种参数,一种是窗口名称,
另一种是子窗口ID。

Window* Window::getChild(uint ID) const
{
size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
if (d_children[i]->getID() == ID)
{
return d_children[i];
}
}
// 抛出未知对象的异常,因为直接子窗口中没有ID 对应的窗口
char strbuf[16];
sprintf(strbuf, "%X", ID);
throw UnknownObjectException("Window::getChild - The Window with ID: '" +
std::string(strbuf) + "' is not attached to Window '" + d_name + "'.");
}

获取间接子窗口的函数,也有两种参数的重载函数。
//获取间接子窗口,返回子窗口的指针

Window* Window::recursiveChildSearch( const String& name ) const
{
size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
String childName = d_children[i]->getName();
//判断是否窗口名称或者前缀加上窗口名称与name 相等
if(childName == name || childName == d_windowPrefix + name)
{
return d_children[i];
}
}
//递归查找子窗口
for(size_t i=0;i<child_count;i++)
{
Window* temp = d_children[i]->recursiveChildSearch(name);
if(temp)
return temp;
}
return 0;
}


 

获取当前激活的窗口,这个窗口是最顶层的激活窗口,也就是激活的最上层的子窗口。
激活的概念读者应该很熟悉,激活窗口接收当前的用户输入。这个函数唯一的调用类就是S
ystem 类。

const Window* Window::getActiveChild(void) const
{
// 如果自己没有被激活则子窗口不可能被激活
if (!isActive())
{
return 0;
}
size_t pos = getChildCount();
while (pos-- > 0)
{
//如果子窗口被激活则递归返回被激活的子窗口,目的是找到最上层的子窗口
if (d_drawList[pos]->d_active)
return d_drawList[pos]->getActiveChild();
}
// 没有子窗口被激活的话,返回自己,此时自己就是最上层的激活窗口
return this;
}


 

其次,介绍父窗口相关的处理函数。父窗口的处理函数主要有,判断一个窗口是否是这
个窗口的祖先,获取这个窗口的子窗口,添加和删除子窗口。
isAncestor 用来判断是否name 对应的窗口是这个窗口的祖先。它还有两个参数不同的
重载窗口。

bool Window::isAncestor(const String& name) const
{
// 如果没有父窗口,显然不用判断,直接返回false
if (!d_parent)
{
return false;
}
// 检查直接父窗口
if (d_parent->getName() == name)
{
return true;
}
// 不是直接父窗口,则递归到父窗口的父窗口
return d_parent->isAncestor(name);
}

获取一个窗口的父窗口,非常简单的返回d_parent 变量。
Window* getParent(void) const {return d_parent;}
添加一个子窗口,这个函数还有一个带窗口名称的重载函数。

void Window::addChildWindow(Window* window)
{
// 有效性检查,不能添加自己为自己的孩子,不能添加空指针
if (window == this || window == 0)
{
return;
}
addChild_impl(window);
//激发事件
WindowEventArgs args(window);
onChildAdded(args);
//通知窗口Z 值改变,窗口的Z 值影响窗口的描述顺序
window->onZChange_impl();
}

添加子窗口的具体实现。

void Window::addChild_impl(Window* wnd)
{
// 如果窗口先前有父窗口则先断开与先前窗口的联系
if (wnd->getParent())
wnd->getParent()->removeChildWindow(wnd);
//添加窗口到描绘列表中
addWindowToDrawList(*wnd);
// 添加子窗口到列表中
d_children.push_back(wnd);
// 设置父窗口
wnd->setParent(this);
// 强制更新子窗口区域,因为父窗口改变了
WindowEventArgs args(this);
wnd->onParentSized(args);
}

有添加必然有删除一个子窗口了,但这里就不做介绍了。
提示:
伴随窗口的一些操作会激发一些事件,有提供给外部使用的比如调用事件集的fireEvent 函
数。也有内部使用的事件以on 开头的一些事件处理函数,当然这些函数也可以激发外部事
件,如果需要。总的来说,如果设计窗口的功能的人认为有必要通知外部或者内部一些事件
就可以激发需要的事件。
3.2.3 窗口位置和大小
提到位置和窗口,这里要做简单的介绍。CEGUI 中位置和大小使用了比较特殊的方法
来表示。一般来说窗口的大小和位置使用绝对坐标和相对坐标两者之一。绝对坐标使用的表
示数据是以像素为单位的。比如说位置坐标x=10,y=4,表示相对于父窗口在x 方向偏移1
0 个像素单位,在y 方向上偏移4 个单位。相对坐标使用的数据表示的是相对于父窗口的百
分比。比如说x=0.2,y=0.3,表示x 方向偏移现在父窗口宽度的百分之20,也就是乘以0.2,
y 方向上偏移父窗口高度的百分之30,也就是乘以0.3。CEGUI 采用的是两者的结合,使用
相对坐标加偏移来表示位置或者大小。CEGUI 称之为统一坐标系统。一个位置信息使用两
个数来表示,一个是相对坐标,两个是绝对坐标(偏移量),就是说x = scale*getWidth()
+ offset。(这里拿x 坐标来举例)如果scale 为0 且offset 不为0 则统一坐标变成了绝对坐
标系统,如果offset 为0 且scale 不为0 则表示相对坐标系统。
提示:
最终用于渲染的坐标必须是绝对坐标,渲染系统只支持绝对坐标系统(而且这个坐标系统是
渲染窗口的坐标系统,或者说屏幕坐标系统)。统一坐标转换为绝对坐标的公式就是R =
scale*A + offset。其中A 可以是高度或者宽度,scale 代表相对坐标,offset 是绝对偏移所以
不用做转换直接与相对坐标转换成绝对坐标后相加。最终结果R 为绝对坐标。在CEGUI 中
子窗口的永远在父窗口形成的坐标系下,也就是说子窗口的坐标系的原点永远是父窗口的位
置。子窗口的坐标数据是相对于父窗口的。比如说子窗口位置x=10,y=5 这个10 和5 表示
从父窗口的位置(父窗口的位置是子窗口的原点(0,0))开始偏移10 和5 个单位。CEGUI
中窗口的位置全部使用父窗口坐标系,没有使用过渲染窗口(或者叫做区域,这是操作系统
提供的窗口区域)的坐标系,除非窗口没有父窗口。如读者还是不明白各种坐标以及坐标系
之间的关系,请参考图3-2。
图3-2 描述了父窗口和子窗口坐标之间的关系。从图中可以看到子窗口使用父窗口形成
的坐标系来描述自己的位置(O‘的位置是父窗口坐标系下的坐标)。同时自己又形成了一
个坐标系,这个坐标系是以自己的位置(O’)为原点的,X,Y 轴方向和父窗口相同。子窗
口还可以有自己的子窗口(设为B),B 使用子窗口形成的坐标系。也就是说在设置窗口的
位置的时候,读者需要考虑的是这个位置使用的是父窗口坐标系。窗口的大小和坐标系是独
立的,直接设置它就可以了。其实Window 的GUI 系统也是这样处理子窗口和父窗口之间
的坐标关系的。

提示:
没有父窗口的窗口使用那个坐标系呢?它使用Windows 应用程序(这里以WindowGUI 系统
为例,CEGUI支持Linux,Mac系统)渲染窗口客户区坐标系。Windows 应用程
序使用屏幕坐标系。显卡驱动程序也使用屏幕坐标系,所以要描述一个CEGUI 窗口必须使
用屏幕坐标系的位置坐标。
位置和大小涉及到的成员变量如下。

//保存窗口的矩形区域,使用统一坐标表示
URect d_area;
//当前窗口的大小,以像素为单位
Size d_pixelSize;
//d_screenUnclippedRect 表示窗口在屏幕空间的没有裁剪前的坐d_screenUnclippedRectValid,
//表示d_screenUnclippedRect 是否有效,从他的名字也可以看出
mutable Rect d_screenUnclippedRect;
mutable bool d_screenUnclippedRectValid;
//表示窗口排除窗口的某些部分的未裁剪矩形,类似WindowsGUI 中的客户区区域
mutable Rect d_screenUnclippedInnerRect;
mutable bool d_screenUnclippedInnerRectValid;
//d_screenUnclippedRect 的屏幕裁剪后的区域
mutable Rect d_screenRect;
mutable bool d_screenRectValid;
//d_screenUnclippedInnerRect 的屏幕裁剪后的区域
mutable Rect d_screenInnerRect;
mutable bool d_screenInnerRectValid;
//窗口的最小尺寸
UVector2 d_minSize;
//窗口的最大尺寸
UVector2 d_maxSize;

WindowGUI 的客户区域指的是那一部分区域呢?比如打开记事本程序,客户区如图3-
3 的黑色部分所示:

注意:
这里的屏幕指的是Window 应用程序(游戏客户端)客户区区域,而不是电脑的屏幕。
URect 和Size 都是CEGUI 定义的类,用来描述窗口的位置矩形和以像素为单位的大
小信息,也就是高和宽。前面我们介绍过,CEGUI 使用统一坐标,使用两个数表示一个X
或者Y 坐标,普通的相对坐标和绝对坐标都使用一个数(一般是浮点型的)表示,CEGUI
使用UDim 类来描述这个值。
这个类有两个成员变量d_scale 代表相对量,d_offset 代表绝对量。这个类可以提供
绝对坐标和相对坐标。通过函数Absolute 和asRelative。也就是说统一坐标只是提供了一种
全新的计算方法,它并不能直接被渲染系统接受,需要转化为绝对坐标。
float d_scale, d_offset;
他们的定义如下,可以看出转化为绝对坐标时,将相对分量乘以base(一般为窗口的高
或者宽)转化为绝对坐标,然后在加上绝对偏移(它自己已经是绝对坐标了)。转化为相对
坐标时,绝对分量处以base 然后加上相对偏移。
float asAbsolute(float base) const { return PixelAligned(base * d_scale) + d_offset; }
float asRelative(float base) const { return (base != 0.0f) ? d_offset / base + d_scale : 0.0f; }
UDim 类定义了一些操作符,比如加减乘除等一般是绝对部分和绝对部分做相应操作,
相对部分和相对部分做操作,这里就不在介绍了。
UDim 只能代表一个X 或者Y 轴上的坐标,所以两个UDim 变量才能代表一个二维点。
在CEGUI 中二维点使用UVector2 类来表示。很显然它有两个成员变量。分别代表X 轴和Y
轴的坐标值。
UDim d_x, d_y;
这个类的其他成员函数也很好理解这里就不在列出了。
哪么要定义一个矩形,我们知道需要两个点,分别是左上角和右下角。CEGUI 定义U
Rect 来表示一个矩形区域。它有两个成员变量分别代表左上角和右下角的点。
UVector2 d_min, d_max;
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 47 -
d_min 代表左上角,d_max 代表右下角。其实有这个矩形已经知道窗口的大小了哪么何
必有个成员变量d_pixelSize 来表示窗口的大小呢?我个人认为主要是方便使用。我们知道
窗口的大小是使用非常频繁的,如果每次都计算显然不太方便,还不如设置一个变量保存他。
Size 类只有两个变量一个表示宽度,一个表示高度。它的成员函数只有==和!=两个操
作符。
float d_width, d_height;
位置相关函数中,最重要的就是setArea_impl,它实现了改变窗口位置和大小等重要的
操作,其他的函数比如setArea 的几个重载函数都是调用它来实现的。

void Window::setArea_impl(const UVector2& pos, const UVector2& size, bool topLeftSizing,
bool fireEvents)
{
// 设置这些变量的无效标志,在相应获取变量时会重新计算
d_screenUnclippedRectValid = false;
d_screenUnclippedInnerRectValid = false;
d_screenRectValid = false;
d_screenInnerRectValid = false;
// 标记变量,用来标识是否窗口移动或者大小改变了
bool moved = false, sized;
// 保存旧的窗口大小,后边要使用这个值
Size oldSize(d_pixelSize);
// 计算当前的最大和最小尺寸,来限制我们窗口大小
Vector2 absMax(d_maxSize.asAbsolute(System::getSingleton().getRenderer()->getSize()));
Vector2 absMin(d_minSize.asAbsolute(System::getSingleton().getRenderer()->getSize()));
//这一步是什么原理,获取父窗口的大小来计算自己的大小,相对值是相对于父窗口的
//或者说子窗口使用的是父窗口形成的坐标系
d_pixelSize = size.asAbsolute(getParentPixelSize()).asSize();
// 限制我们计算出的大小不超过最大和最小的限制
if (d_pixelSize.d_width < absMin.d_x)
d_pixelSize.d_width = absMin.d_x;
else if (d_pixelSize.d_width > absMax.d_x)
d_pixelSize.d_width = absMax.d_x;
if (d_pixelSize.d_height < absMin.d_y)
d_pixelSize.d_height = absMin.d_y;
else if (d_pixelSize.d_height > absMax.d_y)
d_pixelSize.d_height = absMax.d_y;
//设置区域的大小
d_area.setSize(size);
sized = (d_pixelSize != oldSize);
//修改位置信息
if (!topLeftSizing || sized)
{
// 如果发生位置改变,则修改位置信息
if (pos != d_area.d_min)
{
d_area.setPosition(pos);
moved = true;
}
}
// 如果需要则激发事件
if (fireEvents)
{
WindowEventArgs args(this);
if (moved)
{
onMoved(args);
// 重设置这个句柄为false 方便下面OnSize 的调用
args.handled = false;
}
if (sized)
{
onSized(args);
}
}
}

读者可能不能理解当前的最大和最小尺寸限制窗口的含义以及system::getSingleton().ge
tRenderer()->getSize()获取的是什么内容。Window 下有个消息WM_GETMINMAXINFO,用
来获取窗口的最大和最小值。我们这里的absMax 和absMin 代表窗口的最大和最下值。窗
口的大小不能超过最大值,也不能小于最小值。system::getSingleton().getRenderer()->getSize
(),获取渲染窗口的尺寸(一般来说就是游戏客户区的大小)。absMax 和absMin 的默认值
分别是(1.0f,1.0f)和(0.0f 和0.0f),也就是说最大是渲染窗口的尺寸,最小值是宽高都
是0。setArea 设置整个区域包括位置和大小,它共有三个重载函数。setPosition,setXPositi
on,setYPosition 单独设置窗口的位置。setSize 设置大小,setWidth 设置宽度,setHeight 设
置高度。setMaxSize,setMinSize 设置最大和最下尺寸。这些设置函数还有对应的get 函数。
这里还要介绍两个重要的函数onSized 和onMoved,分别响应尺寸变化和位置变化。
void Window::onSized(WindowEventArgs& e)
{
//通知所有孩子父窗口尺发生变化
size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
WindowEventArgs args(this);
d_children[i]->onParentSized(args);
}
//通知子窗口重新布局,其实最终还是设置子窗口区域(Area)信息
performChildWindowLayout();
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 49 -
//请求重画,具体如何描绘请读者参考第4 章
requestRedraw();
//激发事件,调用事件处理函数EventNamespace 用来区分事件来自那类窗口
fireEvent(EventSized, e, EventNamespace);
}
//窗口移动是被setArea_impl 调用
void Window::onMoved(WindowEventArgs& e)
{
//通知子窗口父窗口已经被移动了
const size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
d_children[i]->notifyScreenAreaChanged();
}
// 因为移动位置不需要全部重画,所以只要重新提交,不需要全部重画
System::getSingleton().signalRedraw();
//激发事件
fireEvent(EventMoved, e, EventNamespace);
}
除了以上介绍的一些位置和尺寸相关的函数外,还有一类比较重要的函数。就是获取窗
口矩形的函数,这些矩形不在父窗口坐标系下的结果,而是在屏幕坐标系下的结果。他们的
主要目的是方便渲染接口使用。第一个getPixelRect 和getPixelRect_impl。前者是调用后者
实现的。
Rect Window::getPixelRect(void) const
{
//如果先前计算的值无效了,重新计算
if (!d_screenRectValid)
{
d_screenRect = (d_windowRenderer != 0) ?
d_windowRenderer->getPixelRect()
: getPixelRect_impl();
d_screenRectValid = true;
}
//否则直接返回保存的值
return d_screenRect;
}
Rect Window::getPixelRect_impl(void) const
{
// 判断是否被父窗口裁剪,getIntersection 获取两个矩形的交集
if (isClippedByParent() && (d_parent != 0))
{
return getUnclippedPixelRect().getIntersection(d_parent->getInnerRect());
}
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 50 -
// 如果不被父窗口裁剪则被屏幕裁剪
else
{
return getUnclippedPixelRect().getIntersection(System::getSingleton().getRenderer()->g
etRect());
}
}
读者可能还是不理解这两个函数在做什么?没关系随着本书的深入讲解,读者会陆续理
解的。这两个函数返回的是在屏幕空间的窗口矩形。getArea 函数返回的是在统一坐标结果,
而且不是在屏幕坐标系下是在父窗口的局部坐标系下。下面介绍getUnclippedPixelRect,这
个函数也是返回屏幕坐标下的窗口区域,但是它是没有被裁剪过的,getPixelRect 返回的是
被裁剪过的区域。
Rect Window::getUnclippedPixelRect(void) const
{
if (!d_screenUnclippedRectValid)
{
Rect localArea(0, 0, d_pixelSize.d_width, d_pixelSize.d_height);
d_screenUnclippedRect = CoordConverter::windowToScreen(*this, localArea);
d_screenUnclippedRectValid = true;
}
return d_screenUnclippedRect;
}
这个函数首先获取屏幕的局部坐标localArea,然后通过坐标转换函数windowToScreen
将局部坐标转换为在屏幕空间的坐标。这个函数的实现是通过窗口的父链递归累加,最终到
屏幕空间的,有兴趣的读者可以看具体代码。
除了刚才讲的两个函数外还有另外两个函数getUnclippedInnerRect 和getInnerRect,由
于篇幅限制这里就不做介绍了。他们也是获取在屏幕空间的区域,不过获取的是内部区域,
一般用于框架窗口。效果就像Window GUI 中获取窗口的客户区域一样,如图3-3 所示。
3.2.4 窗口渲染
窗口的渲染比较复杂,涉及到CEGUI 的渲染机制和渲染模块。所以这里这是简单的介
绍Window 类中与渲染相关的函数和成员变量,具体渲染的原理和流程在第4 章详细介绍。
render 函数是窗口渲染入口函数。
void Window::render(void)
{
// 如果窗口被隐藏则不做任何处理,隐藏窗口不可见
if (!isVisible()) {
return;
}
// 激发渲染开始的事件,这个我觉得没有必要,用户可以根据需要选择是否激发
WindowEventArgs args(this);
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 51 -
onRenderingStarted(args);
// 渲染这个窗口,首先获取系统的渲染模块
Renderer* renderer = System::getSingleton().getRenderer();
//调用描绘自己的函数
drawSelf(renderer->getCurrentZ());
//递增Z 值,Z 值基本没用,窗口的层叠关系是通过d_drawList 的顺序确定的
renderer->advanceZValue();
// 渲染子窗口
size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
d_drawList[i]->render();
}
// 激发渲染结束的事件
WindowEventArgs args(this);
onRenderingEnded(args);
}
render 函数是窗口的基本渲染函数。由于窗口的父子关系,在CEGUI 中只有一个根窗
口,它就是DefaultGUISheet(当然它可以是任何一种窗口类型),所有CEGUI 其他窗口都
是它的直接或者间接子窗口。所以渲染的时候只要它调用了render 函数,所有的CEGUI 可
见窗口都会被渲染。我们看到render 函数主要调用了drawSelf 函数来渲染自己,所以这个
函数就是窗口描绘自己的主函数。
void Window::drawSelf(float z)
{
//如果需要重绘则清理渲染缓冲,重新填充渲染缓冲区
if (d_needsRedraw)
{
// 清除上次重绘的渲染缓冲内容
d_renderCache.clearCachedImagery();
// 如果窗口有渲染窗口类则调用它的render 方法,否则调用populateRenderCache
if (d_windowRenderer != 0)
{
d_windowRenderer->render();
}
else
{
populateRenderCache();
}
// 标记下次不用在重绘了,如果窗口需要重绘只需要设置这个变量为true
d_needsRedraw = false;
}
//如果渲染缓冲有内容,则调用渲染缓冲的渲染函数
if (d_renderCache.hasCachedImagery())
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 52 -
{
Point absPos(getUnclippedPixelRect().getPosition());
// 以自己的屏幕窗口区域作为裁剪器
Rect clipper(getPixelRect());
// 如果窗口不是完全裁剪
if (clipper.getWidth())
{
// 发送渲染命令到渲染模块,注意absPos,它是本窗口在屏幕上的偏移
d_renderCache.render(absPos, z, clipper);
}
}
}
关于d_renderCache 以及CEGUI 的渲染机制请参考第4 章。populateRenderCache 函数
Window 类提供了空实现。它是个虚函数,如果派生类没有渲染模块,哪么必须重载这个函
数实现窗口的描绘,否则窗口将不会被描绘。requestRedraw 请求重绘函数,在窗口需要重
绘的时候可以调用这个函数。d_needsRedraw 控制窗口是否重新渲染,signalRedraw 告诉系
统需要重新绘制窗口了。
void Window::requestRedraw(void) const
{
d_needsRedraw = true;
System::getSingleton().signalRedraw();
}
还有几个间接和渲染有关系的函数,他们与窗口的Alpha 值有关系。setAlpha 设置窗口
的Alpha 值,setInheritsAlpha 设置是否窗口从父窗口取得Alpha 值。
void Window::setAlpha(float alpha)
{
d_alpha = alpha;
WindowEventArgs args(this);
onAlphaChanged(args);
}
void Window::setInheritsAlpha(bool setting)
{
if (d_inheritsAlpha != setting)
{
// 保存旧的Alpha 值,以便和设置后的Alpha 值比较
float oldAlpha = getEffectiveAlpha();
// 通知设置改变了
d_inheritsAlpha = setting;
WindowEventArgs args(this);
onInheritsAlphaChanged(args);
// 如果Alpha 值发生改变了,通知子窗口
if (oldAlpha != getEffectiveAlpha())
{
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 53 -
args.handled = false;
onAlphaChanged(args);
}
}
}
//这个函数产生继承Alpha 的效果,自己的Alpha 值和父窗口的相乘
float Window::getEffectiveAlpha(void) const
{
if ((d_parent == 0) || (!inheritsAlpha()))
{
return d_alpha;
}
return d_alpha * d_parent->getEffectiveAlpha();
}
void Window::onAlphaChanged(WindowEventArgs& e)
{
// 扫描子窗口列表,调用所以继承Alpha 子窗口的onAlphaChanged 通知消息
size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
if (d_children[i]->inheritsAlpha())
{
WindowEventArgs args(d_children[i]);
d_children[i]->onAlphaChanged(args);
}
}
requestRedraw();
fireEvent(EventAlphaChanged, e, EventNamespace);
}
CEGUI 提供了窗口Alpha 继承的操作效果,似乎用的也不多。它产生什么效果呢?读
者可以思考这个问题。moveToFront_impl 函数貌似和渲染没有关系,但它会调整渲染的层叠
次序,所以把它放在这里。这个函数主要的功能是把窗口放在所以窗口的最上面。
bool Window::moveToFront_impl(bool wasClicked)
{
bool took_action = false;
// 如果窗口没有父窗口,则集合窗口自己,这也是递归结束的标志,一般到根窗口
if (!d_parent)
{
// 如果窗口没有被激活则激活之
if (!isActive())
{
took_action = true;
ActivationEventArgs args(this);
args.otherWindow = 0;
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 54 -
onActivated(args);
}
return took_action;
}
// 提升父窗口到父窗口的所有兄弟窗口的最上层,这个会开始递归调用
took_action = wasClicked ? d_parent->doRiseOnClick() :
d_parent->moveToFront_impl(false);
// 获取激活的兄弟窗口
Window* activeWnd = getActiveSibling();
// 如果自己没有被激活,则激活自己
if (activeWnd != this)
{
took_action = true;
// 通知自己被激活
ActivationEventArgs args(this);
args.otherWindow = activeWnd;
onActivated(args);
// 通知先前被激活的窗口,现在不在激活了
if (activeWnd)
{
args.window = activeWnd;
args.otherWindow = this;
args.handled = false;
activeWnd->onDeactivated(args);
}
}
// 提升自己到所有自己的兄弟窗口的最高层
if ((d_zOrderingEnabled) && !isTopOfZOrder())
{
took_action = true;
// 从父窗口的渲染窗口列表中移除自己
d_parent->removeWindowFromDrawList(*this);
// 重新加入到父窗口的渲染列表中,通过这一步自己变成了父窗口中最高层
d_parent->addWindowToDrawList(*this);
// 通知相关的窗口自己的Z 值改变了,可见渲染模块提供的Z 值是没有用处的
onZChange_impl();
}
return took_action;
}
这个函数会沿着父链递归激活所有的父窗口,直到激活调用者窗口。被激活的窗口是T
op-Most 的,它可以遮盖所有的窗口。窗口有个点击激活窗口的属性,如果窗口被点击了则
会调用doRiseOnClick 函数,这个函数调用moveToFront_impl 并传递true 给这个函数。
bool Window::doRiseOnClick(void)
{
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 55 -
// 这个窗口是否提升自己,当窗口被点击后
if (d_riseOnClick)
{
return moveToFront_impl(true);
}
//否则继续父链递归其他窗口
else if (d_parent)
{
return d_parent->doRiseOnClick();
}
return false;
}
addWindowToDrawList,根据at_back 参数决定添加到什么位置。窗口在d_drawList 中
的位置决定了窗口显示的位置,也就是我们平时说的Z 值。(为什么不直接设置使用渲染模
块提供的Z 值呢?因为渲染UI 的时候一般要关掉深度测试,所以渲染模块提供的Z 值来决
定显示的层叠顺序的方法已经无效了。)
void Window::addWindowToDrawList(Window& wnd, bool at_back)
{
//添加到窗口的后面,它不可能被显示在最上面了,一般是没有被激活的窗口
if (at_back)
{
// 计算窗口需要被插入的位置
ChildList::iterator pos = d_drawList.begin();
if (wnd.isAlwaysOnTop())
{
// 找到第一个TopMost(最高层)的窗口
while ((pos != d_drawList.end()) && (!(*pos)->isAlwaysOnTop()))
++pos;
}
// 添加的第一个不是TopMost 窗口的后面
d_drawList.insert(pos, &wnd);
}
// 添加到窗口的前面,它一般是激活窗口显示在最上面
else
{
// 计算窗口需要被插入的位置
ChildList::reverse_iterator position = d_drawList.rbegin();
if (!wnd.isAlwaysOnTop())
{
// 找到最后一个非最高层窗口
while ((position != d_drawList.rend()) && ((*position)->isAlwaysOnTop()))
++position;
}
//添加窗口到列表中
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 56 -
d_drawList.insert(position.base(), &wnd);
}
}
d_drawList 越靠前的窗口越先被渲染,越先被渲染的窗口越容易被覆盖掉(后续渲染
覆盖先前渲染)。所以 d_drawList 添加到后面的窗口越靠近读者。addWindowToDrawList 用
来调整窗口的渲染次序也就是它的渲染层次。
CEGUI 0.6.0 版将窗口的逻辑和窗口的渲染分开,逻辑还是放在Window 类里面,渲染
放到了一种新的以WindowRender 为基类的窗口渲染类里面。我们从render 类就可以看出来,
如果窗口有渲染类会调用它的render 函数,否则才调用窗口自己的渲染缓冲函数来渲染。s
etWindowRenderer 函数为窗口设置一个渲染窗口类。
void Window::setWindowRenderer(const String& name)
{
WindowRendererManager& wrm = WindowRendererManager::getSingleton();
if (d_windowRenderer != 0)
{
// 容许改变一个窗口的渲染模块
if (d_windowRenderer->getName() == name)
{
return;
}
WindowEventArgs e(this);
onWindowRendererDetached(e);
wrm.destroyWindowRenderer(d_windowRenderer);
}
if (!name.empty())
{
//创建一个新的窗口渲染类,并且和窗口关联起来
d_windowRenderer = wrm.createWindowRenderer(name);
WindowEventArgs e(this);
onWindowRendererAttached(e);
}
else
{
//抛出异常
}
}
WindowRenderer* Window::getWindowRenderer(void) const
{
return d_windowRenderer;
}
void Window::onWindowRendererAttached(WindowEventArgs& e)
{
//忽略参数的检查,这个函数把一个WindowRender 和Window 联系起来
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 57 -
d_windowRenderer->d_window = this;
d_windowRenderer->onAttach();
fireEvent(EventWindowRendererAttached, e, EventNamespace);
}
//这个函数断开WindowRender 和Window 之间的联系
void Window::onWindowRendererDetached(WindowEventArgs& e)
{
d_windowRenderer->onDetach();
d_windowRenderer->d_window = 0;
fireEvent(EventWindowRendererDetached, e, EventNamespace);
}
和渲染相关的一个重要的函数就是setLookNFeel。它涉及到LooKNFeel 比较多,这里
只做简单介绍,更加详细的内容读者参考第6 章。
void Window::setLookNFeel(const String& look)
{
if (d_windowRenderer == 0)
{
//抛出异常,省略这部分代码
}
//获取外观管理器的单件实例
WidgetLookManager& wlMgr = WidgetLookManager::getSingleton();
//如果窗口有外观的定义,CEGUI 的实现允许重设窗口的外观,读者可以直接抛出异常
//来防止重设窗口外观的发生
if (!d_lookName.empty())
{
d_windowRenderer->onLookNFeelUnassigned();
const WidgetLookFeel& wlf = wlMgr.getWidgetLook(d_lookName);
wlf.cleanUpWidget(*this);
}
d_lookName = look;
// 调用外观的初始化函数对本窗口做一些处理
const WidgetLookFeel& wlf = wlMgr.getWidgetLook(look);
// 添加属性定义,应用属性以及创建子窗口等操作
wlf.initialiseWidget(*this);
// 作必要的绑定和初始化工作,Window 类提供空的默认实现,派生类可以修改
initialiseComponents();
// 通知渲染窗口外观定义添加到窗口
d_windowRenderer->onLookNFeelAssigned();
//请求重绘
requestRedraw();
}
窗口的外观影响的窗口的渲染,可以说一旦一个窗口的外观被定义后,这个窗口的渲染
出的大概样子也就确定了。详细的原理请读者参考第6 章。
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 58 -
3.2.5 事件响应与处理
窗口内部响应事件的函数,其实就是一些以on 开头的函数。这些函数一般是CEGUI
内部处理事件的函数,所以这些函数中一般都要激发一些事件,来调用外部的事件响应函数。
这些函数根据事件的不同,功能各异。但有个共同的特点是他们处理内部状态改变。
下面以onParentSized 为例介绍这组函数。
void Window::onParentSized(WindowEventArgs& e)
{
//设置自己的区域,并且传递false 表示不产生事件
setArea_impl(d_area.getPosition(), d_area.getSize(), false, false);
//计算窗口是否被移动或者尺寸是否改变
bool moved = ((d_area.d_min.d_x.d_scale != 0) || (d_area.d_min.d_y.d_scale != 0));
bool sized = ((d_area.d_max.d_x.d_scale != 0) || (d_area.d_max.d_y.d_scale != 0));
// 查看是否被移动,激发事件(外部处理)
if (moved)
{
WindowEventArgs args(this);
onMoved(args);
}
if (sized)
{
WindowEventArgs args(this);
onSized(args);
}
//如果窗口没有移动,且没有改变大小则调用子窗口布局函数
if (!(moved || sized))
performChildWindowLayout();
//激发外部事件
fireEvent(EventParentSized, e, EventNamespace);
}
//处理窗口移动的事件
void Window::onMoved(WindowEventArgs& e)
{
// 通知子窗口父窗口已经被移动了
const size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
d_children[i]->notifyScreenAreaChanged();
}
//这里没有调用requestRedraw 函数,这是通知系统重新提交每个窗口的缓冲图像
System::getSingleton().signalRedraw();
//激发外部处理函数
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 59 -
fireEvent(EventMoved, e, EventNamespace);
}
//这个函数只是设置以前计算的各种屏幕矩形不在有效,递归调用所有孩子对应函数
void Window::notifyScreenAreaChanged()
{
d_screenUnclippedRectValid = false;
d_screenUnclippedInnerRectValid = false;
d_screenRectValid = false;
d_screenInnerRectValid = false;
// 通知所有孩子需要更新内部保存的屏幕矩形的数据
const size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
d_children[i]->notifyScreenAreaChanged();
}
}
//响应窗口改变的事件,并激发外部事件
void Window::onSized(WindowEventArgs& e)
{
//通知子窗口,父窗口大小已经改变了
size_t child_count = getChildCount();
for (size_t i = 0; i < child_count; ++i)
{
WindowEventArgs args(this);
d_children[i]->onParentSized(args);
}
//重新子窗口布局,这个函数会调整窗口外观LooKNFeel 里定义的子窗口的位置
performChildWindowLayout();
//因为窗口的大小改变了,所以需要完全重绘,所以调用了这个函数
requestRedraw();
//激发外部事件
fireEvent(EventSized, e, EventNamespace);
}
类似的这种函数还很多,这里就不一一介绍了。读者只要理解他们是响应某些事件的函
数。比如窗口状态改变,操作状态改变(比如鼠标移入,移出),字体改变等等。这些改变
都有对应的事件处理函数。读者也可以根据自己需要添加类似的函数。
3.2.6 窗口状态
窗口有许多状态,比如是否激活,是否可以操作,是否可见,是否总是在最高层等等。
这里以setEnabled 和setVisible 为例。
void Window::setEnabled(bool setting)
{
// 仅仅当设置值和当前值不同时才设置
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 60 -
if (d_enabled != setting)
{
d_enabled = setting;
WindowEventArgs args(this);
if (d_enabled)
{
//判断一个窗口是否被可用,取决于它的祖先是否被激活
if ((d_parent && !d_parent->isDisabled()) || !d_parent)
onEnabled(args);
}
else
{
onDisabled(args);
}
}
}
//设置窗口的可见性
void Window::setVisible(bool setting)
{
//只有设置改变是才处理
if (d_visible != setting)
{
d_visible = setting;
WindowEventArgs args(this);
d_visible ? onShown(args) : onHidden(args);
}
}
3.2.7 窗口与输入系统
输入来自键盘或者鼠标,键盘输入Window 类的处理非常简单,只是简单的激发对应事
件,这是因为Window 是基类,没有涉及到具体的逻辑,这些处理留给了派生类。鼠标事件
基类需要提供处理,以便派生类使用。所以这里主要介绍鼠标输入相关的函数。
void Window::onMouseButtonDown(MouseEventArgs& e)
{
// 当鼠标单击的时候,不管是左键,中键还是右键,这是ToolTips 应该消失
Tooltip* tip = getTooltip();
if (tip)
{
tip->setTargetWindow(0);
}
//处理左键,执行doRiseOnClick 函数,默认只处理左键
if (e.button == LeftButton)
{
e.handled |= doRiseOnClick();
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 61 -
}
// 如果窗口容许自动重复,设置自动重复的状态,自动重复的状态下如果鼠标没有抬起
//则过一定的时间会自动产生鼠标单击的事件,调用鼠标本函数。请参考updateSelf。
if (d_autoRepeat)
{
if (d_repeatButton == NoButton)
captureInput();
if ((d_repeatButton != e.button) && isCapturedByThis())
{
d_repeatButton = e.button;
d_repeatElapsed = 0;
d_repeating = false;
}
}
//激发鼠标单击的事件
fireEvent(EventMouseButtonDown, e, EventNamespace);
}
void Window::onMouseButtonUp(MouseEventArgs& e)
{
// 重设自动重复产生事件的状态
if (d_autoRepeat && d_repeatButton != NoButton)
{
releaseInput();
d_repeatButton = NoButton;
}
//激发鼠标抬起的消息。
fireEvent(EventMouseButtonUp, e, EventNamespace);
}
captureInput 捕获鼠标,大家应该对他比较熟悉。当一个窗口捕获输入后,所有的输入
都会有捕获窗口的函数处理,直到该窗口释放了输入捕获。
鼠标进入窗口区域和鼠标离开窗口区域和鼠标点击抬起一样都是非常常用的函数,派生
类经常需要修改。
//鼠标进入窗口的处理函数
void Window::onMouseEnters(MouseEventArgs& e)
{
// 设置鼠标
MouseCursor::getSingleton().setImage(getMouseCursor());
// 控制ToolTip 显示自己的信息
Tooltip* tip = getTooltip();
if (tip)
{
tip->setTargetWindow(this);
}
//激发鼠标进入窗口的事件
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 62 -
fireEvent(EventMouseEnters, e, EventNamespace);
}
//鼠标离开窗口的处理函数
void Window::onMouseLeaves(MouseEventArgs& e)
{
// 关闭这个窗口的ToolTip 的显示
Tooltip* tip = getTooltip();
if (tip)
{
tip->setTargetWindow(0);
}
//激发鼠标离开的消息
fireEvent(EventMouseLeaves, e, EventNamespace);
}
//鼠标移动的消息处理函数
void Window::onMouseMove(MouseEventArgs& e)
{
// 设置ToolTips 状态
Tooltip* tip = getTooltip();
if (tip)
{
tip->resetTimer();
}
//激发鼠标移动的消息
fireEvent(EventMouseMove, e, EventNamespace);
}
这个几个函数是如何激发的,被谁调用的呢?是CEGUI 的System 类,关于它的详细说
明在第4 章。
3.2.8 窗口的其他功能
CEGUI 窗口基类中有一些名称含有XML 的函数,他们是负责将窗口写入到XML 流中
的,为了保存窗口到布局文件而设计的。他们虽然在游戏界面里没有什么用,但如果设计布
局编辑器他们就派上用场了。由于篇幅原因这里就不在介绍他们了。除此之外还有字体的相
关的函数,设置窗口文本的函数等。这些函数非常简单,就是简单的设置这些值,需要的话
激发一些事件。当时需要说明的是字体在CEGUI 的文字显示中非常重要。读者可以详细参
考第8 章的字体部分。
//设置窗口显示的文字
void Window::setText(const String& text)
{
d_text = text;
WindowEventArgs args(this);
onTextChanged(args);
}
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 63 -
//设置字体,通过指针
void Window::setFont(Font* font)
{
d_font = font;
WindowEventArgs args(this);
onFontChanged(args);
}
//通过字体的名称设置字体
void Window::setFont(const String& name)
{
if (name.empty())
{
setFont(0);
}
else
{
setFont(FontManager::getSingleton().getFont(name));
}
}
到这里CEGUI 基本窗口类Window 就介绍到这里,也许读者还有许多不理解的地方。
读者可以自己阅读更多的代码,加深理解。
3.3 窗口类厂和类厂管理
本节共有两小节,第1 节介绍窗口的类厂和类厂管理器,第2 节介绍渲染窗口的类厂以
及类厂管理器。与功能窗口不同的是渲染窗口的类厂管理器和渲染窗口管理器是同一个管理
器。
3.3.1 窗口的类厂和类厂管理
CEGUI 中窗口的创建和删除都是由类厂控制的,使用设计模式中的类厂的概念来设计
的。CEGUI 还普遍使用了另一个设计模式就是单件模式。大多数管理器都是单件,它们的
特征就是有个getSingleton 的函数。
CEGUI 的类厂是使用三个宏以及一个工厂基类来创建的,三个宏是CEGUI_DECLARE
_WINDOW_FACTORY,CEGUI_DEFINE_WINDOW_FACTORY 以及CEGUI_WINDOW_F
ACTORY。一个基类便是WindowFactory,他们全部定义在CEGUIWindowFactory.h 和.cpp
中。
首先介绍所有类厂的基类WindowFactory。
class CEGUIEXPORT WindowFactory
{
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 64 -
public:
//删除一个窗口,接口函数强制子类实现
virtual void destroyWindow(Window* window) = 0;
//返回这个类厂的类型,也就是它创建的窗口的类型
const String& getTypeName(void) const {return d_type;}
protected:
//构造函数保护
WindowFactory(const String& type) : d_type(type) {}
public:
// 兼容luabind
virtual ~WindowFactory(void) {}
protected:
//保存类型的成员变量
String d_type;
};
哪么CEGUI_DECLARE_WINDOW_FACTORY 宏是如何声明一个类厂的呢。
#define CEGUI_DECLARE_WINDOW_FACTORY( T )\
class T ## Factory : public WindowFactory\
{\
public:\
T ## Factory() : WindowFactory( T::WidgetTypeName ) {}\
Window* createWindow(const String& name)\
{\
return new T (d_type, name);\
}\
void destroyWindow(Window* window)\
{\
delete window;\
}\
};\
T ## Factory& get ## T ## Factory();
c++在宏展开的时候##会作为连接操作,比如T 传入GUISheet 哪么就会定义一个GUI
SheetFactory 的类厂。\表示下一行也是宏的展开,只不过为了方便阅读换行的。这下读者应
该明白它展开后的样子了。为了直观一些我们以Window 为例,展开这个宏。
class GUISheetFactory : public WindowFactory
{
public:
GUISheetFactory() : WindowFactory( GUISheet::WidgetTypeName ) {}
Window* createWindow(const String& name)
{
return new GUISheet(d_type, name);
}
void destroyWindow(Window* window)
{
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 65 -
delete window;
}
};
GUISheetFactory& getWindowFactory();
getWindowFactory 是全局函数,为了方便类注册而定义的。WidgetTypeName 是每个C
EGUI 窗口类定义的窗口的类型,它是静态变量。
类厂声明好了后还有一个全局函数没有定义,所以需要实现它。CEGUI_DEFINE_WIN
DOW_FACTORY 完成这个功能。
#define CEGUI_DEFINE_WINDOW_FACTORY( T )\
T ## Factory& get ## T ## Factory()\
{\
static T ## Factory s_factory;\
return s_factory;\
}
它的实现一目了然,这里就不在带入实例了。
还有一个为了方便注册类厂的宏,它就是CEGUI_WINDOW_FACTORY。它调用全局
的获取类厂的函数。
#define CEGUI_WINDOW_FACTORY( T ) (get ## T ## Factory())
显而易见窗口的创建和删除是由类厂的创建和删除函数实现的,创建简单的new 一个
新的对象,删除delete 这个对象。
Window* createWindow(const String& name)
{
return new Window (d_type, name);
}
void destroyWindow(Window* window)
{
delete window;
}
类厂的好处不言而喻,它具有非常好的扩展性。创建和删除一种类型的对象不需要知道
这个对象的细节,只需提供对象的类型,就可以窗口对象。而且可以通过在其他模块里注册
新的类厂,各个模块里可以共享这些对象。它的好处还有很多这里就不在介绍了。有兴趣的
读者可以参考设计模式相关的书籍。
CEGUI 所有的类厂的注册和实现分别在CEGUIBaseFactories.h 和CEGUIBaseFactories.
cp 文件中。这里简单列出如何定义和实现一个类厂。(省略的许多定义)
namespace CEGUI
{
CEGUI_DEFINE_WINDOW_FACTORY(GUISheet);
CEGUI_DEFINE_WINDOW_FACTORY(DragContainer);
}
类厂的声明,在CEGUIBaseFactories.h 中。
namespace CEGUI
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 66 -
{
CEGUI_DECLARE_WINDOW_FACTORY(GUISheet);
CEGUI_DECLARE_WINDOW_FACTORY(DragContainer);
}
是不是非常简单呢。有了这些宏,实现的类厂即美观有简单。哪么另外一个宏用在那里
呢?答案是WindowFactoryManager。
显然类厂需要一个管理器,来统一管理它。CEGUI 使用WindowFactoryManager 类来管
理。它是一个单件类,显然CEGUI 中所有的管理器都是单件了,以后就不做特殊说明了。
所有的单件类都是使用Singleton 模板来实现的。它的具体实现就不做讨论了,相信许多设
计模式的书籍都有类似的定义。类厂管理器必须有个类厂名称到类厂指针的映射集,才能方
便的通过名称找到类厂。一个窗口的(也可以称作类厂的)名称到窗口内部类型和窗口渲染
窗口,以及窗口外观的映射。具体如下所示:
//字符串到类工厂指针的映射类型定义
typedef std::map<String, WindowFactory*, String::FastLessCompare> WindowFactoryRegistry;
//定义一种窗口类型别名的映射,就是给窗口类型起个别名,方便读者理解窗口功能
typedef std::map<String, AliasTargetStack, String::FastLessCompare> TypeAliasRegistry;
//定义标准窗口映射类型的映射定义
typedef std::map<String, FalagardWindowMapping, String::FastLessCompare>
FalagardMapRegistry;
//定义的三种映射变量
WindowFactoryRegistry d_factoryRegistry;
TypeAliasRegistry d_aliasRegistry;
FalagardMapRegistry d_falagardRegistry;
想必读者还是不清楚d_aliasRegistry,以及d_falagardRegistry 究竟是什么含义。类Alia
sTargetStack 其实就是一个String 类的数组。它的数据成员定义如下:
//定义String 数组类型
typedef std::vector<String> TargetTypeStack;
//定义数组类型变量
TargetTypeStack d_targetStack;
TypeAliasRegistry 定义了一个字符串到一个字符串数组的映射。alias 在英文里是别名的
意思。读者可以想象d_aliasRegistry 是一个窗口类型别名到窗口类型的映射,这种映射是一
对多的,也就是说一个别名可以对应多个实际窗口类型。给窗口类型起别名主要是为了方便
理解这类窗口是做什么的。但一个别名对应多个实际类型,就不知是为何设计的了。我认为
这个设计完全没有必要,d_aliasRegistry 没有什么太大的意义,没有它程序一样可以非常顺
利的跑起来,所以读者可以忽略它,我们也不在介绍它相关的几个函数了。
d_falagardRegistry 非常重要的一个成员,它定义了窗口类型(创建窗口时使用,外部的
窗口类型)到内部窗口类型(具体那个类,也就是前文所说的WidgetTypeName,它是窗口
类的静态成员,也是类厂的类型),内部窗口渲染类型,以及窗口外观定义的映射。下面这
个结构FalagardWindowMapping 定义了这种映射关系。
struct CEGUIEXPORT FalagardWindowMapping
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 67 -
{
//外部的窗口类型名称
String d_windowType;
//窗口外观名称,对应LooKNfeel 文件中定义的外观名称
String d_lookName;
//窗口内部类型名称,对应窗口类的静态变量WidgetTypeName
String d_baseType;
//渲染窗口的类型名称,用来创建渲染窗口
String d_rendererType;
};
有了数据成员必须要有成员函数来操作这些成员变量,下面介绍三个和这个成员相关的
重要函数。
//添加一个映射到d_falagardRegistry
void WindowFactoryManager::addFalagardWindowMapping(const String& newType, const
String& targetType, const String& lookName, const String& renderer)
{
WindowFactoryManager::FalagardWindowMapping mapping;
mapping.d_windowType = newType;
mapping.d_baseType = targetType;
mapping.d_lookName = lookName;
mapping.d_rendererType = renderer;
//查找是否已经定义了这个名字的类型映射
if (d_falagardRegistry.find(newType) != d_falagardRegistry.end())
{
//这里原本记录了日志,这里省略
}
//添加到映射列表中
d_falagardRegistry[newType] = mapping;
}
//根据外部窗口类型,查找LooKNFeel 的类型
const String& WindowFactoryManager::getMappedLookForType(const String& type) const
{
FalagardMapRegistry::const_iterator iter =
d_falagardRegistry.find(getDereferencedAliasType(type));
if (iter != d_falagardRegistry.end())
{
return (*iter).second.d_lookName;
}
// 没有找到,抛出异常
else
{
throw InvalidRequestException("WindowFactoryManager::getMappedLookForType -
Window factory type '" + type + "' is not a falagard mapped type (or an alias for one).");
}
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 68 -
}
//根据外部窗口类型名称找到窗口的渲染类型
const String& WindowFactoryManager::getMappedRendererForType(const String& type) const
{
FalagardMapRegistry::const_iterator iter =
d_falagardRegistry.find(getDereferencedAliasType(type));
if (iter != d_falagardRegistry.end())
{
return (*iter).second.d_rendererType;
}
//没有找到抛出异常
else
{
throw InvalidRequestException("WindowFactoryManager::getMappedLookForType -
Window factory type '" + type + "' is not a falagard mapped type (or an alias for one).");
}
}
//这个函数就是别名的函数之一,它递归查找别名对应的窗口类型(外部),为什么要递归呀
//读者可以思考,当没有找到别名的时候,说明没有别名,直接返回原来的类型
String WindowFactoryManager::getDereferencedAliasType(const String& type) const
{
TypeAliasRegistry::const_iterator alias = d_aliasRegistry.find(type);
// 这里递归查找,直到找到窗口类型的名称
if (alias != d_aliasRegistry.end())
return getDereferencedAliasType(alias->second.getActiveTarget());
// 找不到别名的时候返回传入的参数
return type;
}
由getDereferencedAliasType 查找不到别名的时候会返回传入的类型可以理解getMappe
dRendererForType 和getMappedLookForType 的调用原理了。也就是说没有别名的时候,还
是会正常工作,只要窗口外部类型名称的FalagardWindowMapping 存在。
读者肯定想知道这些类型定义在那里,谁负责加载他们,这些内容在第4 章的Schema
相关的章节。读者不用着急,一步一步,扎扎实实的跟着教程走,肯定会有很大收获的。
最后一个成员变量d_factoryRegistry,它保存了类厂类型(也是窗口内部类型)到类厂
指针的映射。操作它的成员函数有添加类厂,删除类厂,查找类厂,和是否存在判断四种。
void WindowFactoryManager::addFactory(WindowFactory* factory)
{
// 省略异常判断参数检查以及日志
// 使用类厂名称(和窗口类型名称相同)映射工厂指针
d_factoryRegistry[factory->getTypeName()] = factory;
}
void WindowFactoryManager::removeFactory(const String& name)
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 69 -
{
//移除工厂
d_factoryRegistry.erase(name);
}
//另一种形式
void WindowFactoryManager::removeFactory(WindowFactory* factory)
{
if (factory)
{
removeFactory(factory->getTypeName());
}
}
//根据类厂名称获取类厂的指针
WindowFactory* WindowFactoryManager::getFactory(const String& type) const
{
// 通过别名查找,这个函数已经介绍过
String targetType(getDereferencedAliasType(type));
//首先尝试别名查找类厂指针
WindowFactoryRegistry::const_iterator pos = d_factoryRegistry.find(targetType);
// 找到后返回
if (pos != d_factoryRegistry.end())
{
return pos->second;
}
//没有找到,尝试在映射中寻找
else
{
FalagardMapRegistry::const_iterator falagard = d_falagardRegistry.find(targetType);
// 找到后,递归调用,通过别名系统的过滤
if (falagard != d_falagardRegistry.end())
{
return getFactory(falagard->second.d_baseType);
}
// 没有找到抛出异常,省略代码
else
{
}
}
}
//判断一个类厂是否存在
bool WindowFactoryManager::isFactoryPresent(const String& name) const
{
// 通过别名找到内部窗口类型,d_factoryRegistry 保存的是内部窗口类型到类厂的映射
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 70 -
String targetType(getDereferencedAliasType(name));
// 尝试查找
if (d_factoryRegistry.find(targetType) != d_factoryRegistry.end())
{
return true;
}
// 否则在d_falagardRegistry.中查找
else
{
return (d_falagardRegistry.find(targetType) != d_falagardRegistry.end());
}
}
有了类厂的这些知识,读者应该明白如何如何创建和删除一个窗口了。与窗口类似,渲
染窗口也有其类厂和类厂的管理类。图3-3 列出了窗口和窗口类厂之间的关系。希望可以加
深读者的理解。这里只是那Eidt,Button,Tree 三个基本窗口来举例子。并不代表只有这三
个基本窗口,实际上所有的窗口类都有一个窗口类工厂,负责他的创建和销毁。读者如果需
要添加一个新的窗口,也需要添加它的类厂。不过类厂的添加不需要太多的代码,使用我们
介绍过的三个宏就可以轻松解决。
下一节介绍渲染窗口的类厂以及类厂管理器。
CEGUI 深入解析 第3 章 CEGUI 基类的实现

3.3.2 渲染窗口的类厂和类厂管理
渲染窗口,读者可能还不是非常明白什么是渲染窗口。它是负责窗口的渲染的,记得在
3.2 节中介绍的drawSelf 函数吗?如果窗口中存在渲染窗口的指针则调用渲染窗口的render
函数,否则调用Window 提供的populateRenderCache 函数。可见渲染窗口是负责渲染工作
的。3.3.1 中介绍的结构FalagardWindowMapping 中d_rendererType 就是渲染窗口类厂或者渲
染窗口的类型名称。与普通窗口最大的不同是渲染窗口是以模块的形式提供的(编译静态库
时例外)。它作为CEGUI 的渲染子集出现,之所以要这样设计是因为渲染窗口是可以对应多
个普通窗口的,也就是说一个渲染窗口可以负责多个窗口的渲染。但不是说每一个渲染窗口
都可以渲染任意一个窗口。只要窗口的LooKNFeel 定义了渲染窗口需要的区域和命名对象
等,渲染窗口就可以渲染这个窗口。也许比较抽象,读者不好理解,这是因为读者还不了解
LooKNFeel 的缘故。LooKFeel 将在第6 章详细介绍。
渲染窗口的类厂也是使用几个宏实现的。具体定义如下:
// 定义一个模块,我们说过渲染窗口使用模块来提供
#define CEGUI_DECLARE_WR_MODULE( moduleName )\
\
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 72 -
class CEGUI::WindowRendererFactory;\
\
//这两个函数是导出函数,供外部的程序调用注册单个和多个模块内部的各种渲染窗口类
extern "C" CEGUIWRMODULE_API void registerFactoryFunction(const CEGUI::String&
type_name);\
extern "C" CEGUIWRMODULE_API CEGUI::uint registerAllFactoriesFunction(void);\
void doSafeFactoryRegistration(CEGUI::WindowRendererFactory* factory);
// 类厂定义的宏,相信有了第1 节的知识读者可以把它还原回来分析了
#define CEGUI_DEFINE_WR_FACTORY( className )\
namespace CEGUI {\
class className ## WRFactory : public WindowRendererFactory\
{\
public:\
className ## WRFactory(void) : WindowRendererFactory(className::TypeName) { }\
WindowRenderer* create(void)\
{ return new className(className::TypeName); }\
void destroy(WindowRenderer* wr)\
{ delete wr; }\
};\
}\
static CEGUI::className ## WRFactory s_ ## className ## WRFactory;
// 开始类厂映射,是不是有点类似MFC 呢?
#define CEGUI_START_WR_FACTORY_MAP( module )\
struct module ## WRMapEntry\
{\
const CEGUI::utf8* d_name;\
CEGUI::WindowRendererFactory* d_factory;\
};\
\
module ## WRMapEntry module ## WRFactoriesMap[] =\
{\
// 工厂映射的结束宏
#define CEGUI_END_WR_FACTORY_MAP {0,0}};
// 映射的入口,定义单独的一个映射
#define CEGUI_WR_FACTORY_MAP_ENTRY( class )\
{CEGUI::class::TypeName, &s_ ## class ## WRFactory},
// 定义模块,实现相应的导出函数
#define CEGUI_DEFINE_WR_MODULE( module )\
void registerFactoryFunction(const CEGUI::String& type_name)\
{\
module ## WRMapEntry* entry = module ## WRFactoriesMap;\
while (entry->d_name)\
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 73 -
{\
if (entry->d_name == type_name)\
{\
doSafeFactoryRegistration(entry->d_factory);\
return;\
}\
++entry;\
}\
\
throw CEGUI::UnknownObjectException("::registerFactory - The window renderer factory f
or type '" + type_name + "' is not known in this module.");\
}\
\
extern "C" CEGUI::uint registerAllFactoriesFunction(void)\
{\
CEGUI::uint count = 0;\
module ## WRMapEntry* entry = module ## WRFactoriesMap;\
while (entry->d_name)\
{\
doSafeFactoryRegistration(entry->d_factory);\
++entry;\
++count;\
}\
return count;\
}\
\
void doSafeFactoryRegistration(CEGUI::WindowRendererFactory* factory)\
{\
assert(factory != 0);\
\
CEGUI::WindowRendererManager& wfm = CEGUI::WindowRendererManager::getSingleto
n();\
if (wfm.isFactoryPresent(factory->getName()))\
{\
CEGUI::Logger::getSingleton().logEvent(\
"WindowRenderer factory '" + factory->getName() + "' appears to be already regist
ered, skipping.",\
CEGUI::Informative);\
}\
else\
{\
wfm.addFactory(factory);\
}\
}
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 74 -
registerFactoryFunction 和registerAllFactoriesFunction 一般会被CEGUI 调用,当CEGUI
加载模式的时候会查找所有的外部接口,加载他们然后调用这两个函数(不一定都调用)。
这两个函数的功能是完成模块(Window 下是Dll 模块,Linux 下是so 模块)内部所有的窗
口类的类厂注册工作。渲染窗口和窗口一样,都是只有类厂可以创建和删除一个。需要注意
的是由于排版的缘故,发生了一些串行的事情。这几个宏构造了一个静态的数组然后将通过
对数组的扫描注册数组中的每一个类厂。实现比较简单,很类似MFC 中提供的宏。
哪么这些宏如何使用呢?首先需要在头文件中声明一个模块,通过CEGUI_DECLARE
_WR_MODULE 宏生成一个模块导出函数的声明。然后在实现文件中调用CEGUI_DEFINE
_WR_MODULE 宏实现上面声明的几个函数。函数已经完成了声明但是类厂的数组还没有
定义。定义一个类厂通过CEGUI_DEFINE_WR_FACTORY,数组以CEGUI_START_WR_F
ACTORY_MAP 开始,以CEGUI_END_WR_FACTORY_MAP 收尾,中间还有许多填写数组
的宏CEGUI_WR_FACTORY_MAP_ENTRY。具体实现读者参考FalModule 头文件和实现文
件。
渲染窗口的管理器和渲染窗口类工厂管理器是一个管理器。它就是WindowRendererMa
nager。这个类负责管理类工厂以及渲染窗口。它有唯一的成员变量就是类厂的名称到类厂
指针的映射。可以发现这种映射在CEGUI 中使用非常广泛。
//定义类厂映射类型
typedef std::map<String, WindowRendererFactory*, String::FastLessCompare> WR_Registry;
//定义成员变量
WR_Registry d_wrReg;
WindowRendererManager 提供了六个主要的函数。四个负责管理类厂,两个负责管理渲
染窗口。所谓管理无非就是添加,删除,存在判断,获取等几项。
//存在判断函数
bool WindowRendererManager::isFactoryPresent(const String& name) const
{
return (d_wrReg.find(name) != d_wrReg.end());
}
//获取类厂函数
WindowRendererFactory* WindowRendererManager::getFactory(const String& name) const
{
WR_Registry::const_iterator i = d_wrReg.find(name);
if (i != d_wrReg.end())
{
return (*i).second;
}
throw UnknownObjectException("There is no WindowRendererFactory named '"+name+"'
available");
}
//添加一个新的类厂函数
void WindowRendererManager::addFactory(WindowRendererFactory* wr)
{
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 75 -
if (wr == 0)
{
return;
}
if (d_wrReg.insert(std::make_pair(wr->getName(), wr)).second == false)
{
throw AlreadyExistsException("A WindowRendererFactory named '"+wr->getName()+"
' already exist");
}
Logger::getSingleton().logEvent("WindowRendererFactory '"+wr->getName()+"' added.");
}
//删除类厂的函数
void WindowRendererManager::removeFactory(const String& name)
{
d_wrReg.erase(name);
}
添加类厂的函数,上面介绍的模块导出宏中定义的doSafeFactoryRegistration 函数调用。
总之读者如果希望管理渲染窗口的类厂,就可以调用这个单件的这四个成员函数。下面是创
建和销毁渲染窗口的两个渲染窗口函数。他们的实现也非常简单。
//创建一个渲染窗口,先找到类厂,然后通过类厂创建
WindowRenderer* WindowRendererManager::createWindowRenderer(const String& name)
{
WindowRendererFactory* factory = getFactory(name);
return factory->create();
}
//销毁一个渲染窗口,先找到类厂,然后通过类厂销毁
void WindowRendererManager::destroyWindowRenderer(WindowRenderer* wr)
{
WindowRendererFactory* factory = getFactory(wr->getName());
factory->destroy(wr);
}
这两个函数没有检查是否能获取渲染窗口的类厂,如果名称不对无法获取渲染窗口的类
厂会导致程序崩溃,使用的时候需要注意。
本节介绍了窗口的类厂和渲染窗口的类厂以及渲染窗口的管理。下一节详细介绍窗口的
管理类。
3.4 窗口管理系统
CEGUI 提供许多种窗口,而且每种窗口都有许多的实例,显然需要一个管理类来管理
这些窗口。这个管理类就是WindowManager,它是个单件类,这个系统只有一个。
这个类的主要数据成员如下所示:
//定义用来实现窗口注册的类,通过窗口名称来映射窗口的指针
typedef std::map<String, Window*, String::FastLessCompare> WindowRegistry;
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 76 -
//定义保存窗口指针的数组
typedef std::vector<Window*> WindowVector;
//定义窗口注册的成员变量
WindowRegistry d_windowRegistry;
//窗口的指针数组
WindowVector d_deathrow;
//定义自动产生名称用到的自增计数器
unsigned long d_uid_counter;
//默认资源组的名称
static String d_defaultResourceGroup;
每个成员的含义已经说明,相信读者完全可以理解。下面介绍重要的成员函数。首先肯
定是创建一个窗口了。创建窗口只需要它的名称和窗口类型,以及窗口名称的前缀。
Window* WindowManager::createWindow( const String& type, const String& name /*= ""*/,
const String& prefix /*= ""*/ )
{
// 确保不是空的名称传递给类厂创建
String finalName(prefix + name);
if (finalName.empty())
{
//产生唯一的窗口名称,使用它来确保窗口名称的唯一性
finalName = generateUniqueWindowName();
}
//如果窗口已经存在则抛出异常
if (isWindowPresent(finalName))
{
throw AlreadyExistsException("WindowManager::createWindow - A Window object
wit h the name '" + finalName +"' already exists within the system.");
}
//获取类厂管理器,根据类型获取对应类厂
WindowFactoryManager& wfMgr = WindowFactoryManager::getSingleton();
WindowFactory* factory = wfMgr.getFactory(type);
//调用类厂的创建窗口函数,new 一个新的窗口
Window* newWindow = factory->createWindow(finalName);
newWindow->setPrefix(prefix);
// 查看是否需要映射一个外观的定义
if (wfMgr.isFalagardMappedType(type))
{
//获取窗口的外部名称到内部名称,外观定义,以及渲染窗口定义映射
const WindowFactoryManager::FalagardWindowMapping& fwm = wfMgr.getFalagard
MappingForType(type);
//这是一个有外观的定义的窗口,应用窗口的外观定义以及渲染窗口
newWindow->d_falagardType = type;
newWindow->setWindowRenderer(fwm.d_rendererType);
newWindow->setLookNFeel(fwm.d_lookName);
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 77 -
}
//保存到窗口的注册映射集中,并且返回
d_windowRegistry[finalName] = newWindow;
return newWindow;
}
其次,删除一个窗口。
void WindowManager::destroyWindow(const String& window)
{
//查找窗口,在窗口名称到窗口指针的映射中
WindowRegistry::iterator wndpos = d_windowRegistry.find(window);
//窗口找到了
if (wndpos != d_windowRegistry.end())
{
Window* wnd = wndpos->second;
//从映射中移除
d_windowRegistry.erase(wndpos);
// 窗口内部清理
wnd->destroy();
// 添加窗口的死亡窗口数组中,CEGUI 会每帧清理这个数组
d_deathrow.push_back(wnd);
// 通知系统一个窗口被销毁了,系统修改自己的状态
System::getSingleton().notifyWindowDestroyed(wnd);
}
}
//调用destroyWindow 函数并没调用类厂来销毁窗口,只做了窗口的内部清理,真正的清理
//在cleanDeadPool 函数中
void WindowManager::cleanDeadPool(void)
{
WindowVector::reverse_iterator curr = d_deathrow.rbegin();
for (; curr != d_deathrow.rend(); ++curr)
{
//获取类厂指针,然后删除窗口
WindowFactory* factory = WindowFactoryManager::getSingleton().getFactory((*curr)
->getType());
factory->destroyWindow(*curr);
}
// 清空死亡窗口数组
d_deathrow.clear();
}
第三,加载布局文件的函数,我们知道CEGUI 创建窗口可以通过XML 格式的布局文
件。所以通过加载布局文件可以生成一些窗口,这个函数返回他们的根窗口。
//filename 是布局文件的路径。name_prefix 是创建窗口时的前缀,设计前缀是为了方便布局
//文件的导入,就像C++中的#include 命令一样。resourceGroup 是资源组,资源提供模块根
//据资源组来加载资源,callback 是遇到窗口属性时的回调函数,userdata 是callback 的参数
CEGUI 深入解析 第3 章 CEGUI 基类的实现
- 78 -
Window* WindowManager::loadWindowLayout(const String& filename, const String&
name_prefix, const String& resourceGroup, PropertyCallback* callback, void* userdata)
{
if (filename.empty())
{
//抛出异常,这里省略
}
//创建布局文件的处理类,2.3 节详细介绍了布局文件的加载
GUILayout_xmlHandler handler(name_prefix, callback, userdata);
//分析布局文件handler 具体处理每个元素,创建窗口,窗口事件和窗口属性等
try
{
System::getSingleton().getXMLParser()->parseXMLFile(handler,
filename, GUILayoutSchemaName, resourceGroup.empty() ? d_defaultResourceGr
oup : resourceGroup);
}
catch(...)
{
Logger::getSingleton().logEvent("WindowManager::loadWindowLayout - loading of la
yout from file '" + filename +"' failed.", Errors);
throw;
}
//返回根窗口的指针
return handler.getLayoutRootWindow();
}
这个函数返回了加载的布局文件的根窗口的指针。它是布局文件中所有窗口的直接或间
接父窗口。
第四,窗口的获取和窗口重命名,窗口是否存在,输出一个窗口的布局流到指定的流中。
这几个函数比较简单,这里就不做介绍。
3.5 本章小结
1.还原渲染窗口的注册类厂的宏,并且理解它们是如何工作的。
2.尝试着给窗口添加一个新的属性表示是否激发渲染事件,如果这个属性值是True 则
激发否则不激发事件。
3.尝试着注册一个窗口单击事件的处理函数,并在处理函数里弹出一个对话框。
看懂了,不代表真的懂了。自己动手非常重要。虽然本章并没有第2 和第3 题的例子,
但读者可以思考如何如何实现,如果实在实现不了也不要气馁。后续章节会有例子。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值