嵌牛導(dǎo)讀:寫了這么久,終于接觸到UI了。在我的印象中UI的因果邏輯我完全不懂。在《跟我一起學(xué)C++第三季》中接觸了一個非常簡陋的UI框架,然后結(jié)合最近學(xué)習(xí)wxWidgets的經(jīng)驗(yàn),所以想純靠偽代碼寫一下UI框架干的事情。
嵌牛鼻子:UI框架
嵌牛提問:UI框架window之間的通信如何進(jìn)行的
嵌牛正文:
事件驅(qū)動
本UI是事件驅(qū)動模型。什么是事件驅(qū)動模型呢?實(shí)際上是比較抽象的。其實(shí)就和Reactor模式一樣。當(dāng)我們移動鼠標(biāo),或者點(diǎn)擊鼠標(biāo),鍵盤輸入。就會產(chǎn)生一個事件。我們針對這些事件寫好對應(yīng)的eventHandler。當(dāng)事件一個一個發(fā)生的時候,eventHandler就一個一個調(diào)用。所以是沒有涉及多線程,因此如果某個eventHandler比較耗時,就會導(dǎo)致我們感覺起來很卡。實(shí)際上就是因?yàn)槠渌僮鬟€在排隊(duì),并沒有被調(diào)用。
Application 代表運(yùn)行的程序,其邏輯很簡單。
while(1)
{
? ? // 當(dāng)有事件到來的時候被喚醒,
? ? eventID = getEventID();
? ? switch(eventID)
? ? {
? ? case ButtonClicked:
? ? ? ? eventHandler();
? ? break;
? ? case ...
? ? }
}
Application的實(shí)現(xiàn)一般為單例模式,因?yàn)檫\(yùn)行的程序只有一個。
App單例的實(shí)現(xiàn)
WindowBase
我們需要將圖形繪制到屏幕上。而屏幕是由一個一個像素點(diǎn)構(gòu)成的。我們繪圖的過程實(shí)際上就是向這些像素點(diǎn)填充的一個過程。有點(diǎn)小孩子在方格上填充色彩的感覺。
ScreenBuffer
因此我們就需要抽象出一個二維數(shù)組。用來代表圖形上的像素。ScreenBuffer,其是WindowBase的嵌套類。
圖形的表示
我們在坐標(biāo)軸上確定一個矩形需要倆個東西。
圖形的起始坐標(biāo)?!狿osition
圖形的寬度和高度?!猄ize
這樣我們就知道了我們的圖形是要畫在哪個位置。
舉個栗子:
比如說我們當(dāng)前的畫布是高25。寬80。由25 * 80個像素點(diǎn)構(gòu)成我們的畫布。
當(dāng)我們想要畫一條線的時候。我們給定起始點(diǎn)(0,4)。
for(int i = 0; i < 80; ++i)
? ? buffer[4][i] = '-'
這樣我們就在buffer中畫出了一條線。而buffer常常也被我們稱為邏輯屏幕。如果需要其呈現(xiàn)到實(shí)際屏幕上,只需要調(diào)用相關(guān)的接口(暫定為refresh)將buffer中的數(shù)據(jù)刷新到屏幕上即完成繪圖。
便利的接口
我們就可以提供一些便利的接口給派生類。
DrawHLine(startX, startY, length);
DrawVLine(startX, startY, length);
FillReact(startX, startY, width, length);
Write(char)
Write(string)
refresh()
Window
Window類用來抽象窗口。我們看到的一個Button,一個Label,一個子窗口都是窗口。Window是繼承自WindowBase的。因?yàn)槊總€Window都需要自己的圖形。所有都會有自己的Draw方法。
可能的派生類
Button
Label
…
繼承Window的子類有很多。它們之間的最大區(qū)別在于繪制自己的圖形方式不同。從而呈現(xiàn)出不同的樣子。
Window標(biāo)識
對于不同的Window或者說控件來說。我們都需要一個標(biāo)識。標(biāo)識主要是用來關(guān)聯(lián)eventHandler的。
比如說。
在Exit和About這倆個Button上。我們發(fā)生ButtonClicked事件的eventHandler肯定是不同的。但事件類型時一樣的。
所以我們的回調(diào)函數(shù)不僅僅需要確定發(fā)生事件的類型,同時還需要確定發(fā)生事件的Window。
Window之間的關(guān)系
以一個一般的窗口為例。我們總是有一個root窗口。這個窗口代表著這個程序,關(guān)閉這個窗口的同時也會導(dǎo)致程序的退出。
root窗口下有很多子窗口。子窗口下又有很多子窗口。關(guān)閉一個窗口會導(dǎo)致所有子窗口的關(guān)閉。不難明白窗口之間的關(guān)系是呈現(xiàn)樹狀關(guān)系。
Window之間的通信
這里直接舉了wxWidget上的例子。
可以看到我們總共有5個Window。
TotalWindow
LeftWindow
PlusWindow(實(shí)際上是PlusButton,Button類繼承自Window)
MinusWindow
RightWindow
當(dāng)我們點(diǎn)擊+號的時候,我們希望右側(cè)的窗口的數(shù)值+1。這該怎么通信呢?
我的第一反應(yīng)是去做一個eventfd。當(dāng)+點(diǎn)擊的時候,就向eventfd寫一個char。然后RightPanel注冊了一個事件。但是想想就覺的麻煩。
實(shí)際上我們可以直接利用樹裝關(guān)系。通過Parent來獲得RightPanel。
大致的偽代碼思路如下。
LeftPanel::OnPlus()
{
? ? count++;
? ? p = getParent();
? ? p->rPanel->text = count;
? ? p->rPanel->refresh();
}
Event
事件類。我覺的是最好寫的。
eventID用來標(biāo)識事件的類型。
eventHandler事件的回調(diào)函數(shù)要準(zhǔn)備好。
因?yàn)槭录|發(fā)的時候,大部分時候會重繪Window,所以我們需要讓事件保留Window的指針。
總體關(guān)系如下:
一個RootWindow可以有無數(shù)的childWindow。每個Window可以綁定無數(shù)個事件。每個事件只能關(guān)聯(lián)到一個Window。
————————————————