这是我昨天发在新人交流版块的帖子,还是放到这边比较合适。嫌麻烦,没有叫版主转移,我自己拷贝过来咯。。。。
首先感谢 『嵌入式平台安全』版块版主加百力的邀请(貌似算不上邀请,表达不好,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控件实现的伪控件。
- 标 题:WIN MOBILE UI开发入门
- 作 者:打小
- 时 间:2009-06-06 12:17:14
- 链 接:http://bbs.pediy.com/showthread.php?t=90857