艾偉_轉載:如何開發絢麗、高效率的介面(Windows嵌入式系統)

weixin_33912246發表於2011-08-29

上篇文章中提到使用者體驗(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) 續

相關文章