標 題: 【原創】WIN MOBILE UI開發入門
作 者: 打小
時 間: 2009-06-06,12:17:14
鏈 接: http://bbs.pediy.com/showthread.php?t=90857
這是我昨天發在新人交流版塊的文章,還是放到這邊比較合適。嫌麻煩,沒有叫版主轉移,我自己拷貝過來咯。。。。
首先感謝 『嵌入式平台安全』版塊版主加百力的邀請(貌似算不上邀請,表達不好,Whatever),才有了這篇文章的誕生,
在下才識有限,接觸WIN MOBILE開發時間不久,倉促整理了一些UI部分入門級的東東(內容並不系統,全面),希望能夠對剛涉足MOBILE開發的朋友有協助。
註:由於自身局限,本文不涉及.net
OK,廢話完畢,歡迎雞蛋和鮮花。
本文假設您已經瞭解SMART PHONE與POCKET PC的區別。沒有特別說明,均指在POCKET PC上。
現狀:IPHONE的風靡,引領了當前智能手機的系統及APP介面潮流。MS雖然發布了WINDOWS MOBILE 6.5,但在將來不短的 一段時間內,承載MOBILE 6.2及以下系統的PPC仍將是主流。手持功能的特殊性決定了其上APP介面表現的重要性,很多時候甚至項目70%的代碼 都與介面有關。
一。GDI繪圖基礎(必須掌握的基礎)
1.1 裝置環境DC
“裝置環境”(device context),經常簡稱寫為DC, 是Windows 用來管理訪問顯示和列印裝置的工具。
1) 什麼是DC
一個DC是一個結構,它定義了一系列繪圖物件的集合以及它們相關的屬性,以及影響輸出效果的一些圖形模式。
這些繪圖物件包括一個畫線的筆,一個填充和painting的畫刷,一個用來向螢幕拷貝的位元影像,一個定義了一系列顏色集合的調色盤,一個用來剪裁等操作的地區。
例如:使用TextOut,DC的屬性確定了文字的顏色、文字的背景色、顯示文字時字型等等。
如何理解DC:
DC用於繪圖輸出,輸出裝置包括螢幕,印表機等,在WM中一般總是螢幕輸出,總是與特定表單相關。實際上是GDI內部儲存的資料結構, DC中的有些值定義了GDI繪圖函數工作的細節。
2) 擷取裝置DC
一個應用程式從不直接地訪問(access)dc,常見的取得dc的方式:
HDC GetDC( HWND hWnd);
HDC GetWindowDC( HWND hWnd);
HDC GetDCEx( HWND hWnd, HRGN hrgnClip,DWORD flags);
Value Description
DCX_WINDOW 返回與視窗矩形而不是與客戶矩形相對應的裝置上下文環境
DCX_CACHE 從快取而不是從OWNDC或CLASSDC視窗中返回裝置上下文環境。覆蓋CS_OWNDC和CS_CLASSDC。
DCX_PARENTCLIP(*) 使用父視窗的可見地區,父視窗的WS_CIPCHILDREN和CS_PARENTDC風格被忽略,並把裝置上下文環境的原點,設在由hWnd所標識的視窗的左上方。
DCX_CLIPSIBLINGS 排除hWnd參數所標識視窗上的所有子視窗的可見地區。
DCX_CLIPCHILDREN 排除hWnd參數所標識視窗上的所有子視窗的可見地區。
DCX_NORESETATTRS(*) 當裝置上下文環境被釋放時,並不重設該裝置上下文環境的特性為預設特性。
DCX_EXCLUDERGN 從返回裝置上下文環境的可見地區中排除由hrgnClip指定的剪下地區。
DCX_EXCLUDEUPDATE(*) 排除視窗更新地區.
DCX_INTERSECTRGN The clipping region identified by hrgnClip is intersected with the visible region of the returned device context.
DCX_INTERSECTUPDATE Returns a region that includes the window's update region
DCX_VALIDATE(*) 當與DCX_INTERSECTUPDATE一起指定時,致使裝置上下文環境完全有效,該函數與 DCX_INTERSECTUPDATE和DCX_VALIDATE一起使用時與使用BeginPaint函數相同。.
1.2 有效區和無效區
Windows內部為每個視窗儲存一個「繪圖資訊結構」,這個結構包含了包圍無效地區的最小矩形的座標以及其它資訊,這個矩形就叫做「無效矩形」,或者「無效地區」。當重繪視窗時(收到WM_PAINT訊息),僅需重繪「無效地區」就可以 。
表單收到WM_PAINT訊息,
BeginPaint擷取無效地區的dc,計算無效地區,
重繪無效地區,
EndPaint函數,將無效地區標記為有效地區。
Windows不會將多個WM_PAINT訊息都放在訊息佇列中。WM_PAINT只會更新無效地區內資訊。在處理WM_PAINT訊息後,整個程式介面都變為有效地區,如果不對程式進行任何操作,它是不會產生無效地區的。引起WM_PAINT訊息的事件:
使用者移動視窗時,先前被覆蓋的視窗顯示時;
使用者調整視窗大小
使用ScrollDC等函數滾動視窗時
使用者模組發送WM_PAINT訊息(一般在InvaladataRECT 之後發送訊息)
WM_PAINT
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, g_szMessage, -1, &rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint (hwnd, &ps);
}
break;
將有效地區標記為無效地區
BOOL InvalidateRect( HWND hWnd,const RECT *lpRect, BOOL bErase);
InvalidateRect只是增加重繪地區,在下次WM_PAINT的時候才生效 。使無效地區立刻重繪:
InvalidateRect(…);
hWnd表單發出WM_PAINT的訊息,
lpRect:是指定要重新整理的地區,此地區外的地區 不被重繪,這樣防止一個局部的改動,而導致整個用戶端區域重繪而導致閃爍。
BOOL bErase的參數TRUE表示在無效地區重繪之前之前還向表單發送WM_ERASEBKGND訊息,這樣將導致,用背景色將所選地區覆蓋一次後再重繪,(背景色可通過設定BRUSH來改變)。
將無效地區標記為有效地區
可以通過呼叫ValidateRect函數使顯示地區內的任意矩形地區變為有效。如果這呼叫具有令整個無效地區變為有效效果,則目前隊列中的任何 WM_PAINT訊息都將被刪除。
windows不會將多個WM_PAINT訊息都放在訊息佇列中。
1.3 位元影像
BMP(Bitmap-File)圖形檔案是Windows採用的 圖形檔案格式,在Windows環境下啟動並執行所有圖象處理軟體都支援BMP圖象檔案格 式。Windows系統內部各映像繪製操作都是以BMP為基礎的。 Windows 3.0以前的BMP圖檔案格式與顯示裝置有關,因此把這種BMP圖象 檔案格式稱為裝置相關位元影像DDB(device-dependent bitmap)檔案格式。Windows 3.0以後的BMP圖象檔案與顯示裝置無 關,因此把這種BMP圖象檔案格式稱為裝置無關位元影像DIB(device-independent bitmap)格式(註:Windows 3.0以 後,在系統中仍然存在DDB位元影像,象BitBlt()這種函數就是基於DDB位元影像的,只不過如果你想將映像以BMP格式儲存到磁碟檔案中時,微軟 極力推 薦你以DIB格式儲存),目的是為了讓Windows能夠在任何類型的顯示裝置上顯示所儲存的圖象。BMP位元影像檔案預設的副檔名是BMP或 者 bmp(有時它也會以.DIB或.RLE作副檔名)。
檔案結構:
位元影像檔案可看成由4個部分組成:位元影像檔案頭(bitmap-file header)、位元影像資訊頭(bitmap- information header)、彩色表(color table)和定義位元影像的位元組陣列,它具有如下所示的形式。
位元影像檔案的組成 結構名稱 符號
位元影像檔案頭(bitmap-file header) BITMAPFILEHEADER bmfh
位元影像資訊頭(information header) BITMAPINFOHEADER bmih
彩色表(color table) RGBQUAD aColors[]
圖象資料陣列位元組 BYTE aBitmapBits[]
二:常用繪圖技巧
2.1 雙緩衝繪製技術:
限於手機的硬體效能,開發人員經常需要解決螢幕閃爍的問題,一般都可以通過雙緩衝繪製來解決。
所謂的雙緩衝就是把所有內容先繪製在一個記憶體DC上; 之後一次性拷到螢幕DC,作為最終顯示。
記憶體DC,是一個虛擬記憶體裝置上下文,對它進行繪圖等操作,不會顯示在螢幕上,在記憶體DC繪製完成之後,再拷貝到螢幕上,這樣可以避免因為操作而給螢幕帶來的閃爍。
步驟:
HDC memdc = CreateCompatibleDC(hdc);//建立和目的DC一致的內DC
HBITMAP hbmp;
hbmp= CreateCompatibleBitmap(hdc,rect.Width(),rect.Height());
SelectObject(memdc,bmp); //建立一張螢幕DC的位元影像並選入記憶體DC
Drawmemdc (memdc) ; //在記憶體DC繪圖
BitBlt(hdc,0,0,rect.right,rect.bottom,memdc,0,0,SRCCOPY);//繪製到螢幕DC
DeleteObject(memdc); //銷毀資源,釋放記憶體DC
2。2映像半透明混合:
原理:操作像素點陣,假設一幅圖象是A,另一幅透明的圖象是B,那麼透過B去看A,看上去的圖象C就是B和A的混合圖象,設B圖象的透明度為 alpha(取值為0-1,1為完全透明,0為完全不透明),Alpha混合公式如下:
R(C)=(1-alpha)*R(B)+alpha*R(A)
G(C)=(1-alpha)*G(B)+alpha*G(A)
B(C)=(1-alpha)*B(B)+alpha*B(A)
步驟:1:擷取原圖大小 2:擷取原始像素點陣 3:對各像素RGB分量進行混合
for(int i=0;i<length;i++)
{
for(int t=0; t<width; t++ )
{
pix_array[i*width+t] = Getpixel(hdc,i,t);
}
}
for(int i=0; i<length*width; i++)
{
newpix_array[i].byRed = pix_array[i].byRed* alpha + 255*(1- alpha);
newpix_array[i].byGreen = pix_array[i].byGreen* alpha + 255*(1- alpha);
newpix_array[i].byBlue = pix_array[i].byBlue* alpha + 255*(1- alpha);
}
或者直接調用API:AlphaBlend
2.3背景色透明
調用API:TransparentBlt
BOOL TransparentBlt(
HDC hdcDest, //目的DC控制代碼
int nXOriginDest, //目的DC左上X軸座標
int nYOriginDest, //目的DC左上Y軸座標
int nWidthDest, //繪製時的矩形寬度
int hHeightDest, //繪製時的矩形高度
HDC hdcSrc, //源DC控制代碼
int nXOriginSrc, //源DC左上X軸座標
int nYOriginSrc, //源DC左上Y軸座標
int nWidthSrc, //源矩形寬度
int nHeightSrc, //源矩形高度
UINT crTransparent // 需要透明的顏色RGB值
);
三:PPC UI常見問題
3.1 手勢識別
原理:捕獲使用者觸筆點擊事件,收集觸筆運動軌跡,觸筆離開後綜合收集到的軌跡點,進行分析判斷。
基本實現:捕獲系統響應訊息,進行處理
單擊事件:WM_LBUTTONDOWN
移動事件:WM_MOUSEMOVE
彈起事件:WM_LBUTTONUP
常用技巧:
控制項聚焦 : 由於手持功能螢幕有限,控制項分布相對密集,對使用者操作的精準性有一定要求,當使用者進行捲軸的下拉或者其他行動控制項的動作時,很有可能在移動過程中觸筆或手指會離開控制項範圍,從而中斷移動操作,而這並非使用者所預期。
因此,有必要對控制項進行聚焦,即便出現上述狀況,控制項仍能收到WM_MOUSEMOVE事件。
解決該問題可調用API : SetFocus( HWND hWnd);
3.2介面自適應
由於POCKET PC支援橫豎屏兩種模式,在豎屏,橫屏間切換時,如果不對程式的控制項座標進行調整,會造成介面混亂的後果。
基本實現:捕獲系統的橫豎屏切換訊息,判斷將切換到何種模式,在OnSize()中進行相應處理。
常用方法:
另外由於PPC螢幕尺寸的繁雜,對不同螢幕尺寸的自適應也需要注意。此處不再贅述。
1):調整控制項座標
捕獲WM_SIZE訊息,在響應函數中擷取當前螢幕模式,根據螢幕大小對控制項座標進行調整,使整個介面自適應。
2):準備兩套不同UI資源
針對橫豎屏各準備一套UI資源,在WM_SIZE訊息的處理函數中擷取當前螢幕模式,根據不同模式調用相應UI資源,達到介面自適應的目的。
3.3 螢幕輸入面板(sip)
在POCKET PC上進行應用開發時,並非所有介面都需要SIP輸入面板,因此經常需要對SIP輸入面板的顯示和隱藏處理。
下面以直觀的圖給出兩者的對比:
在WINDOWS MOBILE中隱藏SIP的方法很多,以下簡要介紹其中幾種方法:
1)SHSipPreference(m_hWnd,SIP_Down);
2)SIPINFO si;
memset(&si,sizeof(si));
SHSipInfo(SPI_GETSIPINFO,0,&si,0);
si.fdwFlags&=~SIPF_ON;
SHSipInfo(SPI_SETSIPINFO,0,&si,0);
3) SHFullScreen(hDlg,SHFS_SHOWTASKBAR,SHFS_HIDESIPBUTTON);
4)SipShowIM(SIPF_DOWN);
5) 獲得視窗名為menu_worker的SIP視窗控制代碼,進行隱藏或顯示,實現:
CWnd* pWndSIP = FindWindow( _T("menu_worker"), 0 );
if ( pWndSIP )
{
pWndSIP->ShowWindow(SW_HIDE);// SW_SHOW
}
6)另外通過IMM也可以對SIP進行控制。
3.4 MenuBar定製
MOBILE底部的MenuBar在應用程式中扮演著和使用者互動的關鍵作用,根據不同的需求,經常需要定製MenuBar,本節將介紹如何對 MenuBar資源變更。
基本操作:
1. SHMENUBARINFO結構體
typedef struct tagSHMENUBARINFO {
DWORD cbSize; // SHMENUBARINFO結構體大小
HWND hwndParent; //CommondBar父視窗控制代碼
DWORD dwFlags; //MenuBar類型標識
UINT nToolBarId; //工具列標識
HINSTANCE hInstRes; //控制資源的執行個體控制代碼
int nBmpId; // 按鈕背景bmp圖片資源ID
int cBmpImages; //bmp圖片資源數量
HWND hwndMB; //【輸出參數】控制MenuBar的視窗控制代碼
COLORREF clrBk; //MenuBar背景顏色參數,包括SIP()
} SHMENUBARINFO,*PSHMENUBARINFO;
2.MenuBar類型
可以通過對dwFlags的設定,建立不同類型的MenuBar
SHCMBF_COLORBK 設定menu bar背景顏色
SHCMBF_EMPTYBAR 建立一個空的menu bar
SHCMBF_HIDDEN 建立一個隱藏的menu bar
SHCMBF_HIDESIPBUTTON 建立一個沒有sip的menu bar
SHCMBF_HMENU 通過資源定製menu bar而不通過工具列
3.MenuBar的建立
SHMENUBARINFO mbi;
memset(&mbi, 0, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hWnd;//視窗控制代碼
mbi.nToolBarId = IDR_MENU;//菜單資源號
mbi.hInstRes = g_hInst;//執行個體控制代碼
SHCreateMenuBar(&mbi)
g_hWndMenuBar = mbi.hwndMB;
建立完MenuBar執行個體之後,再對資源檔進行修改,指定
IDR_MENU SHMENUBAR DISCARDABLE
BEGIN
IDR_MENU, //ID
2,//個數
I_IMAGENONE, IDM_OK/*COMMAND ID*/,
TBSTATE_ENABLED,
TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE/*按鈕或快顯功能表*/,
IDS_OK/*字元*/,
0, NOMENU,
I_IMAGENONE,IDM_HELP,TBSTATE_ENABLED, TBSTYLE_DROPDOWN|TBSTYLE_AUTOSIZE,
IDS_HELP, 0, 0,
END
IDR_MENU SHMENUBAR DISCARDABLE
BEGIN
IDR_MENU,
1,
I_IMAGENONE, IDM_OK, TBSTATE_ENABLED,
TBSTYLE_BUTTON |TBSTYLE_AUTOSIZE,
IDS_OK, 0, NOMENU,
END
通過以上操作,開發人員可以給自己的程式定製個人化的MenuBar。
四:定製控制項
這是當前MOBILE開發很重要,也很讓開發人員頭疼的一個問題,有很多可以說但一下子說完貌似又不現實。大體來說,主要分為控制項自繪,以及“偽控制項”。
所幸,大部分控制項和案頭系統一致,碰到有關控制項的自繪和偽控制項的實現問題,相關資料網路上都能找到不少,故在此不再展開。(combobox和案頭系統不同,MOBILE上不支援自繪)
如果覺得描述的不夠具體可以看看下面的連結,是一些筆者曾製作的UI:http://hi.baidu.com/%C0%B6%C9%AB%D3%...5a044df04.html
其中 “按鈕GO”就是BUTTON控制項自繪
另外的COMBOBOX及顯示資料的表格控制項,都是用STATIC控制項實現的偽控制項。