WINCE應用的UI實現方案

一、MFC的硬傷

在接手現在這個項目之前,我對WINDOWS平臺上的UI開發還是個白癡,除了MFC,就只知道GDI了。而且居然大言不慚地說用MFC只能畫畫灰色的對話框和按鈕。但不論如何,在嵌入式這種對成本極度敏感的項目上,我是不會拍板用MFC的。假設極端情況,定製後的系統是31.8M,我放一個ARMV4I上的MFC DLL進去,大概500多K,那麼只有兩種選擇,要麼把32M的FLASH換成64M的——我的上司會把我給砍了,要麼把應用層的UI代碼全部重寫——我的下屬會把我給剁了。另一方面,WINCE上的應用軟件我看過不少開源代碼,也接觸了一些外包的軟件,還真沒見過誰用MFC的。網上公論用MFC後會導致程序在不同平臺上移植性降低,因爲你不能指望別人的平臺給你準備好奢侈的MFC。另一方面,多數高手都不屑用。我不是高手,但可以學人家擺譜,於是“不會用”就變成了“不屑用” ^_^

二、GDI的痛苦

把整套UI從CreateWindow開始寫起,的確很累人。我寫了500多行才勉強實現BUTTON類,另一個同事也用了500行左右才實現了TRACK BAR類,而且還未經測試,也沒有很正式的CODE REVIEW。如果工業設計中心多增加幾種圖樣,那麼我們就得多些幾個基類,然後再賠進去CODE REVIEW的時間、測試時間、BUG FIX的時間。不痛苦,那是不可能滴~。

三、GWES的探路,我不是先鋒

羣衆的智慧是無窮的。當我這組同事的思維都受制於我的GDI方案時,從通信部過來協助完成項目的軟件工程師從WINCE500的一個應用SAMPLE CODE裏把DialogBox函數給抓出來了。我認爲自己在定UI實現方案上很失敗的一點就是習慣性思維地從eVC裏建立DIALOG RESOURCE後,立刻就要去點Class Wizard, 然後就是關聯MFC類。而他卻畫出來的DIALOG和BUTTON後,拿着RESOUCE ID從DialogBox函數建立起UI。並且我又習慣性思維地認爲DialogBox並不在STANDARD SDK 500裏面,但他確實從STANDARDSDK_500裏不引用其它LIB和DLL就把DialogBox和BUTTON用起來了,然後過來找我談論如何把圖片疊加在DIALOG和BUTTON上。淚奔一百里~ 我應該去找塊豆腐撞死~

四、最後的攻關,GWES API能否成爲我們需要的堅實地基
GWES系列API能否實現我們所需的所有UI功能呢?沒有人知道,需要評估。剛纔起草稿時,我把這些都寫在同一篇文章裏了。現在覺得還是分篇好些,畢竟主題不同。請繼續看中篇:GWES方案上幾技術難點的解決


這裏談論的所謂技術難點,其實根本不值一提。只不過微軟定了一套遊戲規則,我們目前不清楚這套遊戲規則,花時間去摸索而已。

1、BUTTON的動畫效果

我們用了GWES裏提供的BUTTON類,在WINCE PRODUCT DOCUMENT裏的位置是
ms-help://MS.WindowsCE.500/wceshellui5/html/wce50grfButtonReference.htm
裏面並沒有給出太多的說明,在Button Messages裏提到有WM_CTLCOLORBTN消息, 但簡單試用後發現和預期效果不符。我亂翻亂點時注意到了eVC在畫圖時,對BUTTON點右鍵出的菜單裏,打開Properties,裏面的Styles頁有個複選框"Owner draw", 我就抓住這根稻草,GOOGLE一把,方法就出來了。

當Owner draw屬性被勾選時,輪到該BUTTON繪圖時,程序就不會跑DefDlgProc去畫個灰色突出的效果並把按鈕名字寫上去,而是給BUTTON的父窗口,也就是DIALOG的PROC發個消息WM_DRAWITEM,並且所帶的lParam中有我們需要的所有東西。來個強制轉換
LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)lParam;
然後根據 lpDIS->itemAction 和 lpDIS->itemState 判斷BUTTON當前狀態,以決定加載哪幅圖片

DRAWITEMSTRUCT的詳細說明請參考 ms-help://MS.WindowsCE.500/wceshellui5/html/wce50lrfdrawitemstruct.htm

我簡單試了了一下,
(1) 初始化時
itemAction == 1;  // ODA_DRAWENTIRE
itemState == 16;  // ODS_FOCUS
(2) BUTTON被按下時
itemAction == 2;  // ODA_Select
itemState == 17;  // ODS_FOCUS | ODS_SelectED
(3) BUTTON被點擊後鬆開時
itemAction == 2;  // ODA_Select
itemState == 16;  // ODS_FOCUS

ODA和ODS宏定義數值
/*** from Winuser.h ***/
#define ODT_MENU        1
#define ODT_LISTBOX     2
#define ODT_COMBOBOX    3
#define ODT_BUTTON      4
//action
#define ODA_DRAWENTIRE  0x0001
#define ODA_Select      0x0002
#define ODA_FOCUS       0x0004
//state
#define ODS_SelectED    0x0001
#define ODS_GRAYED      0x0002
#define ODS_DISABLED    0x0004
#define ODS_CHECKED     0x0008
#define ODS_FOCUS       0x0010

在WM_DRAWITEM中有兩點要特別注意
(1) 不能在裏面用InvalidateRect(lpDIS->hwndItem, lpDIS->rcItem, NULL),這會立即再發一個WM_DRAWITEM消息過來,接着再調InvalidateRect, 進入死循環直至把設備上的內存耗光,導致死機
(2) 對itemAction和itemState作判斷時,必須把兩者同時都判斷了才能確定CLICK狀態,單獨判斷action或單獨判斷state是不夠的,會導致重繪作用在不希望發生的情況下。並且不能簡單地作itemAction & ODA_Select這樣的位與判斷,還必須有排他性,我乾脆就用==號了。

參考代碼如下





1 BOOL CALLBACK DialogProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
2 {
3     switch(uMsg)
4     {
5         case WM_DRAWITEM:
6         {
7             LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)lParam;
8
9             if(lpDIS->CtlID == IDC_BTN_1)
10             {
11
12                 if( (lpDIS->itemAction & ODA_DRAWENTIRE)   //這是初始化時的情況
13                     ||    ( (lpDIS->itemAction == ODA_Select) && (lpDIS->itemState == ODS_FOCUS) ) ) //這是按下後放開時的情況
14                 {
15                     //畫上未被按下時的圖片
16                 }
17                 else if( (lpDIS->itemAction & ODA_Select) && (lpDIS->itemState == (ODS_FOCUS | ODS_SelectED) ) ) //被按下時的情況
18                 {
19                     //畫上被按下時的圖片
20                     MessageBeep(MB_OK); //順便響一聲,比較有手感
21                 }
22             }
23             return TRUE;
24         }
25         default:
26             //STUB
27             break;
28     }
29    
30     return FALSE;
31 }

2、DLU和PIXEL的單位轉換

本來以爲做完BUTTON效果後就OVER了,結果今天傍晚時候遇到一個很惱火的問題。在VC / eVC / VS中畫的對話框、按鈕等控件時,在IDE右下角顯示的 XX * XX單位是DLU (Dialog Unit), 這是根據你設置的對話框字體大小而改變的。這種做法無可厚非。如果把字體改大了,那麼DIALOG和BUTTON自然也被“撐”大了,比較靈活。但是我往上疊加的圖片是按像素(PIXEL)來算的。最後實現出來,有兩個方法。按照國際慣例,當然是先講笨的方法,MSDN上的作風也是如此。

(1)方法一:我查了下DLU和PIXEL之間的換算關係,有個講得比較全的網頁是http://support.microsoft.com/default.aspx?scid=kb;en-us;145994 (How to caculate dialog box units based on the current font in Visual C++) 按照文中的Method Two簡單測試了一下


1 BOOL CALLBACK DialogProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
2 {
3     switch(uMsg)
4     {
5         case WM_INITDIALOG:
6         {
7             RECT rc={0,0,4,8};
8
9             MapDialogRect(hDlg, &rc );
10             printf("baseUnitX = %ld\n", rc.right);
11             printf("baseUnitY = %ld\n", rc.bottom);
12
13             //DLU_Weight = pixel_weight * 4 / baseunitX;
14             //DLU_Height = pixel_height * 8 / baseunitY;
15
16             return TRUE;
17         }
18     }
19
20     return FALSE;
21 }

對於我DIALOG中設置的Courier New 10pt字體,
第一步:從代碼中得到baseUnitX = 7, baseUnitY = 16,
第二步:套用公式
weight_DLU = weight_pixel * 4 / baseUnitX
height_DLU = height_pixel * 8 / baseUnitY
比如我想建立一個320*240 PIXEL的對話框,那麼
weight_DLU = 320 * 4 / 7 = 182.86, 取整爲183
height_DLU = 240 * 8 / 16 = 120
第三步:到VC裏去拖動對話框的邊界,畫出183*120的對話框,那麼代碼運行起來後,通過GetClientRect查一下,的確是得到320*240 pixel的對話框了。

但是這種方法有一個致命的缺點。比如我要畫一個60*70的按鈕,按照上面的baseUnitX和baseUnitY折算後應該爲34.28 * 35,但是我畫34*35 DLU時,運行得到59*70 PIXEL窗口;畫35*35 DLU時,運行得到61*70 PIXEL窗口,無法恰到好處,這導致了往上疊加60*70 pixel的圖片時,按鈕邊緣會出現不連續的黑點。所以這段文字只能當作DLU和PIXEL的換算關係來玩了,沒有任何實際應用價值。

(2) 方法二:正確的方法
其實也很簡單。晚飯後突然想到的,用SetWindowPos強制設置window的長寬和左上角座標。我在WM_DRAWITEM消息的處理中簡單試了一下:
SetWindowPos(lpDIS->hwndItem, NULL, 10, 10, 60, 70, SWP_NOZORDER);
設置前是59*70的按鈕,設置後就是60*70了,並且疊加圖片沒有任何問題。OK,整個基於GWES的UI方案至此成型了。後面貌似沒有什麼大的技術障礙了。

至此,我沒有發現GWES方案上還有什麼路障了,可以拍板使用這套方案了,和只用GDI寫UI相比,軟件研發的工作量大概降低了30%左右。當然事情還沒有就這樣結束,這套方案對我這項目組的意義是很深遠的。請看下篇:代碼中的一小步,項目進度管理上的一大步



當我試驗SetWindowPos成功時,我感覺到對我這個應用開發組來說,這是一次革命了。項目進度上的革命。

按照目前的進度安排方式,事業部發佈設計需求後各部門的工作狀態時這樣的:
(1) 軟件研發,首先去確定底層接口,比如要調用BSP的哪些DeviceIoControl,要用哪些協議棧,要約定哪些註冊表鍵值,約定各應用的進程間通信。
(2) 工業設計中心, 同步開始設計UI圖片。
(3) 測試組,同步開始編寫測試例

而三者之中,工業設計中心是最慢的,界面風格需要多次評審和修改,而且主觀因素很強,領導說不好看,就得繼續改,隨便調整一下就是一兩天。以我做幾個項目的經歷來看,往往是軟件研發人員和測試都完成第一步了,工業設計中心還沒發出切割圖,然後大家就傻在那裏等資源。等工業設計中心正式發佈切割圖後,軟件研發纔開始埋頭苦幹,這時候測試組又繼續閒着,等到出了ALPHA版纔開始測試工作。

以我當前這個項目爲例,工業設計用兩個人花了足足一個月的時間才完成一級界面和二級界面,所以應用組的人也不緊不慢地花了一個月的時間來作底層接口的研究和確定,慢慢地看文檔。實際上如果都是該領域的熟手,並且效率夠高的話,這些事情最多兩週就能做完了。

而如果用了GWES的API,加上SetWindowPos的做法之後,項目進度上的優勢是非常明顯的:
(1) 軟件研發:確定底層接口後,立刻建立起DIALOG和BUTTON,EDITOR等控件,根本不用關心UI最後設計成什麼樣。重點是上層的數據結構和邏輯,和編寫代碼對底層接口進行調用測試。UI並不再會成爲瓶頸,只要隨便拖幾個控件出來就行了,座標和長寬也是隨意的,只要把功能做對了。
(2) 工業設計中心:可以慢慢地做圖片,一輪一輪地慢慢評審。由於疊加圖片的方式已經很明確,並且程序員寫繪圖代碼時可以同時指定座標和長寬,直接修正原型開發時亂拼湊的界面,所以切割圖在軟件BETA RELEASE前兩三天發佈就來得及了。
(3) 測試組:由於軟件研發可以很快地把界面醜陋、但功能實現好的ALPHA版程序發佈,所以測試組可以大大提前手工測試的開始時間點。並且儘早開始BUG反饋。甚至於在UI圖片出來之前,就可以改幾輪BUG了。在UI圖片出來之前完成ALPHA版,並且改過幾輪BUG,這種情況在以前是從來不能想象的,應用工程師肯定會說:圖片都還沒有,怎麼寫代碼?寫了也白寫,反正還要改。
(4) 圖片發佈後,每個應用程序最多花兩天工夫作圖片疊加,而且原先寫的代碼在圖片疊加的工作中完全不用改動。
(2) 由於功能實現的代碼段沒有因爲圖片疊加而改動,所以之前測試的BUG仍然全部有效,並且因爲圖片疊加而產生的高級別BUG可能性很小。

OHYE,事情想象得真美妙。
難道是我年少無知,其實其他公司早就是這麼開發應用軟件的?我今天造的新詞很適合形容現在的心情:淚奔一百里。GO ON~嗯~嗯~一百里啊一百里~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章