?? minigui 體系結(jié)構(gòu)之二 多窗口管理和控件及控件類.htm
字號:
face=helvetica,helv,arial size=-2>附:MiniGUI 的最新進(jìn)展</FONT></A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/linux/embed/minigui/minigui-5/index.shtml#resources"><FONT
face=helvetica,helv,arial size=-2>資源</FONT></A></TD></TR>
<TR>
<TD><A
href="http://www-900.ibm.com/developerWorks/cn/linux/embed/minigui/minigui-5/index.shtml#author"><FONT
face=helvetica,helv,arial size=-2>有關(guān)作者</FONT></A></TD></TR>
<TR>
<TD bgColor=#000000><FONT face=helvetica,helv,arial size=-3><IMG
height=3 alt="" src="MiniGUI 體系結(jié)構(gòu)之二 多窗口管理和控件及控件類.files/c.gif"
width=137></FONT></TD></TR></TBODY></TABLE><BR><BR><!-- End Table of Contents --><!-- comments #6: html content of the paper -->
<P><EM>本文是 MiniGUI 體系結(jié)構(gòu)系列文章的第二篇,重點(diǎn)介紹 MiniGUI 的多窗口機(jī)制以及相關(guān)的窗口類技術(shù)。其中涉及到窗口 Z
序、窗口剪切、控件類和控件以及輸入法模塊設(shè)計等等。</EM></P><A id=1 name=1></A>
<P><STRONG class=subhead>1 引言</STRONG></P>
<P>在任何一個足夠復(fù)雜的 GUI
系統(tǒng)中,處理窗口之間的互相剪切是其首要解決的問題。因?yàn)槎啻翱谙到y(tǒng)首先要確保一個窗口中的繪制輸出不會影響到另外一個窗口。為此,GUI 系統(tǒng)一般要利用
Z 序來管理窗口之間的互相剪切關(guān)系。根據(jù)窗口在 Z 序中所處的位置,GUI
系統(tǒng)要計算每個窗口受剪切的區(qū)域,即剪切域。通常,窗口的剪切域定義為互不相交的矩形集合。GUI
系統(tǒng)的底層圖形引擎在進(jìn)行輸出時,要根據(jù)當(dāng)前輸出的剪切域進(jìn)行輸出的剪切操作。從而保證窗口的繪制輸出不會互相影響。因?yàn)槿魏我粋€窗口的創(chuàng)建、銷毀、隱藏、顯示均有可能影響其他窗口的剪切域,所以首先要有一個高效的剪切域維護(hù)算法。本文將詳細(xì)描述
MiniGUI 中的剪切域生成算法。</P>
<P>許多人對控件(或者部件)的概念已經(jīng)相當(dāng)熟悉了。控件可以理解為主窗口中的子窗口。這些子窗口的行為和主窗口一樣,即能夠接收鍵盤和鼠標(biāo)等外部輸入,也可以在自己的區(qū)域內(nèi)進(jìn)行輸出――只是它們的所有活動被限制在主窗口中。MiniGUI
也支持子窗口,并且可以在子窗口中嵌套建立子窗口。我們將 MiniGUI 中的所有子窗口均稱為控件。</P>
<P>在 Windows 或 X Window
中,系統(tǒng)會預(yù)先定義一些控件類,當(dāng)利用某個控件類創(chuàng)建控件之后,所有屬于這個控件類的控件均會具有相同的行為和顯示。利用這些技術(shù),可以確保一致的人機(jī)操作界面,而對程序員來講,可以像搭積木一樣地組建圖形用戶界面。MiniGUI
使用了控件類和控件的概念,并且可以方便地對已有控件進(jìn)行重載,使得其有一些特殊效果。比如,需要建立一個只允許輸入數(shù)字的編輯框時,就可以通過重載已有編輯框而實(shí)現(xiàn),而不需要重新編寫一個新的控件類。</P>
<P>在多語種環(huán)境中,輸入法是一個必不可少的模塊。輸入法提供了將標(biāo)準(zhǔn)鍵盤輸入翻譯為適當(dāng)語種的文字的能力。MiniGUI
中也包含有標(biāo)準(zhǔn)的中文簡體輸入法,包括全拼、五筆和智能拼音等等。本文最后將介紹 MiniGUI 中的輸入法模塊實(shí)現(xiàn)。</P><A id=2
name=2></A>
<P><STRONG class=subhead>2 窗口 Z 序</STRONG></P>
<P>Z 序?qū)嶋H定義了窗口之間的層疊順序。說起“Z
序”這個名稱,實(shí)際是相對屏幕坐標(biāo)而言的。一般而言,屏幕上的所有窗口均有一個坐標(biāo)系,即原點(diǎn)在左上角,X 軸水平向右,Y 軸垂直向下的坐標(biāo)系。Z
序就是相對于一個假想的 Z 軸而言的,這個 Z 軸從屏幕外指向屏幕內(nèi)。窗口在這個 Z 軸上的值,就確定了其 Z 序。Z 序值大的窗口,覆蓋了 Z
序值小的窗口。</P>
<P>當(dāng)然,在程序當(dāng)中,Z 序一般表示為一個鏈表。越接近于鏈表頭的節(jié)點(diǎn),其 Z 序值就越大。在 MiniGUI 中,我們維護(hù)了兩個 Z 序。其中一個
Z 序永遠(yuǎn)位于另一個 Z 序之上。這樣,就可以創(chuàng)建始終位于其他窗口之上的窗口,比如輸入法窗口。如果在建立窗口時,指定了 WS_EX_TOPMOST
擴(kuò)展屬性,就可以創(chuàng)建這樣的主窗口。因?yàn)?Z 序的操作實(shí)際就是鏈表的操作,這里就不再贅述。</P><A id=3 name=3></A>
<P><STRONG class=subhead>3 窗口剪切算法</STRONG></P>
<P>有了窗口 Z 序,我們就可以計算每個窗口的剪切域。我們把因?yàn)榇翱?Z
序而產(chǎn)生的剪切域稱為“全局剪切域”,這是相對于窗口自身定義的剪切域而言的,我們把后者稱為“局部剪切域”。窗口中的所有輸出,首先要受到全局剪切域的影響,其次受到局部剪切域的影響。我們在這里重點(diǎn)講解窗口的全局剪切域的生成和維護(hù)。</P><STRONG>3.1
全局剪切域的生成和維護(hù)</STRONG>
<P>在 MiniGUI
中,剪切域表示為若干互不相交的矩形之并集,這些矩形稱為剪切矩形。最初,屏幕上沒有任何窗口時,桌面的剪切域由一個矩形組成,即屏幕矩形;當(dāng)屏幕上只有一個窗口時,該窗口的剪切域由一個矩形組成,該矩形即為窗口在屏幕上的矩形,而桌面的剪切域卻可能是由多個矩形組成的。圖
1 說明了只有一個窗口時的桌面的剪切域組成。從圖中可以看出,此時桌面的剪切域由四個矩形組成,分別是 A、B、C 和 D。如果窗口在桌面的位置變化為圖
2 所示,則桌面的剪切域?qū)⒂蓛蓚€矩形組成(A和B)。</P>
<P align=center><IMG alt=""
src="MiniGUI 體系結(jié)構(gòu)之二 多窗口管理和控件及控件類.files/image01.jpg" border=0><BR>圖 1
由四個矩形組成的桌面剪切域</P>
<P align=center><IMG alt=""
src="MiniGUI 體系結(jié)構(gòu)之二 多窗口管理和控件及控件類.files/image02.jpg" border=0><BR>圖 2
由兩個矩形組成的桌面剪切域</P>
<P>讀者很容易看出,在只有一個窗口的情況下,形成桌面剪切域的矩形最多只能有四個。</P>
<P>此時,如果有一個新的窗口出現(xiàn),則新的窗口將同時剪切舊的窗口和桌面(圖
3。窗口的剪切矩形用空心矩形表示,而桌面的剪切矩形用實(shí)心矩形表示)。而這時,桌面和舊窗口的剪切域?qū)⒍喑鲆恍┚匦危@些矩形應(yīng)該是原有剪切域中的每個矩形受到新窗口矩形影響之后生成的剪切矩形。同樣,原有剪切域中的每個矩形只能最多只能派生出4個新剪切域,而某些矩形根本不會受到新窗口矩形的影響。</P>
<P align=center><IMG alt=""
src="MiniGUI 體系結(jié)構(gòu)之二 多窗口管理和控件及控件類.files/image03.jpg" border=0><BR>圖 3
有新窗口被創(chuàng)建時,桌面和舊窗口的剪切域</P>
<P>這樣,我們可以將某個窗口全局剪切域歸納為原有剪切域中排除(Exclude)某個矩形而生成的:</P>
<OL>
<LI>窗口的全局剪切域初始化為窗口矩形。
<LI>當(dāng)窗口之上有其他窗口覆蓋時,則該窗口的全局剪切域?yàn)榕懦麓翱诰匦沃蟮募羟杏颉?
<LI>沿 Z 序迭代第 2 步,直到最頂層窗口。 </LI></OL>
<P>清單 1 中的代碼是在顯示一個新窗口時,MiniGUI 處理被該窗口所覆蓋的其他所有窗口的代碼。這段代碼調(diào)用了剪切域維護(hù)接口中的
SubtractClipRect 函數(shù)計算新的剪切域。</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>清單 1 顯示新窗口時計算被新窗口覆蓋的窗口的全局剪切域
// clip all windows under this window.
static void clip_windows_under_this (ZORDERINFO* zorder, PMAINWIN pWin, RECT* rcWin)
{
PZORDERNODE pNode;
PGCRINFO pGCRInfo;
pNode = zorder->pTopMost;
while (pNode->hWnd != (HWND)pWin)
pNode = pNode->pNext;
pNode = pNode->pNext;
while (pNode)
{
if (((PMAINWIN)(pNode->hWnd))->dwStyle & WS_VISIBLE) {
pGCRInfo = ((PMAINWIN)(pNode->hWnd))->pGCRInfo;
pthread_mutex_lock (&pGCRInfo->lock);
SubtractClipRect (&pGCRInfo->crgn, rcWin);
pGCRInfo->age ++;
pthread_mutex_unlock (&pGCRInfo->lock);
}
pNode = pNode->pNext;
}
}
</PRE></TD></TR></TBODY></TABLE>
<P>與排除矩形相反的操作是包含(Include)某個矩形到剪切域中。這個操作用于隱藏或者銷毀某個窗口時。當(dāng)一個窗口被隱藏或銷毀時,該窗口之下的所有窗口將受到影響,此時,要將被隱藏或銷毀窗口的矩形包含到這些受影響窗口的全局剪切域中。為此,MiniGUI
的剪切域維護(hù)接口中有一個函數(shù)專用于該類操作(IncludeClipRect)。為確保剪切域中矩形互不相交,該函數(shù)首先計算與每個剪切矩形的相交矩形,然后將自己添加到該剪切域中。</P>
<P>但是,在某些情況下,我們必須重新計算所有窗口的全局剪切域,比如在移動某個窗口時。</P><STRONG>3.2
剪切矩形的私有堆</STRONG>
<P>顯然,在剪切域非常復(fù)雜,或者窗口非常多時,需要大量的矩形來表示每個窗口的全局剪切域。而在 C 程序中,如果頻繁使用 malloc 和 free
申請和釋放每個剪切矩形,將帶來許多問題。第一,malloc 和 free 是非常耗時的操作;第二,頻繁的 malloc 和 free 將導(dǎo)致 C
程序堆的碎片化,從而可能導(dǎo)致將來的內(nèi)存分配失敗。為了避免頻繁使用 malloc 和 free,MiniGUI
在初始化時,建立了一個私有的堆。我們可以直接從這個堆中分配剪切矩形,而不需要從進(jìn)程的全局堆中分配剪切矩形。這個私有堆實(shí)際是由一些空閑待用的剪切矩形組成的。每次分配時返回該鏈表的頭節(jié)點(diǎn),而在釋放時放進(jìn)該鏈表的尾節(jié)點(diǎn)。如果該鏈表為空,則利用
malloc 從進(jìn)程的全局堆中分配剪切矩形。清單 2 說明了這個私有堆的初始化和操作。</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><PRE>清單 2 從剪切矩形私有堆中分配和釋放剪切矩形
PCLIPRECT GUIAPI ClipRectAlloc(PFREECLIPRECTLIST pList)
{
PCLIPRECT pRect;
#ifndef _LITE_VERSION
pthread_mutex_lock (&pList->lock);
#endif
if (pList->head) {
pRect = pList->head;
pList->head = pRect->next;
}
else {
if (pList->free < pList->size) {
pRect = pList->heap + pList->free;
pRect->fromheap = TRUE;
pList->free ++;
}
else {
pRect = malloc (sizeof(CLIPRECT));
if (pRect == NULL)
fprintf (stderr, "GDI error: alloc clip rect failure!\n");
else
pRect->fromheap = FALSE;
}
}
#ifndef _LITE_VERSION
pthread_mutex_unlock (&pList->lock);
#endif
return pRect;
}
void GUIAPI FreeClipRect(PFREECLIPRECTLIST pList, CLIPRECT* pRect)
{
#ifndef _LITE_VERSION
pthread_mutex_lock (&pList->lock);
#endif
pRect->next = NULL;
if (pList->head) {
pList->tail->next = (PCLIPRECT)pRect;
pList->tail = (PCLIPRECT)pRect;
}
else {
pList->head = pList->tail = (PCLIPRECT)pRect;
}
#ifndef _LITE_VERSION
pthread_mutex_unlock (&pList->lock);
#endif
}
</PRE></TD></TR></TBODY></TABLE><A id=4 name=4></A>
<P><STRONG class=subhead>4 主窗口和控件、控件類</STRONG></P><STRONG>4.1
控件類和控件</STRONG>
<P>如果讀者曾經(jīng)編寫過 Windows 應(yīng)用程序的話,就應(yīng)該了解窗口類的概念。在 Windows
中,程序所建立的每個窗口,都對應(yīng)著某種窗口類。這一概念和面向?qū)ο缶幊讨械念悺ο蟮年P(guān)系類似。借用面向?qū)ο蟮男g(shù)語,Windows
中的每個窗口實(shí)際都是某個窗口類的一個實(shí)例。在 X Window 編程中,也有類似的概念,比如我們建立的每一個 Widget,實(shí)際都是某個
Widget 類的實(shí)例。</P>
<P>這樣,如果程序需要建立一個窗口,就首先要確保選擇正確的窗口類,因?yàn)槊總€窗口類決定了對應(yīng)窗口實(shí)例的表象和行為。這里的表象指窗口的外觀,比如窗口邊框?qū)挾龋欠裼袠?biāo)題欄等等,行為指窗口對用戶輸入的響應(yīng)。每一個
GUI
系統(tǒng)都會預(yù)定義一些窗口類,常見的有按鈕、列表框、滾動條、編輯框等等。如果程序要建立的窗口很特殊,就需要首先注冊一個窗口類,然后建立這個窗口類一個實(shí)例。這樣就大大提高了代碼的可重用性。</P>
<P>在 MiniGUI
中,我們認(rèn)為主窗口通常是一種比較特殊的窗口。因?yàn)橹鞔翱诖a的可重用性一般很低,如果按照通常的方式為每個主窗口注冊一個窗口類的話,則會導(dǎo)致額外不必要的存儲空間,所以我們并沒有在主窗口提供窗口類支持。但主窗口中的所有子窗口,即控件,均支持窗口類(控件類)的概念。MiniGUI
提供了常用的預(yù)定義控件類,包括按鈕(包括單選鈕、復(fù)選鈕)、靜態(tài)框、列表框、進(jìn)度條、滑塊、編輯框等等。程序也可以定制自己的控件類,注冊后再創(chuàng)建對應(yīng)的實(shí)例。清單
3 中的代碼就創(chuàng)建了一個編輯框,一個按鈕。</P>
<P>采用控件類和控件實(shí)例的結(jié)構(gòu),不僅可以提高代碼的可重用性,而且還可以方便地對已有控件類進(jìn)行擴(kuò)展。比如,在需要建立一個只允許輸入數(shù)字的編輯框時,就可以通過重載已有編輯框控件類而實(shí)現(xiàn),而不需要重新編寫一個新的控件類。在
MiniGUI 中,這種技術(shù)稱為子類化或者窗口派生。子類化的方法有三種:</P>
<UL>
<LI>一種是對已經(jīng)建立的控件實(shí)例進(jìn)行子類化,子類化的結(jié)果是只影響這一個控件實(shí)例;
<LI>一種是對某個控件類進(jìn)行子類化,將影響其后創(chuàng)建的所有該控件類的控件實(shí)例;
<LI>最后一種是在某個控件類的基礎(chǔ)上新注冊一個子類化的控件類,不會影響原有控件類。在 Windows 中,這種技術(shù)又稱為超類化。
</LI></UL>
<P>在 MiniGUI 中,控件的子類化實(shí)際是通過替換已有的窗口過程實(shí)現(xiàn)的。清單 4
中的代碼就通過控件類創(chuàng)建了兩個子類化的編輯框,一個只能輸入數(shù)字,而另一個只能輸入字母:</P>
<TABLE class=code-sample cellPadding=0 width="100%" border=0>
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -