如何開發絢麗、高效率的界面(Windows嵌入式系統)

作者: 王克偉  來源: 博客園  發布時間: 2009-10-12 10:35  閱讀: 2806 次  推薦: 1   原文鏈接   [收藏]  

上篇文章中提到用戶體驗(UE),并且說到國內有專門去做UE的團隊也很少。據我了解Microsoft、Nokia、Google等,還有國內的Baidu是有比較專業的UE團隊。對于我們這樣的普通團隊、普通開發者來說,這樣的經驗實在太少了。而且普遍更認為UE是UI Designer的事情,與我們這樣的Developer沒有太多關系。

當然不是,UE遠超過UI。很多因素造成了UE差,比如一份不正確的數據表明17%的用戶認為手機運行速度慢,Windows Mobile手機開機漫長的等待就十分的讓我受不了。我們開發的應用是否有過優化?運行效率是否已經很讓用戶滿意了?等等這些問題留在開發中思考吧。

在使用優秀的產品時用心體會、用心觀察、用心思考,在此基礎上創新。逐漸提高UE設計能力。(等Windows 7正式發布了,我們可以討論討論其UE^^)

這篇文章僅僅討論有關界面開發上Developer涉及到的技術問題,在學習過程中,隨著越深越廣越覺得自身水平的不足,所以只敢拋磚引玉,更多希望能夠引起大家對界面開發技術、對UE的討論。

上篇文章已經列出目錄:

1.相關商用產品一覽

2.Windows系統下圖形編程的相關基礎知識

3.DirectDraw簡介

4.DirectDraw驅動開發

5.DirectDraw應用開發

6.一個推薦的入門Sample

第1部分已經在上篇文章講過,鑒于篇幅的原因2、3、4、5、6部分將在下篇文章介紹。這篇文章先介紹下如何使用Win32下的GDI等接口實現絢麗、高效率的界面。這樣我們就能發現GDI等接口的不足,進而引申到DirectDraw上面。(這篇文章默認你有一定的Windows編程基礎,熟悉GDI等概念。)

 

補充內容●如何使用Win32下的GDI等接口實現絢麗、高效的界面

1.如何讓界面絢麗?

怎么樣的算絢麗?有很漂亮的圖片?有Alpha透明?有Animation?

每個人的審美觀點都不同,所以如果你的界面很多人認為絢麗那就可以了。設計界面主要是Designer的工作,包括UI邏輯的設計,色彩搭配設計等,我認為這也可以進一步分工:熟悉用戶習慣的Designer、美學Designer等。但是一般情況下這些讓程序員給代勞了。

下面介紹Windows提供給開發人員的相關接口,利用這些接口設計你認為絢麗的界面。

2.如何透明?如何半透明?如何顏色漸變?

以下是我使用Imaging COM組件封裝的一個函數,可以使用其繪制PNG圖片,當然也可以繪制其它圖片。繪制帶Alpha通道的PNG圖片即實現了透明。

#include 
#include 
#include 

#pragma comment(lib, "Imaging.lib")

BOOL DrawPNG(HDC hDC, TCHAR *szPicString, RECT &rcDraw)
{
	BOOL br = FALSE;
	IImagingFactory *pImgFactory = NULL;
	IImage *pImage = NULL;
	ImageInfo sImgInfo;

	CoInitializeEx(NULL, COINIT_MULTITHREADED);

	// Create the imaging factory.
	if (SUCCEEDED(CoCreateInstance(CLSID_ImagingFactory,
		NULL,
		CLSCTX_INPROC_SERVER,
		IID_IImagingFactory,
		(void **)&pImgFactory)))
	{
		// Load the image from the JPG file.
		if (SUCCEEDED(pImgFactory->CreateImageFromFile(
			szPicString,
			&pImage)))
		{
			// Draw the image.
			pImage->Draw(hDC, &rcDraw, NULL);
			pImage->Release();
			pImage = NULL;
			br = TRUE;
		}

		pImgFactory->Release();
	}
	CoUninitialize();

	return br;
}

------------------------------------------------------------------------------------------------------

而封裝的這個函數實現了將一個DC根據Alpha值半透明繪制到另一個DC上,使用GDI函數AlphaBlend實現。

BOOL AlphaBlt(HDC hdcDest, int nXOriginDest, int nYOriginDest,
			  int nWidthDest, int nHeightDest,
			  HDC hdcSrc, int nXOriginSrc, int nYoriginSrc,
			  int nWidthSrc, int nHeightSrc,
			  BYTE alpha) {

				  BLENDFUNCTION bf;
				  bf.BlendOp = AC_SRC_OVER;
				  bf.BlendFlags = 0;
				  bf.SourceConstantAlpha = alpha;
				  bf.AlphaFormat = 0;

				  return AlphaBlend(hdcDest, nXOriginDest, nYOriginDest, nWidthDest, nHeightDest, 
					  hdcSrc, nXOriginSrc, nYoriginSrc, nWidthSrc, nHeightSrc, bf);

}
                      

如果你的設備支持AlphaBlend硬件加速那將是非常棒的事情,否則軟件方式會有點影響性能。

------------------------------------------------------------------------------------------------------

顏色漸變也是直接有API可以支持:

BOOL GradientFill(
  HDC hdc,
  PTRIVERTEX pVertex,
  ULONG nVertex,
  PVOID pMesh,
  ULONG nCount,
  ULONG ulMode
);

hdc
[in] Handle to the destination device context.

pVertex
[in] Pointer to an array of TRIVERTEX structures, each of which defines a triangle vertex.

nVertex
[in] The number of vertices in pVertex.

pMesh
[in] Array of GRADIENT_RECT structures in rectangle mode.

nCount
[in] The number of rectangles in pMesh.

ulMode
[in] Specifies gradient fill mode. The following table shows the possible values for ulMode.

This function fills rectangular regions with a background color that is interpolated from color values specified at the vertices.

不管你使用.Net CF平臺調用這些API,還是Win32/MFC/ATL/WTL直接調用這些API,你都是可以實現這些效果的。更多內容請查詢開發文檔,畢竟那才是最好的參考資料。

3.如何實現動畫?

動畫的原理就是一幀一幀的畫面按照時間軸向后移動,在騙過眼睛之后就成了動畫,所以你得到動畫最簡單的方法就是按照一定間隔將不同圖片一張一張繪制到屏幕上,雖然很簡單,但是在編程中經常使用這種方法。有時簡單的往往是最好的。

這里還有個技巧,比如將每張圖片使用Photoshop中的運動濾鏡模糊下,這樣使用上面方法得到的動畫會有種非常快速的感覺。也可以用類似的方法來用2D表現三維的事物,得到3D動畫的效果。

還可以使用GIF動畫的方式,比如在開機和關機時。以下封裝的函數僅供參考,我沒用心整理。

BOOL DisplayGIF(TCHAR *szPicString)
{
HANDLE hFile = CreateFile(strFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return FALSE;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
if ( (DWORD)-1 == dwFileSize )
{
CloseHandle(hFile);
return FALSE;
}
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
if (hGlobal == NULL)
{
CloseHandle(hFile);
return FALSE;
}
LPVOID pvData = GlobalLock(hGlobal);
if (pvData == NULL)
{
GlobalUnlock(hGlobal);
CloseHandle(hFile);
return FALSE;
}
DWORD dwBytesRead = 0;
BOOL bRead = ReadFile(hFile, pvData, dwFileSize, &dwBytesRead, NULL);
GlobalUnlock(hGlobal);
CloseHandle(hFile);
if (!bRead)
{
return FALSE;
}
IStream* pStream = NULL;
if ( FAILED(CreateStreamOnHGlobal(hGlobal, TRUE, &pStream)) )
{
return FALSE;
}
if( NULL == pStream )
{
return FALSE;
} IImage *pImage = NULL;
RECT rc;
IImagingFactory *pImgFactory = NULL;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
if ( !SUCCEEDED(CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void **)&pImgFactory)) )
{
return FALSE;
}
IImageDecoder* pDecoder = NULL;
UINT nCount = 0;
if ( !SUCCEEDED(pImgFactory->CreateImageDecoder(pStream, DecoderInitFlagNone, &pDecoder)) )
{
return FALSE;
}
pDecoder->GetFrameDimensionsCount(&nCount);
GUID *pDimensionIDs = (GUID*)new GUID[nCount];
pDecoder->GetFrameDimensionsList(pDimensionIDs,nCount);
TCHAR strGuid[39];
StringFromGUID2(pDimensionIDs[0], strGuid, 39);
UINT frameCount = 0;
pDecoder->GetFrameCount(&pDimensionIDs[0],&frameCount);
UINT iSize = 0;
pDecoder->GetPropertyItemSize(PropertyTagFrameDelay,&iSize);
BYTE* pBuff = new BYTE[iSize];
PropertyItem* pItem = (PropertyItem*)pBuff;
pDecoder->GetPropertyItem(PropertyTagFrameDelay,iSize,pItem);
int fCount = 0;
ImageInfo Info;
pImgFactory->CreateImageFromStream(pStream,&pImage);
pImage->GetImageInfo(&Info);
rc.left = rc.top = 0;
rc.right = Info.Width;
rc.bottom = Info.Height;
HDC   tempDC;
HBITMAP    hbmNew = NULL;
void *     pv;
BITMAPINFO bmi = { 0 };
HBITMAP    hbmOld = NULL;
tempDC = CreateCompatibleDC(NULL);
bmi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth       = Info.Width;
bmi.bmiHeader.biHeight      = Info.Height;
bmi.bmiHeader.biPlanes      = 1;
bmi.bmiHeader.biBitCount    = (SHORT) max(16, GetDeviceCaps(tempDC, BITSPIXEL));
bmi.bmiHeader.biCompression = BI_RGB;
hbmNew = CreateDIBSection(tempDC, &bmi, DIB_RGB_COLORS, &pv, NULL, 0);
hbmOld = (HBITMAP)SelectObject(tempDC, hbmNew);
pImage->Draw(tempDC, &rc, NULL);
pDecoder->SelectActiveFrame(&pDimensionIDs[0], ++fCount);
BitBlt(g_hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, tempDC, 0, 0, SRCCOPY);
delete []pBuff;
delete []pDimensionIDs;
pDecoder->Release();
pImage->Release();
pImgFactory->Release();
CoUninitialize();
return TRUE;
}

 

4.如何有較高的運行效率?

后面的內容會介紹到使用GDI這些“較高層次”的接口是很難有較高的運行效率。

但是可以使用一些技巧,比如“空間換取時間”。相信"Lazy Computation”你有聽過,延遲處理這項任務直到真正需要的時候(在編程中我們也會經常用到,需要有這個意識。)這里使用的技巧有點恰恰相反的味道,把用戶將來很可能用到的地方先處理好,然后儲存起來,而并不是等到用戶真正需要的時候才去處理。

比如使用Imaging COM組件繪制PNG圖片時,每次都需要加載組件的庫文件,然后卸載,界面可能要反復刷新,然后反復繪制PNG圖片。這時可以考慮在程序啟動的時候使用非界面主線程將繪制好的PNG圖片保存起來(比如以Device Context的形式),界面刷新的時候僅僅是BitBlt到目標設備。BitBlt的效率是比較高的,如果仍然不能滿足你的效率要求,可以考慮下面介紹的DirectDraw等技術。

上面的方法對于具有豐富開發經驗的應該比較清楚,但是新手往往會忽略。在開發界面時我們要保證一個基本原則:想盡一切辦法在現有的條件下提高界面響應用戶的速度,界面要以用戶為中心。所以開發時需要保持這個意識。

5.如何提高程序啟動速度?

第4部分說過,為了提高運行效率,可以將常用的界面在程序啟動時一起緩存到內存中,那么程序的啟動時間會大大增加,如何解決這個問題?我的建議是UI主線程僅僅加載少量的用戶啟動后直接就能看到的界面,而另起一個子線程(叫A)用于加載其它界面,其它界面加載完之后這個子線程退出,當用戶點擊其它界面時,主線程如果發現子線程A并沒有退出,說明其它界面還沒有加載完,讓用戶等待。
這么設計的好處是,將最耗時的任務分攤出去,即能保證了用戶快速看到界面,又能在之后的運行中有較高的效率。

6.如何在絢麗和效率之間平衡?

最好的方法是得到界面運行時具體的時間消耗數據,如果必要可以精確到每個函數。得到一份系統正常情況下的數據,得到幾份環境惡劣情況下的數據(比如系統非常繁忙、設備電量很少、要處理的數據非常多等)。定量的去分析解決這些問題。如果在惡劣的環境下你的絢麗界面表現的仍然不錯,恭喜你,你太棒了!

Windows CE/Windows Mobile也提供了些基本的Performance API(像DirectDraw等技術還有自己的Performance接口和工具):

BOOL QueryPerformanceCounter(
LARGE_INTEGER* lpPerformanceCount
);
lpPerformanceCount

[in] Pointer to a variable that the function sets, in counts, to the current performance-counter value. If the installed hardware does not support a high-resolution performance counter, this parameter can be set to zero.

This function retrieves the current value of the high-resolution performance counter if one is provided by the OEM.

BOOL QueryPerformanceFrequency(
LARGE_INTEGER* lpFrequency
);
lpFrequency

[out] Pointer to a variable that the function sets, in counts per second, to the current performance-counter frequency. If the installed hardware does not support a high-resolution performance counter, the value passed back through this pointer can be zero.

This function retrieves the frequency of the high-resolution performance counter if one is provided by the OEM.

上面兩個API需要OEM在OAL層提供實現,精度可以低于1ms,否則可以使用下面的API。

DWORD GetTickCount(void);

For Release configurations, this function returns the number of milliseconds since the device booted, excluding any time that the system was suspended. GetTickCount starts at zero on boot and then counts up from there.

For debug configurations, 180 seconds is subtracted from the the number of milliseconds since the device booted. This enables code that uses GetTickCount to be easily tested for correct overflow handling.

另外優化PNG、Bitmap、GIF等圖片,讓圖片清晰度和大小剛好滿足要求。

7.控件為什么如此降低運行效率?怎樣減少控件的使用?

手機軟件不同于桌面系統軟件,一方面手機的處理速度更低、電池容量更小,另一方面用戶會使用手機處理更緊急的事情。所以這也是我認為 不應該完全把桌面系統軟件開發經驗借鑒到手機軟件開發上的原因。一個240x320分辨率大小的手機界面,你給放上5、6個控件,甚至更多,這個界面注定不會太高效率,這樣的界面也不適合作為用戶最常用的界面,比如今日界面。另一方面,Windows的標準、通用控件不會有太絢麗的外觀,即使自定義的。但是這些控件能夠帶來很明顯的開發速度。所以我們要協調好。不能為了窗口而窗口,更不能一切皆窗口。
那么你會問如何協調。我的建議是能不用控件的地方就不要用,大多地方可以直接使用圖片,比如實現多狀態按鈕你可以這樣做:
WM_LBUTTONDOWN消息處理里面先判斷Point是否在按鈕的Rect中,如果是將按下狀態的圖片DC BitBlt到屏幕對應位置,WM_LBUTTONUP消息處理里面再BitBlt回來。

8.基于Win32的界面運行效率比基于.Net CF高,但是開發效率低,怎么辦?

Win32編程已經很古老、很“落后”了。但是在處理速度還不及奔三的Windows嵌入式設備上有時你不得不選擇。把界面常用的功能代碼封裝成庫(類庫也可以),積累這樣的資源可以提高團隊的開發效率。C++泛型編程就是以犧牲編譯時效率換取代碼重用,但是不影響運行時效率,值得去深入學習下,而且有現成的庫可用,比如STL。

還有其它的技術可供選擇:DirectDraw(后面介紹的)、Direct3DM、OpenGL ES等。但是開發難度較高。

9.如何使用GDI+(Native/Managed)?

GDI+是GDI的下一個版本,它進行了很好的改進,并且易用性更好。GDI的一個好處就是你不必知道任何關于數據怎樣在設備上渲染的細節,GDI+更好的實現了這個優點,也就是說,GDI是一個中低層API,你還可能要知道設備,而GDI+是一個高層的API,你不必知道設備。以下引用自MSDN文檔:

"2-D vector graphics involves drawing primitives (such as lines, curves, and figures) that are specified by sets of points on a coordinate system.

For example, the Rect class stores the location and size of a rectangle; the Pen class stores information about line color, line width, and line style; and the Graphics class has methods for drawing lines, rectangles, paths, and other figures. There are also several Brush classes that store information about how closed figures and paths are to be filled with colors or patterns.

Certain kinds of pictures are difficult or impossible to display with the techniques of vector graphics. Imaging part will resolve this problem. An example of such a class is CachedBitmap, which is used to store a bitmap in memory for fast access and display.

Typography is concerned with the display of text in a variety of fonts, sizes, and styles. One of the new features in GDI+ is subpixel antialiasing. “

Windows CE/Windows Mobile下的GDI+僅僅是Windows桌面系統的一個很小的子集。OpenNETCF中封裝了GDI+,可以為基于.Net CF的開發者提供便利,微軟提供的Native Code版本就是前面有提到的Imaging COM組件,你也可以直接調用gdiplus.dll里面的類和方法。網上已經有人將Windows CE版本GDI+不支持的部分桌面系統版本GDI+的功能整理進來,你可以使用其提供的Lib庫和頭文件進行開發。但可能不是很穩定。

Windows Mobile 6中的gdiplus.dll文件:

image

將上面的dll文件導出得到的函數:

image

10.如何實現透明控件等其它問題?

因為Windows系統目前不支持窗口Alpha透明,所以無法直接使控件背景透明,我們常用的方法是將控件后面的窗口中對應的背景作為控件的背景。

原理說的有點繞,你可以去研究下代碼:

http://www.codeproject.com/KB/mobile/transparent_controls.aspx(C++)

http://www.codeproject.com/KB/dotnet/TransparentControl.aspx(C#)

其它參考內容:

黎波的博客

怎樣在Windows Mobile上設計一個美觀的用戶界面程序(Win32)

Windows Mobile 6.0下實現自繪多種狀態按鈕(Win32)

Windows Mobile 6.0下實現自繪多種狀態按鈕(Win32) 續

1
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()