行者无疆 始于足下 - 行走,思考,在路上

Windows mobile开发总结--资源工具篇

俗话说:磨刀不误砍柴工。得心应手的工具,对于长时间的软件开发,能够起到事半功倍的效果。VS本身已经是一个非常强大的开发平台,只是在这个巨无霸的开发平台上,某些小功能还是不尽如人意。这个时候就需要插件来帮忙了。但是由于VS本省的原因,VS的插件并不像Eclipse那样丰富,我用到的有:

1、Visual Assist X:简单的说就是编辑器增强。安装后会增加VAssistX的菜单,里面主要有一些代码的snippet(比yasnippet差远了),一些查找引用等贴心的功能。VA Outline的功能比VS自身的要好很多。其余的主要是代码补全方面的增强。比如你写一个类,重命名其中一个成员的时候,会出现以下的窗口:

这是一个很方便的功能。免除了手工修改反复编译的错误和潜在的危险。另一方面,这也说明事先的设计是非常重要的。当你敲了几万行代码后却发现其中一个英语单词拼写错误的时候,再想去修改时一件非常尴尬的事情。不改吧,显得自己没水平;改吧,很麻烦。在我们的代码中,我就发现了类似的错误,关于汉字拼音输入法的命名,chinese写成了chiese,结果一直就这么用下来了……还有,我们新开发的控件,由于要仿照IPhone的效果,所以新的一套控件啊类啥的统一加上前缀'I'。可是毕竟大家习惯不同,有的是大写的'I',有的是小写的'i';还有最开始的控件都是只能支持bmp图片的载入,过了一段时间,底层引擎改进,支持png图片的载入了。可是类的成员函数很多却没有跟上。SetBitmap里面有很多载入的却是png图片,也一直这么用下来了。

2、ViEmu:简单的说就是给VS的编辑器加上vim的key bindings。官网的ViEmu还支持word和outlook,不过我用的不多。令我非常惊喜的是这个小插件竟然还支持vim键盘宏的功能。

	iLogoLabel		* m_pJnyTrnLabel;	// logol label

	ILabel			* m_pJnyTrnStaticTrain;
	ILabel			* m_pJnyTrnStaticSeat;
	ILabel			* m_pJnyTrnStaticStartStation;
	ILabel			* m_pJnyTrnStaticStartTime;	
	ILabel			* m_pJnyTrnStaticDestStation;
	ILabel			* m_pJnyTrnStaticDestTime;
	ILabel			* m_pJnyTrnStaticBookInfo;
	ILabel			* m_pJnyTrnStaticNote;
	
	// 动态框架可变信息
	ILabel			* m_pJnyTrnTitle;	// title of this window	
	ILabel			* m_pJnyTrnTrain;
	ILabel			* m_pJnyTrnSeat;
	ILabel			* m_pJnyTrnStartStation;
	ILabel			* m_pJnyTrnStartTime;	
	ILabel			* m_pJnyTrnDestStation;
	ILabel			* m_pJnyTrnDestTime;
	ITextArea		* m_pJnyTrnBookInfo;
	ILabel			* m_pJnyTrnNote;
	
	//buttons
	iIdxButton		* m_pJnyTrnDelete;			//删除按钮
	iIdxButton		* m_pJnyTrnServicePhone;	//客服电话按钮
	iIdxButton		* m_pJnyTrnShare;			//共享按钮
	iIdxButton		* m_pJnyTrnStartMap;		//出发站地图按钮
	iIdxButton		* m_pJnyTrnDestMap;		//终点站地图按钮

以上代码是一个窗口的.h文件里面的一部分,是这个窗口类的成员函数。一般来说,在构造函数里面将这么多成员初始化为NULL。没有键盘宏,我们只能动用自己的四肢,一会end,一会又点点鼠标,折腾3-5分钟,终于把这些成员都“初始化为NULL"了。有了键盘宏,这种工作只是十几秒钟的事情。进入命令模式:

  • qa开始记录宏
  • q结束宏的录制
  • @a执行宏。可以加数字参数,比如10@a表示执行键盘宏10次。

 其余的具体细节,看vim manual吧。

3、.vssettings文件:多数用VS的人可能不知道,长时间对着白底黑字对眼睛是非常有害的。找一个深色调的主题,一来延长显示屏寿命,二来有效保护眼睛。在这方面vim和emacs是榜样,他们都有非常多的color-theme。vim本身是内置color-theme的,emacs的color-theme需要插件支持,我偏好于黑底白字的color-theme——bash终端就是这个颜色,比较cool,呵呵。在word2003里面也有设定默认背景色和字体颜色的选项,较好的方案是蓝底白字,对眼睛比较好;但是到了word2007里面这个功能却不见了,不知道MS是怎么想的呢。好在WPS有这个功能,所以我现在用WPS。废话说了这么多,在VS里面,也是可以设置字体和颜色的。工具-选项-环境-字体和颜色:

但是一项一项的设置不仅非常麻烦,而且由于80%的程序员都没有与其编码能力相称的美工配色能力,所以这个方案基本是不可行的。剩下的,就是前人栽树,后人乘凉。Google: filetype:vssettings,找到自己喜欢的配色文件,直接导入即可。

 4、git-extentions:我对版本控制并不是特别了解,主要是自己经验不足,没有多少实际的开发经历。只是在去年的java课上,自己像模像样的用了一下git。对版本控制的印象也只停留在了“好像是个时光机”这个层次上。目前主流的版本控制应该是cvs、svn和git吧。cvs廉颇老矣,逐渐被svn取代,git是Linus几个星期内写的,却很快风靡了开源世界,还曾引发了一场c和c++的论战。svn的各种插件比较成熟,项目组最开始的引擎也是用svn做版本控制,用的插件是tortoisesvn。VS相关的集成插件是AnkhSVN,我也安装了。但是老实说,我不太喜欢。最不喜欢的是svn在项目目录的每个目录下都创建一个.svn目录,让我十分不爽。而且还有自己架设svn服务器,apache啥的,我看着就头大了。哪如git,一条git init语句就可以投入使用了。

就这么多,Visual Assist X和ViEmu是共享软件,需要发挥中国人的聪明才智破解一下。后面的免费的。

刀已经磨好了,怎样砍柴又是一个问题。因此对嵌入式尤其是wince和windows mobile平台上相关知识的了解是至关重要的。网上有一些非常不错的博客和资源如下:

  • 克伟的博客:里面的一篇《如何开发绚丽、高效率的界面(Windows嵌入式系统)》,讲的非常透彻,深入浅出,非常适合向我一样的新手入门。
  • LinuxGraphics:难得的介绍各种GUI体系结构原理的网站。是我见过的所有讲GUI体系的网站中最好的一个。
  • cexer:这个博客的作者对gui框架体系有相当的研究,里面有一些GUI实现机制的源码,值得学习。
  • The linux mobile development:这个博客有个ftk项目,想学习开发嵌入式GUI的同志们可以研读一下代码。别的开源代码如gtk、qt、minigui等都太庞大了。这个项目刚开始起步,整体来讲研读起来比较容易一些。

当然,“纸上得来终觉浅,绝知此事要躬行”,自己动手实践才是最重要的。 

Windows mobile开发总结--文本控件篇

 这个项目是在自己已有的代码基础上,在windows mobile 6.x平台下开发出一套商旅个人助理软件,软件主要内容是世博相关的机票、酒店、火车票、行程安排等,界面要求是仿照Iphone风格。因为客户公司有了Iphone和Android平台上的成品,但是可能时间紧急,又缺乏mobile平台上的控件代码基础,所以找到了我们这边。

原来的代码基础是一套GUI的控件引擎。利用GAPI,从最底层写起,包括最基本的画点画线函数、显存操作函数、窗口管理函数、控件类继承体系等等。这套体系的第一个产品是宁波公安的移动警务系统。去年的时候我也曾经有所了解,但是终究因为自己课业繁忙没有真正参与进来。这次是个机会。

开始的时候我对这套东西很是抵触。一来是我本身不太欣赏windows的哲学。在进入实验室之前的半年都在linux下生活;二是我觉得重新写这么一套东西是"reinvent the wheels"的愚蠢做法。我还像导师建议Qt。坦白的说,虽然我从来没有用Qt写过一个像样的东西,但是Qt的信号与槽机制,感觉要优雅的多;三是除了Qt,MiniGUI据说也不错,为什么要自己大费周章地写这么一个东西呢?而且我虽然很菜,但是写这么一坨代码,我还是能看出很多问题的——虽然让我去写我肯定写不出特别好的东西。

事实上:

  • 我很讨厌某些宏。大量的宏让程序显得莫名其妙。Win32 API中有大量的宏,什么handle, hwnd, COLORREF等等,虽然这些宏归根到底不过是个int,但是我觉得很多时候还是KISS原则最重要。直接用int好了。干嘛这么麻烦。
  • 我很讨厌某些typedef。明明有了标准的true和false,为什么还要自己去定义个GTrue、GFalse和GOK呢?还有GRESULT?这大概是受windows编程风格的影响吧。
  • 我很讨厌注释不全、命名风格糟糕的代码。

当然,GUI框架的设计是一件很复杂的事情。具体来说可以分为三层:

  • 底层引擎层:包括基本的显存操作,画点画线,alpha渲染,图片压缩载入,图形学算法、消息事件处理、字体引擎、布局管理器、窗口管理器等等。
  • 控件层:控件继承体系、文本类控件(标签,单行文本编辑,多行文本编辑域),按钮类(button、list等)、滚动条类、光标类、复杂控件类(菜单、表格等等)
  • 应用层:这个算最简单的了。只要前两层足够稳定,应用层是手到擒来的事情。其余的事情如数据库连接、xml解析、网络连接等等,属于额外附加功能。

这里有份GUI系统需求描述,可以参考一下。嵌入式的GUI框架还是要简单一些。当然,这也与嵌入式系统的硬件条件是息息相关的。

实验室的这套框架主要是仿照Windows消息机制写的。很多东西保留了Win32 API的痕迹。事实上底层引擎是不可能脱离具体的操作系统而存在的。跨平台的原理就是在统一的接口下面提供不同的内部系统实现。

最开始接触这个项目的时候我心中很是没底。一来我从来没有接触过4W+行代码的项目,二来我对windows消息机制那一套也不是很了解。所以我就花了将近200大洋买了经典的《windows程序设计》,自己在那里面吭哧吭哧啃了好几天,看了200多页。终于算对windows消息循环机制有了初步的了解。

3月28号开会,3月29号熟悉整个系统体系。开始做控件。我被安排的任务是做文本类的控件:

  • ILabel:静态文本标签。
  • ITextField:单行文本编辑框。
  • ITextArea:多行文本编辑框。

三个文本控件统一要求:

  • 支持png图片载入
  • 支持字体类型和大小,
  • 字体颜色,
  • 背景颜色,
  • 圆角透明。
  • 支持编辑锁定和光标定位。

说起来容易做起来难。底层控件的开发往往比上层应用的开发要难的多。写到现在,代码总量写了180k,估摸着6000行应该有的(虽然有很多框架性copy&paste的东西)。总共写了3个文本空间和8个窗口界面。3:8,由此可见一个小小的文本编辑框并不是我们想象中的那么容易。具体来讲,在画点画线画矩形这写仅有的GDI函数的基础上,你需要实现:

  • 如何支持不同的风格(字体颜色,大小,背景颜色,alpha渲染风格);
  • 如何支持png图片的载入(我设计的单行文本框支持一张mainPng和两张leftPng、rightPng的载入,并根据鼠标是否点击在leftPng和rightPng图片上进行不同的处理,如清空文本,确定查询等等,载入png事件简单的事情,但是由此引发的光标定位问题却让我无比头疼);
  • 如何支持光标定位(这是文本框设计中非常复杂的一个问题。可能你不相信,等宽字体(monospace)的发明就是因为早期的电脑画面显示、打字机,由于技术的局限,无法进行字母宽度的比例调整,因此将每个字元都制作成一样的宽度)。说到这里应该明白了吧。当你点击光标的时候,需要根据你的光标的坐标,判断在整个字符串中最合适的位置。总不能让光标在一个字体的中间闪烁吧……原理也是很简单的,无非就是建立个链表,每个node的结构如下: 
struct char_node
{
    wchar_t * ch;
    int ch_width;
    int ch_height;
}

        然后根据光标的位置从头开始遍历链表,最终确定光标位置。但是,当你面对一个1k行的代码基,里面按照原来的机器写死了各种可恶的绝对坐标的时候,你想对 它进行改进,就不是说说那么简单的事情了。

  • 如何支持字体的滚动(当文本输入到边缘时——拿单行编辑框为例,正常情况下文本应该向左滚动,光标始终在最右端的边缘闪烁)。老的平台控件是支持这个功能的,这让我很开心,但是开心了没多久,我发现了一个很严重的bug,就是在整个文本字符串在向左滚动的时候,文本字符会画到整个文本控件的左边……这显然是一个不可饶恕的bug,但是到现在我仍然没有搞定。因此现在我的ITextField,当你的文字字符串输到文本框右边的时候,光标仍然继续向右移动——于是就会消失不见了。
  • 如何支持不同的字体大小(我发现在原来的代码基础上改造而来的ITextField对光标的定位简直是一塌糊涂。经过我一番实验,发现当用29号字体的时候光标的定位效果是最好的,于是我统一把我的文本控件的字体改成了默认的29号大小。陈老师告诉我们说“软件软件,越软越好……”,我当然明白这个道理,但是,写“软”是需要时间的。某些时候,为了赶进度,我们不得不牺牲某些扩展性来追求暂时的能用性。所以我的东西到现在,光标定位依然是很大的问题)
  • 如何支持不同字体载入?据我所知,字体有点阵字体和矢量字体,矢量字体又有OpenType和TrueType等,这些字体的内在原理是什么?什么叫衬线字体?什么叫非衬线字体?为什么一般文章排版正文都要用宋体?等等,这是非常有研究的一个话题。
  • 如何支持文字选块?
  • 如何支持多行文本域的文字折行?
  • 进一步,如何支持标点压缩和头尾压缩(这是一般字处理软件的事情了……)。
  • 再进一步,我们只知道英语和汉语,陈老师说阿拉伯文的文字连着写和分着写也是不一样的,有特殊的规则,我们又该如何支持?

由此可以看出,文本编辑框虽然看似简单,实现起来却要涉及到很多很多的知识和细心斟酌。

当然,以我的水平是不可能在这么短的时间内写出一个完备的文本编辑框的。可取的方法就是模仿、修改。老的单行文本编辑框叫做GTextField,GTextField的邻居如下所示:

我呢,则丝毫没有客气,仿照主要的函数接口,框架代码,对GTextField做起了外科手术。一个好的医生应该是内外兼修。做这么一个东西自然需要对底层的东西有比较深入的了解。可是一来这一套东西是我们实验室自己yy出来的,很多尚达不到工业标准,也没有现成的教程指南,代码注释又不是特别完备,所以自己理解起来颇有些困难;二来很多东西急于求成,所以有非常多莫名其妙的1、2、3、4、5,只有通过自己的实验看效果,将这些12345变成const int default_xx_margin = 3……

经过我的改造,除了光标定位和字体大小,其余功能基本实现,只是代码写的比较恶心,自己都不忍去看了。

无怪乎,Knuth大人一个TeX系统写了十年时间,经过别人改造成LaTeX、ConTeXt、Omega、LuaTeX等等至今尚未完善;无怪乎求伯君大神当年十万行汇编代码的WPS1.0使他成为了全中国程序员的偶像。

当我们费了九牛二虎之力做出来一个可以用的东西时,却发现那个东东是如此的丑陋,以至于连自己都不敢去看它。有了这样的经历,再去使用Windows 7,Compiz Fusion,会多一份敬重之情。简洁优美的背后,隐藏着多少心思和功力。

这是我现在写出来的最终效果:

样式还不错,点击右边小按钮的时候还能清空文本。当然,更灵活的设计时发送个消息,让用户自己处理决定该做什么。

这是头文件,单行文本编辑框的类定义: 

/**************************************************
ITextField: 单行文本编辑框
功能描述:至此png图片载入。支持单行文本编辑。支持锁定操作。但是光标定位和字体大小还存在问题。
作者:Xiao Hanyu <xiaohanyu1988@gmail.com>
参考:GTextEdit
**************************************************/

#pragma once
#include "SimpleCtrl.h"
#include "TwoWayLinkList.h"
#include "DataTypeDef.h"  //-------------
#include "string"
#include "ThTimer.h"
#include "IStyle.h"
#include "TextArea.h"
#include "MCaret.h"
#include "WndContainer.h"
#include "GDIFactory.h"
#include "DrawDevice.h"
#include "GDIPen.h"
#include "GDIBrush.h"
#include "CombinedCtrl.h"
#include "AllCtrlManager.h"
#include "BaseWnd.h"

#define	ITF_TXT_CHANGED		(MD_USER_BEGIN + 1)			// 只要文字输入改变,就发送该消息
#define	ITF_LEFT_PNG_CLICKED	(MD_USER_BEGIN+2)		// 只要设置mainPng和leftPng,如果点击leftPng,就发送该消息
#define ITF_RIGHT_PNG_CLICKED	(MD_USER_BEGIN+3)		// 只要设置mainPng和rightPng,如果点击rightPng,就发送该消息

const int ITEXTFIELD_MAX_LEN = 300;
//设置边距,即绘制光标和文字时,距离边缘的最小值
const int ITF_EDGE_BORDER = 2;

class MCaret;

class ITextField : public SimpleCtrl
{
public:
	ITextField(GisHWND pBWnd, ControlID id);
	virtual ~ITextField();
	Boolean ShowCaret();
	Boolean HideCaret();

public:
	//父类继承需要重写函数
	virtual GRESULT Init(int cx, int cy, int nWidth, int nHeight);
	virtual GRESULT UnInit();
	virtual void Redraw();
	virtual Boolean setDisabled(Boolean b);
	//Mouse Msg Action
	virtual GRESULT OnMouseDown(MouseButtons mbtn,int x,int y);
	virtual GRESULT OnMouseUp(MouseButtons mbtn,int x,int y);
	virtual GRESULT OnMouseDBClick(MouseButtons mbtn,int x,int y);
	virtual void SetFocus(Boolean bFocus);//设置本文本框为鼠标焦点
	virtual void SetVisable(Boolean bVisable);		//重载 当不可见时,失去焦点
	//Msg proc
	virtual GRESULT ProcCharMsg(WPARAM wParam, LPARAM lParam);
	//控件文本内容操作
	//获得当前文本
	wchar_t* GetText();
	//设置文本内容
	void SetText(const wchar_t *content);
	//清空文本内容
	Boolean ResetText();
	static void CALLBACK DoTimer(UINT uTimerID,DWORD dwUser,DWORD dw);
public:
	bool getCharVisable();	//输出文本框字符是否可见
	bool setCharVisable(bool p);//设置文本框字符是否可见
	void SetAutoOpenSip(bool p);//设置是否自动打开键盘
protected:
	//根据传入的x,y判断在字符串中的具体位置
	//x,y,在控件中的相对坐标
	int GetPosInString(int x,int y);
	//通过字符序号活得光标偏移位置长度
	int GetCaretFromCharIndex(int nCharIndex);	
	//获取当前整个字符串的长度
	int GetStringLength();

	//pos:在m_szText中的位置
	Boolean InsertCharInString(wchar_t cdata,int pos);
	Boolean DeleteCharInString(int pos, wchar_t* pCharOut);
	ThTimer m_Timer;	//光标显示定时器

public:
	IStyle GetStyle();
	void SetStyle(wchar_t* fontType, COLORREF fontColor, int iMode, int fontSize, UINT textAlignment, COLORREF bgColor, int alpha, bool round);
	Boolean ResetStyle();
	
	virtual GRESULT SetPngImage(wchar_t* main_png, wchar_t* left_png = NULL, wchar_t * right_png=NULL);
	GRESULT SetEditable(bool editable);

private:
	bool isCharVisable;		//决定文本框字符是否可见的变量,值为true,则输入的字符都可见,
	//值为false,则输入的字符均显示为*号,一般用来输入密码使用
	MCaret* m_pCaret;
	
	//保存文本框字符内容的数组,容量有限,依据ITEXTFIELD_MAX_LEN的值来决定容量
	wchar_t m_szText[ITEXTFIELD_MAX_LEN];
	//根据输入的字符串长度来填充适量的*号,这个变量就是一串*号的首地址
	wchar_t s_szText[ITEXTFIELD_MAX_LEN];
	
	//字符串显示开始位置
	GRect m_rcText;
	
	//字符串链表,因为不仅要保存字符本身,还要保存字符的显示宽度等信息,因此需要用到链表存储
	TwoWayLinkList m_CharList;
	
	//当前光标对应的字符串中的字符位置
	int m_nCurCharPos;
	//当前字符串的字符总数
	int m_nCharNum;
	bool m_bAutoOpenSip;
	
	bool i_tfEditable;					// 是否可编辑
	
protected:
	IStyle i_tfStyle;					// 控制textfield的文字风格
	enum
	{
		i_mainPng = 0,
		i_leftPng,
		i_rightPng,
		StateCount
	};
	
	GImage	i_tfArrPng[StateCount];		        // textfield目前支持三张png图片
	Boolean i_tfSetMainImg;				// 是否设置mainPng?
	Boolean i_tfSetLeftImg;				// 是否设置leftPng?
	Boolean i_tfSetRightImg;			// 是否设置rightPng?
	//HFONT	i_tfFont;				// 保存控件的字体信息,用于光标定位和字体大小
};

多行文本域的设计还要复杂一点。所以这个控件至今有很多的bug,有非常多的改进之处。

除了控件层开发,到现在为止我还写了行程业务的8个窗口页面。在写的过程中思考了很久“如何把软件写软的问题”。但是由于没有系统了解过设计模式,对c++强大的继承用的又不熟,因此现在无法做出自我满意的总结。

还打算总结下这一个月来用VS2008及相关软件的一些小技巧。毕竟磨刀不误砍柴工。

水平有限、敬请批评指正。 




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee