首页
论坛
课程
招聘
[原创]WIN MOBILE UI开发入门
2009-6-6 12:17 8789

[原创]WIN MOBILE UI开发入门

2009-6-6 12:17
8789
这是我昨天发在新人交流版块的帖子,还是放到这边比较合适。嫌麻烦,没有叫版主转移,我自己拷贝过来咯。。。。

首先感谢 『嵌入式平台安全』版块版主加百力的邀请(貌似算不上邀请,表达不好,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控件实现的伪控件。

[公告]请完善个人简历信息,好工作来找你!

收藏
点赞0
打赏
分享
最新回复 (7)
雪    币: 115
活跃值: 活跃值 (10)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
NETTF小金 活跃值 2 2009-6-6 12:23
2
0
我想问一下楼主知道MenuBar相关的消息吗?例如如何让系统重绘一个MenuBar的背景?我用了WM_PAINT和WM_ERASEBGN都不行
雪    币: 2308
活跃值: 活跃值 (24)
能力值: (RANK:510 )
在线值:
发帖
回帖
粉丝
加百力 活跃值 12 2009-6-7 19:01
3
0
感谢 打小 的热情发帖给大家介绍一些WM开发的知识,这正是大家很需要的。

希望能经常看到 打小 成果发布。
雪    币: 191
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
soupcai 活跃值 2009-6-8 09:09
4
0
UI我们一般都是贴图的,楼主所说的COMBOBOX一般也是通过几个控件组合或者全部自已绘图都可以实现的。WIN32做界面比较麻烦一点,如果WIN32和WTL一起开发的话,开发的速度会比较快。
雪    币: 55
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
打小 活跃值 2009-6-8 10:39
5
0
不好意思,这个我没遇到过,不清楚。
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lxw2118 活跃值 2009-6-9 16:34
6
0
我也想知道这个问题
雪    币: 202
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
WisdomZh 活跃值 2009-6-24 22:43
7
0
嗯, 经常碰到手写字时碰到其他控件, 产生误操作的
不知这个怎么解决
雪    币: 7
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
KylixC 活跃值 2009-6-26 01:31
8
0
正是我想学习的,收藏了 谢谢
游客
登录 | 注册 方可回帖
返回