嵌牛导读:写了这么久,终于接触到UI了。在我的印象中UI的因果逻辑我完全不懂。在《跟我一起学C++第三季》中接触了一个非常简陋的UI框架,然后结合最近学习wxWidgets的经验,所以想纯靠伪代码写一下UI框架干的事情。
嵌牛鼻子:UI框架
嵌牛提问:UI框架window之间的通信如何进行的
嵌牛正文:
事件驱动
本UI是事件驱动模型。什么是事件驱动模型呢?实际上是比较抽象的。其实就和Reactor模式一样。当我们移动鼠标,或者点击鼠标,键盘输入。就会产生一个事件。我们针对这些事件写好对应的eventHandler。当事件一个一个发生的时候,eventHandler就一个一个调用。所以是没有涉及多线程,因此如果某个eventHandler比较耗时,就会导致我们感觉起来很卡。实际上就是因为其它操作还在排队,并没有被调用。
Application 代表运行的程序,其逻辑很简单。
while(1)
{
// 当有事件到来的时候被唤醒,
eventID = getEventID();
switch(eventID)
{
case ButtonClicked:
eventHandler();
break;
case ...
}
}
Application的实现一般为单例模式,因为运行的程序只有一个。
App单例的实现
WindowBase
我们需要将图形绘制到屏幕上。而屏幕是由一个一个像素点构成的。我们绘图的过程实际上就是向这些像素点填充的一个过程。有点小孩子在方格上填充色彩的感觉。
ScreenBuffer
因此我们就需要抽象出一个二维数组。用来代表图形上的像素。ScreenBuffer,其是WindowBase的嵌套类。
图形的表示
我们在坐标轴上确定一个矩形需要俩个东西。
图形的起始坐标。——Position
图形的宽度和高度。——Size
这样我们就知道了我们的图形是要画在哪个位置。
举个栗子:
比如说我们当前的画布是高25。宽80。由25 * 80个像素点构成我们的画布。
当我们想要画一条线的时候。我们给定起始点(0,4)。
for(int i = 0; i < 80; ++i)
buffer[4][i] = '-'
这样我们就在buffer中画出了一条线。而buffer常常也被我们称为逻辑屏幕。如果需要其呈现到实际屏幕上,只需要调用相关的接口(暂定为refresh)将buffer中的数据刷新到屏幕上即完成绘图。
便利的接口
我们就可以提供一些便利的接口给派生类。
DrawHLine(startX, startY, length);
DrawVLine(startX, startY, length);
FillReact(startX, startY, width, length);
Write(char)
Write(string)
refresh()
Window
Window类用来抽象窗口。我们看到的一个Button,一个Label,一个子窗口都是窗口。Window是继承自WindowBase的。因为每个Window都需要自己的图形。所有都会有自己的Draw方法。
可能的派生类
Button
Label
…
继承Window的子类有很多。它们之间的最大区别在于绘制自己的图形方式不同。从而呈现出不同的样子。
Window标识
对于不同的Window或者说控件来说。我们都需要一个标识。标识主要是用来关联eventHandler的。
比如说。
在Exit和About这俩个Button上。我们发生ButtonClicked事件的eventHandler肯定是不同的。但事件类型时一样的。
所以我们的回调函数不仅仅需要确定发生事件的类型,同时还需要确定发生事件的Window。
Window之间的关系
以一个一般的窗口为例。我们总是有一个root窗口。这个窗口代表着这个程序,关闭这个窗口的同时也会导致程序的退出。
root窗口下有很多子窗口。子窗口下又有很多子窗口。关闭一个窗口会导致所有子窗口的关闭。不难明白窗口之间的关系是呈现树状关系。
Window之间的通信
这里直接举了wxWidget上的例子。
可以看到我们总共有5个Window。
TotalWindow
LeftWindow
PlusWindow(实际上是PlusButton,Button类继承自Window)
MinusWindow
RightWindow
当我们点击+号的时候,我们希望右侧的窗口的数值+1。这该怎么通信呢?
我的第一反应是去做一个eventfd。当+点击的时候,就向eventfd写一个char。然后RightPanel注册了一个事件。但是想想就觉的麻烦。
实际上我们可以直接利用树装关系。通过Parent来获得RightPanel。
大致的伪代码思路如下。
LeftPanel::OnPlus()
{
count++;
p = getParent();
p->rPanel->text = count;
p->rPanel->refresh();
}
Event
事件类。我觉的是最好写的。
eventID用来标识事件的类型。
eventHandler事件的回调函数要准备好。
因为事件触发的时候,大部分时候会重绘Window,所以我们需要让事件保留Window的指针。
总体关系如下:
一个RootWindow可以有无数的childWindow。每个Window可以绑定无数个事件。每个事件只能关联到一个Window。
————————————————