九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

Linux下的blog writer -- bilobo测试


bilbo 测试成功,Linux总算有办法可以发点东西了!!!

原来在Linux下是通过FireFox发的,但是CSDN对Firefox的支持有问题,在线编辑会丢失格式,更严重的是FireFox提交的文章只能用FireFox才能看到,害得当时我一批文章全部重新发表了一次,不知道现在好了没有,总之我是没有再敢用FireFox发表文章了。

最近总算在KDE下找到了blog writer了!

bilbo虽然不算太好用,分类取不会来,最近文章取不回来,不能设置文章发表的分类,还有如下代码不好贴的问题。。。。。。但是起码,当我想突然看到某个东西想转载的时候,不用再切换到Windows下了。

bilbo是KDE下的离线博客写作工具,主页是:http://bilbo.gnufolks.org/

ubuntu可以在这里下载:https://launchpad.net/~neversfelde/+archive/ppa/+packages

贴代码测试:

#include <GL/glut.h>

#include <stdlib.h>

void display(void)

{

/* clear all pixels */

glClear (GL_COLOR_BUFFER_BIT);

/* draw white polygon (rectangle) with corners at

* (0.25, 0.25, 0.0) and (0.75, 0.75, 0.0)

*/

glColor3f (1.0, 1.0, 1.0);

glBegin(GL_POLYGON);

glVertex3f (0.25, 0.25, 0.0);

glVertex3f (0.75, 0.25, 0.0);

glVertex3f (0.75, 0.75, 0.0);

glVertex3f (0.25, 0.75, 0.0);

glEnd();

/* don't wait!

* start processing buffered OpenGL routines

*/

glFlush ();

}

void init (void)

{

/* select clearing color */

glClearColor (0.0, 0.0, 0.0, 0.0);

/* initialize viewing values */

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);

}

测试结果:格式丢失,颜色丢失,使用bilobo的代码标记工程无效。


试着贴个图

格式测试成功,大小正常,不会像Word2007一样乱掉。

测试结果:完全成功。

阅读全文....

VS2008中自定义C++工程模板与修改新建文件默认编码的办法

VS2008中自定义C++工程模板与修改新建文件默认编码的办法

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

一、   需求

网上讲VS(以下VS均特指VS2008)自定义模板的不少,不过大部分都是讲C#或者Web开发的,什么export template的,都不适合C++工程,由于MS的确是减弱了对C++的支持,(这点没有人否认吧?)所以在VSC++总是被遗忘在那个没有人理睬的角落,所以现在很多人宁愿还用着C++MFC最辉煌的版本VC6.0,哪怕那个版本的C++这么不符合标准。。。。。既然MS不关心C++使用者,那就只有自己奋发图强罗,还好再怎么样,没有了VS,我们总还是有VIM+G+++GDB/Emacs的,没有了MFC我们还有QtGTK+的,何况个人用EclipseLinux下做Qt程序的感觉也不错,闲话不说了,看看正题。

自定义工程模板是用途很大的,当你有一个简单的模板用于开发的时候,没有VS内部的支持时,你有两种办法,其一你将原有整个工程拷贝一份,然后改工程名(在我以前的公司就是这样干的),改名不说,要进VSS,还得改工程的GUID(不懂的就算了),其二是新建一个工程,然后一个一个添加文件,假如有自己的目录结构的时候,还得重新控制目录结构,工程大时也是异常麻烦,其实这些用上了VS自定义模板,一切都可以很简单,就像你进行Win32 SDKMFC开发MS给你的模板一样。

修改VS中新建文件的默认编码嘛,纯Windows人士请忽略,我是因为需要在Linux下编辑和查看这些文件才有此需求。事实上,即使想要在Linux下查看或者编辑这些代码也可以不做改变,但是我就是想将其全部改成UTF8,不喜欢将Linux下设置一大堆东西,gcc需要加gb2312的选项,vim需要加配置,Eclipse也需要改配置,其他文本编辑工具也需要选定编码,全部是因为VS在中文Windows下的默认编码是gb2312(我用的是英文VS2008,这个可能是跟着操作系统环境走的),虽然也不怪MS。。。。既然VS也支持UTF8,就让Windows迁就Linux了。。。。还需要迁就的是Linux下的换行与Windows不同,顺便一起解决了,虽然大部分时候,在公司,都是用Linux的东西来迁就Windows。修改默认编码的办法我在网上找了很久也没有找到,大部分人都说是直接用Advanced Save Option来选择保存,那样多麻烦啊,或者先建立工程以后用find批量改,那样也不见的简单啊。。。。。。。。看下面的解决办法。

 

二、   解决办法

1.      新建文件

奥秘在Microsoft Visual Studio 9.0/VC/vcprojectitems中(Microsoft Visual Studio 9.0根据你安装目录而定)

新建的C++文件,主要是newc++file.cpp这个文件,目前是空的,你将其改成什么样,那就是什么样,比如,我需要在每个文件的头部添加一大串的GPL说明,(不是我想,这是FSF规定的),那么就直接在此文件添加我的文件头即可,以后用add->add new item->C++ files时,就都会自动带着文件头出来了。。。更好用的是,我现在将此文件转为UTF8+Unix换行格式,那么以后新建的文件都是如此了。。。呵呵,此谓之一劳永逸。

新建的头文件是hfile.h这个文件,如上更改,一劳永逸。事实上,我将上面的文件复制一份,重命名为hfile.h^^

普通文本文件由于常做Readme用,也改了。是text.txt这个文件。

 

2.      授人以鱼不如授人以渔

我当然不是一个一个文件试出来的,也不是猜出来的,授人以鱼不如授人以渔,这里告诉大家怎么去自己摸索。上述三个文件有个共同特点,那就是可以通过右键add->new item,那么我首先查看的就是new item了,这里,我的Visual C++下的下面有UICodeDataResourceWebUtilityProperty Sheets7个选项,与Microsoft Visual Studio 9.0/VC/vcprojectitems下面的7个目录及目录名一一对应,很明显了,这些选项由此7个目录中的内容控制的,首先看Code目录,一个code.vsdir的文件,打开一看

 ../NewC++File.cpp|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|#1075|10|#1076|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|9031|4096|#1077

../HFile.h|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|#1078|15|#1079|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|9030|4096|#1080

../IDLFile.idl|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|#1084|20|#1085|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|9035|4096|#1086

../DEFFile.vsz|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|#1087|35|#1088|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|9036|4096|#1089

../addmc++componentclass.vsz|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|#1236|85|#1237|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|9563|4096|#1238

../installer.vsz|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|#1245|85|#1246|{1B027A40-8F43-11D0-8D11-00A0C91BC942}|9564|4096|#1247

咋一看,乱码?2进制文件?的确像,再仔细看看,前面的一部分,那不就是菜单中有的Items项?一一对应的。再仔细看,发现其实前面的字符串都是一个目录,指向父目录下的各个文件,其中前面两个就是.. /NewC++File.cpp.. /HFile.h两个文件,那就是上一节中我发现的两个文件,对应新添加的C++文件和头文件。vsdir后面的东西是更详细的说明,长串的{}GUID#xxx是通过ID来表示资源。但是这里我们不去详细了解了,想要详细了解的参看MSDN这里,同理,文本文件在Utility目录下的VCUtilityItems.vsdir制定的,我就不多说了。

 

3.      自定义工程模板

新建工程中有个Custom Wizard选项,我将其命名为Win32OpenGLWizard。建立以后是一个完整的Wizard工程,天哪,一看,真复杂,我仅仅想建立一个简单的C++工程嘛,至于这样复杂吗?很显然,MS设计VS的自定义模板的时候就是给其他软件开发商用的,根本没有考虑过个人使用-_-!因为其中包含了HTML,CSS,JS….并不是每个人都是做网页的**。。。。作为用C++的我们,就知道想办法摸索摸索罗,符合我们的要求就好。要将引导框设计的多么完美我个人是没有什么希望了。所以建立Win32OpenGLWizard工程时,用户的UI选择就不需要了吧,直接通过名字就好。

首先利用刚才新建的Wizard去建立一个工程,(会出现在New ProjectWizard下,实际的文件放在 我的文档/Visual Studio 2008/Wizards中)发现我们主要关注的是Template Files下的两个文件,那就是生成后工程会包含的文件。尝试一下。

template Files下添加main.cpp,修改Readme.txt,删除sample.txt,实际操作时请将文件添加到/Templates/1033目录下面去,并且手动修改Templates.inf添加进main.cpp。新建工程后果然可行。

 

4.      修改自定义工程模板

有个问题是Readme.txtSource File目录下,而main.cpp跑到Source Files外面去了。作为半个完美主义者,虽然工程已经能编译通过,但是还是有点不爽,我希望进一步改进,那么下一步看看Source Files是哪里规定的罗,在default.js中可以看到

function AddFilters(proj)

{

       try

       {

              // Add the folders to your project

              var strSrcFilter = wizard.FindSymbol('SOURCE_FILTER');

              var group = proj.Object.AddFilter('Source Files');

              group.Filter = strSrcFilter;

       }

       catch(e)

       {

              throw e;

       }

}

此函数,已经明白一半了,此函数不就是寻找匹配的过滤文字,然后将其加入Source Files中去嘛。^^就看SOURCE_FILTER是什么symbo

这里分两种情况:

wizard UI

假如没有添加Wizards UI可以在添加的wizard工程下看到一个vsz文件,我这里如下:

VSWIZARD 7.0

Wizard=VsWizard.VsWizardEngine.9.0

 

Param="WIZARD_NAME = Win32OpenGL"

Param="ABSOLUTE_PATH = D:/MyDocument/Visual Studio 2008/WizardTemplate/Win32OpenGL/Win32OpenGL"

Param="FALLBACK_LCID = 1033"

Param="WIZARD_UI = FALSE"

Param="SOURCE_FILTER = txt"

 

这就是一个简单的类INI文件,详细信息可以参考MSDN这里。我们关心的自然是SOURCE_FILTER参数,改成cpp,竟然没有效果,不知道其他人怎么样,我这里没有效果的原因不明。但是虽然不知道此js wizard.FindSymbol的实现方法,但是我们还是可以用暴力解决!呵呵,将上述js函数改成如下形式:

 function AddFilters(proj)

{

       try

       {

              // Add the folders to your project

              //var strSrcFilter = wizard.FindSymbol('SOURCE_FILTER');

              var strSrcFilter = 'cpp';

              var group = proj.Object.AddFilter('Source Files');

              group.Filter = strSrcFilter;

       }

       catch(e)

       {

              throw e;

       }

}

就好了。我得意的笑啊,我得意的笑^^对于MS奇怪的失灵,有的时候暴力也是必须的。

 

wizard UI

假如添加了UIMS就要考虑你在wizard UI中改变此Symbo的可能了,会在HTML/1033/default.htm这个UI管理文件下看到

<SYMBOL NAME='SOURCE_FILTER' TYPE=text VALUE='txt'></SYMBOL>

改之

<SYMBOL NAME='SOURCE_FILTER' TYPE=cpp VALUE='cpp'></SYMBOL>

有效。

再次创建工程,cpp文件已经在Source Files中了,ReadMe.txt也死出去了,以后再需要头文件什么的也一样处理就好了。

 

5.      接近完美

好了吗?已经很好了,但是作为3/4个完美主义者,我还有有点想改的地方,那就是我平时建工程,很简单的工程主文件与工程名一直,这样万一哪天想全部拷贝到一起管理也方便,但是按上述方式文件将全部是main.cpp,解决之,电脑上没有咋程序员解决不了的事情。

看上述js脚本,如下函数:

function GetTargetName(strName, strProjectName)

{

       try

       {

              // TODO: set the name of the rendered file based on the template filename

              var strTarget = strName;

 

              if (strName == 'readme.txt')

                     strTarget = 'ReadMe.txt';

 

              if (strName == 'sample.txt')

                     strTarget = 'Sample.txt';

 

              return strTarget;

       }

       catch(e)

       {

              throw e;

       }

}

 

再看看它的使用,就知道此处就是我们需要修改的地方,实际上MS已经考虑了这样的情况了,我们不需要暴力^^改成如下内容:

function GetTargetName(strName, strProjectName)

{

       try

       {

              // TODO: set the name of the rendered file based on the template filename

              var strTarget = strName;

 

              if (strName == 'readme.txt')

                     strTarget = 'ReadMe.txt';

 

              if (strName == 'main.cpp')

                     strTarget = strProjectName + ".cpp";

 

              return strTarget;

       }

       catch(e)

       {

              throw e;

       }

}

测试,OK

 

三、   后记

所有的说明,版权信息都在,文件内容,编码,换行符都搞定了,文件名也符合我的要求了,接近完美,鉴定完毕!

此过程发狂的得不到MSDN满篇.Net的帮助,又难领悟广大C#网民的旁敲侧击,独自在VS的漆黑中摸索,逢JS大山则开路,逢HTML大石则劈之,屡次撞壁,失败数回,叹MS之弃cpper至此,捶胸顿足,奋发自强,吐血三升,乃作此文,以兹纪念。

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 



阅读全文....

分布式的,新一代版本控制系统Mercurial的介绍及简要入门

分布式的,新一代版本控制系统Mercurial的介绍及简要入门

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

在经历了《版本控制系统(RCS)的选择与比较》后,选择了Mercurial下面给大家介绍一下Mercurial

一、   提要

本文以假设你已经了解基本的版本控制系统知识,知道诸如库,历史,提交等常见的概念,本文简要的说明了在Windows/Linux下的可用安装版本,在Google Code上建库,管理,修改,提交,取回的过程,基本上仅是平时开发需要的最基础的一些功能。(连merge都没有说)因为要掌握任何可用的版本管理系统的全部功能都是一个复杂的系统过程,本文并不期望能包含全部内容,仅仅希望作为一个对Mercurial的介绍及入门引导,并不对基本的版本控制概念进行过多的描述。想要进一步学习Mercurial的可以参考最后的参考资料,个人比较推荐用《Mercurial 使用教程》入门,然后需要的时候可以查看《Mercurial: The Definitive Guide》。

 

二、   介绍

Mercurial与一般传统的版本控制系统的最大区别在于分布式的概念。所谓分布式,就是指没有一个所谓的集中的中心(central)库,这个库一般由svn server(svn)vss administrator(vss)控制,而Mercurial就没有这样的一个库,所以使用版本控制的时候甚至都不需要一个administratorserver,本地直接建库,直接就使用,任何一个库都可以作为中心库,每个库在Mercurial看来都是平等的。当然,实际使用的时候,可以人为的去指定一个中心库以作为发布,但是,这里强调的是Mercurial本身不关心这个,对它来说都是一样的。Linus他在演讲的时候多次说集中式的版本控制系统没有前途,因此,Subversion的开发者想要开发一个更好的CVS其实是脑子出了毛病-_-!呵呵,实际上,他虽然说得比较过,但是分布式的版本控制的确是比集中式有很多优点。

首先,分布式最大的好处就是离线工作,不仅意味着可以不联网就享受版本控制的好处,并且也意味着普通的提交速度也要快的多,而且,以此带来的巨大灵活性甚至能改变你的工作方式,因为以前集中式的版本控制系统,每次提交都会影响到他人,以至于不能提交未经测试的版本,而使用分布式的版本控制系统时,你可以随时随地的本地提交,安全的保护自己的工作成果,以防意外,也能随时随地的本地clone,本地分支,本地就是一套完整的版本控制系统!直到修改到最终版本,然后才push(相当于集中式版本控制的commit)到真正的一个公用库上去。想到那当年作为一个新员工,每次提交代码都需要请示总监的日子。。。。那是多么痛苦的啊。。。。

其次,对于个人开发者来说,使用集中式版本控制系统的时候有没有想过,仅仅是一个人工作,为啥偏要建立一个中心库,然后每次将自己的代码提交到那上面去啊?自己本地一个副本,库还得有一个副本,也不环保嘛。呵呵,甚至,我以前还为此专门架设了自己的VSS服务和SVN服务器-_-!而用分布式的版本控制系统就没有那么麻烦了,本地建库,直接使用就好了^^只有用过后才知道原来一切都那么简单。。。。。。。。。。。。。并且,当你哪天想要将工作成果share出去的时候,也不麻烦,还是一条普通的push命令,就像远方的服务器一直搭建好了一样。

但是,其实目前来说,分布式的版本控制系统还是有一定缺陷的,比如权限控制的问题,这点可能因为Mercurial的用户群主要在于开源世界,所以没有太过重视,实际上对企业开发可能会比较重要,相对来说SVN,特别是VSS就要好的多。但是,事实上通过外部的手段进行权限的控制还是可行的(比如ssh的登录),只不过相对来说会复杂一点,这也算是个小缺陷了(可大可小)。然后Mercurial相比Git还有个缺点,那就是分支的时候不能对单独的子目录进行,一次clone就是一个工程,这样希望在一个大工程中对一个小项目进行分支,会比较麻烦,这点也算是比较大的缺点了,但是,上述缺点都不是分布式版本控制固有的,仅仅是目前Mercurial的实现的问题,相对来说,分布式和集中式这样工作方式上的区别才是最主要的,毕竟Mercurial还年轻,希望Mercurial将来的实现会更好^^

 

三、   安装

1.      Windows

Windows可使用的版本有3种,首先是官方的版本:http://mercurial.berkwood.com/ ,最新版是1.3.1

2009-08-08: Mercurial 1.3.1 - Release version

其次是可爱的乌龟,不过不叫TortoiseMercurial,而是Tortoisehg(因为Mercurial在命令行的命令是HG,不会有人喜欢Mercurial这样长的命令吧-_-),在http://bitbucket.org/tortoisehg/stable/wiki/download,找到下载地址,最新版是0.8.2 ,此外,此网址还有个中文的注册表文件zh_CN可以下,下载后直接打开,可以使的右键菜单的语言编程中文(但是设置中的语言还是E文,为了统一,建议还是用E文的比较好) (BTW:乌龟真是版本控制之王啊。。。。。。。。。从CVSSVNMercurialGit是无所不包啊。。。。。。)

最后,假如仅仅是使用VS工作的话,也有VS的集成版本,叫Mercurial SCC Package,在http://www.newsupaplex.pp.ru/hgscc_news_eng.html有最新的版本下载。

要说明的是,Mercurial遵循的是典型的Unix风格,即自己只做命令行功能,图形界面留给了别人做,所以官方版本仅仅只有命令行功能,TortoiseHg是在Windows下较好的一种,这里向大家推荐一下。实际上安装完TortoiseHg后,已经包含了完整的官方版本,毕竟,TortoiseHg仅仅是Mercurial的一个GUI前端而已。下载后,安装的流程就不多说了,无非就是下一步。。。。不会的话也不会想要使用这个软件了。(安装后,想使用中文的就再打开上面的中文菜单注册表文件。)      再然后,就可以在右键中看到菜单。

 

2.      Linux

对于官方版本来说,Ubuntu下利用

apt-get install mercurial

就行

Redhat就用yum吧,都有可用版本。不过在我的kubuntu9.04上安装的是1.01版本,实在太老了。

TortoiseHgLinux也可用(事实上,仔细观察一下Windows版本的TortoiseHg就知道,其实它本质上是个GTK程序-_-!

http://bitbucket.org/tortoisehg/stable/wiki/download还是能找到下载的地方:

Linux

Debian packages are coming soon.

Ubuntu packages can be found at: https://launchpad.net/~maxb/+archive/ppa or https://launchpad.net/~tortoisehg-ppa

Fedora RPM packages:

If no package yet exists for your platform, then use the source install method described on the hgtk page. Note that we do not suggest that you run from a tarball, even though we make one available for download. We prefer you use a local clone of TortoiseHg instead. If you do decide to use a tarball, you'll want to delete thgutil/config.py* to remove the hard-coded paths.

To use the settings tool on Linux, you must have http://code.google.com/p/iniparse/ installed.

不翻译了,这里说明一下。。。。。launchpad是出品ubuntu的公司建立的一个源代码host,上述链接上有介绍使用方法,基本上如同普通的程序一样,通过apt-get获取key,然后修改apt-get/etc/apt/下的source.list,添加进新的ppa的源(有3个),然后通过apt-get update更新一下源的信息,就可以直接通过apt-get install tortoisehg来下载了,这时下载的mercurial会是最新的1.3版本,而不是原来的1.01老版了。

我用的是kubuntu,没有用tortoisehg,因为那样要安装一整套的gtk库,用gnome的人就幸福了^^唉。。。这也是当年选择了Qt的后遗症啊。。。。。。。。。。

 

3.      Eclipse

Eclipse由于是Windows,Linux通用的,所谓单独放在这里了,呵呵,JAVA程序的这个优点还是挺吸引人的,说到这里感叹一下,JAVA程序员哪能理解学习了CreateProcess然后重新学习fork,execxxx,学习了CreateThread,然后学习pthread_create的痛苦啊。。。。。呵呵,这些都是题外话。另外,eclipse插件的管理,你得首先安装对应的原始官方版本。。。

Eclipse插件:http://www.vectrace.com/mercurialeclipse/

 

4.      检验安装

首先检验已经安装成功,可以查看一下官方的版本

Windows下:

E:/work>hg version

Mercurial Distributed SCM (version 1.3.1+1444a42f6052)

 

Copyright (C) 2005-2009 Matt Mackall <mpm@selenic.com> and others

This is free software; see the source for copying conditions. There is NO

warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 

Linux下:

jtianling@jtianling-laptop:~$ hg version

分布式软件配置管理工具 - 水银 (版本 1.3.1)

 

版权所有 (C) 2005-2009 Matt Mackall <mpm@selenic.com> 和其他人。

这是自由软件,具体参见版权条款。这里没有任何担保,甚至没有适合

特定目的的隐含的担保。

 

呵呵,在通过ppa那个链接安装了新版后,竟然版权信息是中文的。。。。并且,Mercurial翻译成了水银,这点比较让人吐血。事实上Mercurial表示墨丘利。。。。。也就是希腊神话中的赫尔墨斯(Hermes)。。。。。。

WIKI的说明:

墨丘利(Mercurius)罗马神话中为众神传递信息的使者,相对应于希腊神话的赫耳墨斯Hermes)。他的形象一般是头戴一顶插有双翅的帽子,脚穿飞行鞋,手握魔杖,行走如飞。墨丘利是裘比特玛亚的儿子,是医药、旅行者、商人和小偷的保护神,西方药店经常用他的缠绕两条蛇的手杖作为标志。

此外,由于水星在天上运行的速度很快,所以亦用了他的名字而命名。

怎么说。。。。。。。。。。。我都觉得Matt Mackall原意是指墨丘利吧-_-!又扯远了。。。。。

 

说明安装成功。。。。。。。。。。。。。

 

四、   简要使用说明

因为在Windows下和在Linux下使用Mercurial是类似的(命令行嘛,能差别大到哪去),这里以Windows下的使用为例,之所以使用Windows为例而不是用Linux因为我用的是Word2007编辑所以复制结果比较方便,之所以用Word2007是因为拷贝到IE中的CSDN blog编辑框中比较方便,之所以用IE是因为CSDN blogFirefox的支持不好。。。。。。。。这里抱怨一下-_*!。。。。。

下面的小标题括号中的英文即是对应的Mercurial命令。

1.      帮助(help)

从上面的检验安装方法可以看出来,Mercurial的命令是Hg

首先,因为我不能将所有的命令都讲完,所以先告诉大家怎么用帮助^^,我果然很取巧-_-!

Mercurial有内建的帮助系统,参数是helphelp后还可以以需要查询的命令为参数。

比如:

E:/work>hg help init

hg init [-e CMD] [--remotecmd CMD] [DEST]

 

create a new repository in the given directory

 

    Initialize a new repository in the given directory. If the given

    directory does not exist, it will be created.

 

    If no directory is given, the current directory is used.

 

    It is possible to specify an ssh:// URL as the destination.

    See 'hg help urls' for more information.

 

options:

 

 -e --ssh        specify ssh command to use

    --remotecmd  specify hg command to run on the remote side

 

use "hg -v help init" to show global options

 

2.      建库(init)

上面的帮助内容,懂E文的的就看懂了,init就是Mercurial的建库命令,使用方法如此简单。比如我希望在E:/Work/Hello下建立一个我自己的库,只需要在e:/Work/Hello目录输入hg init如下:

E:/work/hello>hg init

 

E:/work/hello>

事实上,你看不到任何输出,但是通过dir查看的时候会发现多出的.hg目录(Linux因为是“.”开头,默认是隐藏的),而Windows下没有这个规矩,所以还是会显示出来。想起一个笑话,以前有个牛人说,为啥.Net.Net?因为在Linux下甚至都不会显示出来^^

这个时候一个你自己的Mercurial库就建好了:)

 

3.      克隆(clone)

事实上,我机器上还是保留了svn,因为我可以很方便的在sourceforgeGoogle code上下载别人的代码-_-!用的是svn co命令,事实上,大部分人用的最多的也就是这个命令了^^,在mercurial上将svnVSScheckout代码命令叫做克隆(clone),事实上也体现了不同的版本控制系统的管理哲学。集中库吗,需要的是检出(checkout)然后迁入(commit),而分布式呢,因为就没有集中库的概念,你需要做的是在一个人为指定的集中库上克隆一份,你自己的库也就是与服务器上的库一样的库(克隆的概念^^

首先看下帮助,(这里只贴上了第一行):

E:/work>hg help clone

hg clone [OPTION]... SOURCE [DEST]

hg clone 目标

       比如,以后我准备将代码都放在Google Code上共享,而Google CodeMercurial原生支持(哈雷路亚),所以,大家可以直接通过clone命令去随意克隆我的代码,我的博客sample代码存放的Mercurial的地址是

https://blog-sample-code.jtianling.googlecode.com/hg/

那么大家只需要通过如下命令就能下载回去我所有的Sample代码:(当然,Mercurial要是已经安装好的,并且在你想要下载的位置执行下列命令(google推荐的用法

hg clone https://blog-sample-code.jtianling.googlecode.com/hg/ jtianling-blog-sample-code

这样,就将我所有的blog sample代码全部clone到当前目录的jtianling-blog-sample-code目录下了。

如下所示:

E:/work/jtianling-blog-sample-code>dir/w

 驱动器 E 中的卷是 文档

 卷的序列号是 940C-1FF8

 E:/work/jtianling-blog-sample-code 的目录

[.]         [..]        [.hg]       [2009-9-25]

               0 个文件              0 字节

               4 个目录  3,043,004,416 可用字节

E:/work/jtianling-blog-sample-code>

目前仅仅有在E:/work/jtianling-blog-sample-code/2009-9-25/helloMercurial下添加进去的一个文本,helloMercurial.txt

好了,仅仅想学会怎么用Mercurial去享用别人代码的人可以放心的走了,一切很简单吧:)

 

4.      本地提交(commit

Mercurial也有commit,不过这里的commitVSS,SVN中的不同,这里的commit仅仅提交到本地,所以题目特意强调了一下,是本地的提交,这再一次的体现了命令反应哲学的问题^^。在Mercuria里面,你可以放心的commit,因为不会影响到别人。

比如,刚才我clone回来的文本helloMercurial.txt内容是:

Hello World

To Test Mercruail and Google Code

 

Create By JTianLing

我将其修改一下

Hello World

To Test Mercruail and Google Code

And Test Commit

Create By JTianLing

 

然后提交,命令如下:

E:/work/jtianling-blog-sample-code>hg commit

这个时候,在Windows下,直接弹出的是默认的文本编辑工具,我这里是记事本,有如下提示信息:

HG: Enter commit message.  Lines beginning with 'HG:' are removed.

HG: Leave message empty to abort commit.

HG: --

HG: user: JTianLing

HG: branch 'default'

HG: changed 2009-9-25/helloMercurial/helloMercurial.txt

 

告诉你输入提交的message,没有的话是会终止提交的,这也体现了一种好的软件版本管理哲学,每次提交都强制性的要求有说明的,VSS中由于可以忽略说明,所以事实上,我们公司是没有任何人提交带说明的,然后呢?碰到问题的时候就靠喊贝,反正在一个公司-_-!这里,我加入一行:

Add a Line to Test commit command

然后保存退出。这时候提交就已经完成了。

 

5.      记录(log)

怎么确认提交完成没有,怎么查看版本历史,用log命令,执行效果如下:

E:/work/jtianling-blog-sample-code>hg log

changeset:   1:88252aa4d260

tag:         tip

user:        JTianLing

date:        Fri Sep 25 12:27:01 2009 +0800

summary:     Add a Line to Test commit command

 

changeset:   0:baf69ca9684e

user:        JTianLing

date:        Fri Sep 25 12:00:01 2009 +0800

summary:     Create Project Blog

 

 

E:/work/jtianling-blog-sample-code>

 

可以看到我说明的Add a Line to Test commit command版本已经commit了。但是这个时候其实是不影响google上的任何东西的啊:)google上该是啥还是啥。

 

6.      版本同步(push,pull

当我真的需要将修改提交到服务器上怎么做呢?用push(推)出去自己的版本:)事实上,这还是牵涉到版本管理哲学的问题,因为google codehost的那个库在Mercurial看来也就是一个普通的库,没有什么特殊的,你仅仅是将自己的库强行推给它,将它的库修改而已。同理,当你想要放弃修改,需要做的就是将google code上的库pull(拉)回来。比如,我想同步修改google code上我的那个文本。

命令及输出如下:

E:/work/jtianling-blog-sample-code>hg push https://blog-sample-code.jtianling.googlecode.com/hg/

pushing to https://blog-sample-code.jtianling.googlecode.com/hg/

searching for changes

http authorization required

realm: Google Code Mercurial Repository

user: JTianLing

password:

Success.

当然,在google codepush是需要用户名和密码的,完成后显示Success,然后你再去clone的时候会发现内容已经变了。

这里我们确认一下,通过下面的命令,我们再clone一份放到jtianling-blog-sample-code2目录中去。

E:/work>hg clone https://blog-sample-code.jtianling.googlecode.com/hg/ jtianling-blog-sample-code2

requesting all changes

adding changesets

adding manifests

adding file changes

added 2 changesets with 2 changes to 1 files

updating working directory

1 files updated, 0 files merged, 0 files removed, 0 files unresolved

 

E:/work>

这里,会发现输出的信息也与第一次看到的不同了,这里已经有两个changesets了,需要说明的是Mercurial是基于changesets管理的版本控制系统与git基于内容管理(snapshot)管理的方式不同。在Mercurial中,一个系统就是通过一个一个changesets累加起来的。

这里,可以看到新的内容了(按照本文的说明,你下的时候就已经是新的内容了)

 

五、   小结

事实上Mercurial的命令远不止这一些,详细的内容还是那句话,希望大家可以查看教程Mercurial 使用教程与《Mercurial: The Definitive Guide》,一篇小文,难以承载那么多东西,呵呵,何况,也没有必要做太多前人已经做好的工作。《Mercurial 使用教程作为入门非常不错,并且还是中文的,推荐给大家。而且,我说的都是命令行下面的操作,但是,事实上命令行都懂了,图形操作就更不用说了,主要的是理解这些命令对应的分布式版本控制管理概念就好。

我写此文的目的其实主要是因为以后会将blog中的sample代码都放在上述示例的那个工程中,而我会使用Mercurial,为了不需要每次都向大家解释怎么样才能用Mercurial clone回代码,特别写此文简单的介绍一下。

 

六、   参考资料:

需要详细了解Mercurial的话,这里有几篇非常好的教程

1.  Mercurial: The Definitive Guide》,By Bryan O'Sullivan。最好的Mercurial教程,就是篇幅巨大

2.  A tutorial on using Mercurial》,官方推荐的一个教程E文版

3.  Mercurial 使用教程,上面那个教程的中文版,!推荐!

4.  Mercurial快速入门,一个中文的快速入门教程,事实上仅仅翻译了标题-_-!

5.  Mercurial manual 》,作者就是Mercurial的作者。

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

版本控制系统(RCS)的选择与比较

版本控制系统(RCS)的选择与比较

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

为啥考虑选择一个版本控制系统呢?由来已久。其实说到版本控制系统,工作的时候顺从公司的安排,一直用的是VSS,家里面以前常常使用VS,顺便也用上了VSS,但是到了后来,VSS明显不行了,当做Linux的工程,Python工程,或者Eclipse中的工程时,VSS都不太胜任工作,早就想换一个到处能使的版本控制系统了。另外,工作以外其实陆陆续续写了很多代码,在博客上发布的时候常常是用复制粘贴这样的方式,以前也曾经将一些重要的工程打包,放到google groups上去,但是实在是太过麻烦,要方便的话,还是在网站找个项目托管,将所有的代码直接提交,供大家去取的方式最好,顺便将自己的工作之外的代码编写方式统一,也更好组织工程的目录结构。在重装电脑的时候也省事,不用将N大的东西考来考去还怕丢失。在自己家的服务器,台式机,笔记本3者间倒腾代码也更加方便。

       谈谈目前流行的RCS的选择,基本上现在还在用的版本控制系统分为3种,一种以VSS(Visual Source Safe)为代表,在局域网内使用,第二种以CVS(Concurrent Versions System)SVN(Subversion)为代表在互联网上使用,第三种以Mercurial,Git为代表,以分布式方式使用,(对应的,CVS,SVN的使用方式将所有的内容都集中在一台服务器上,被称作集中式)可以在互联网上使用,但是本机也有代码仓库,可以本地提交代码。基本上,将这三种依次的分为3代也未尝不可。

       Wiki上有个网页很详细的列出了众多流行的RCS极其功能比较,《Comparison of revision control software》,建议所有在众多的RCS软件中看的眼花缭乱的人去看看(也许看过后更加乱了)。总而言之我得出的结论是Mercurial > SVN > CVS > VSS

       谈谈选择,选择的目的就是在广泛的方位内使用版本控制系统,抛弃VSS本来就是选择的目的,这里就不多说了。

       CVS实在算是老牌的版本控制系统了,当年整个开源世界都是靠CVS过来的。而SVNCVS开发者特意针对CVS缺点开发的产品,出来以后,整个开源世界都换了颜色。。。。典型的就是Source Forge,这个世界最大的开源站点(个人感觉)都换了SVN后,还有什么理由使用CVS呢?Google Project Host出来的较晚,一开始就是使用SVN。另外,SVNCVS强大的地方除了上面的WIKI文章,还可以参考本文最后的中文补充材料。SVN替代CVS的趋势是不可逆转的,抛弃CVS

       现在就剩下SVN,Mercurial,Git了,其实SVN已经足够强大了,这点毋庸置疑,但是为什么我们还需要分布式呢?(Mercurial,Git的代码管理方式)个人工作时的经历,使我决定使用最新的特性。SVN的代码提交必须首先要能联网,这也是开源时,不同的开发者在不同的地方决定的,分布式的好处就是本地也有代码仓库,这样即使没有联网也可以本地提交代码,然后等待联网时再去Merge,当然,这是简单的说明,最大的好处在于,本地也能有一套完整的版本控制系统,在代码没有提交到正式的代码仓库前享受版本控制的好处,这点的好处非常多。举两个最常见的例子,其一就是一个代码量适中的工作,你没有分出版本,工作到一半的时候,很显然不能提交到正式的库里,影响大家的工作,那么中途你想尝试改改代码,有个本地库,能让你更加的自由,其二就是工作已经完成后,但是还没有来得及测试,中途需要去完成一个更加重要的工作,此时代码提交到正式库中还是不恰当的,假如有个本地的库的话,能够先提交,然后无论过了多久后,你再回来,你都能可靠地获取到完整的代码,以前工作的时候在VSS我就碰到过这样的情况,然后12天的工作就在一个自以为安全的全取上丢失了。因为上面痛苦的经历,我决定尝试使用最新的分布式版本控制系统,虽然我已经在自己的服务器上好不容易搭建了一套SVN服务了,虽然自己的开发机上都已经装好了TortoiseSVN了,虽然分布式的版本控制系统还太新,以至于还不太完善。呵呵,作为程序员,就要敢于拥抱新事物嘛。。。。。。。。又想起某句名言,IT领域最大的特点就是喜新厌旧。。。。。。

       最后剩下的就是MercurialGit之争了,在这两个RCS中选择还真是痛苦的选择,因为各有优势,就像当年选择一个除MFC以外的GUI库一样,QtGTK的选择一样痛苦。(当年最后的选择是Qt)这里,有几篇不错的中文文章《拥抱Mercurial---选择分布式版本控制工具》,《分布式版本控制工具:git & mercurial》,《几个分布式vcs比较(文中的HG就是指Mercurial)对其进行了比较,具体的比较内容这里结合WIKI中的文章总结下:

GIT:

1.   与SVN的集成更好。
2.   GIT
是基于内容的跟踪系统,相较Mecurial而言,模型简单些,因此容易扩展;

3.   Linuxrails世界中应用相当广泛。(GIT本身就是Linus开发的,这点是自然的,当年CSDNCC++世界最大的口水战不就是因为GIT全部是用C开发,然后某个Windows下的C++开发者提出了疑问后触发的吗。。。。。。)

4.   跨平台特性较弱。(可以理解)

5.   命令复杂。。。。。(对应的也许是功能更加强大吧)

6.   branch建立合并功能强大,方便。

 

Mercurial

1. SVN命令相似,学习代价更小。

2. 跨平台特性很好。(事实上TortoiseHg较成熟,好用,再加上用Python开发)

3. 使用广泛,文档很好。

4.获得的支持更多。(最典型的就是Google,Google Code原生支持Mercurial)

 

 

以下是各自获得的支持列表:

Software  

Web interfaces  

Stand-alone GUIs  

Integration and/or Plug-ins for IDEs  

Git

gitweb, wit, cgit, GitHub, gitorious, Trac

gitk, git-gui (Tcl/Tk), tig, TortoiseGit, qgit, gitg (GNOME/GTK), (h)gct (Qt), git-cola (Qt), Git Extensions (Windows Explorer)

Eclipse (JGit/EGit); Netbeans (NbGit); Visual Studio (Git Extensions); Emacs (extension for standard VC); TextMate (Git TextMate Bundle); Vim (VCSCommand plugin); IntelliJ IDEA >8.1 (standard feature); Komodo IDE; Anjuta

Mercurial

included [63], Bitbucket, Trac

Hgk (Tcl/Tk), (h)gct (Qt), TortoiseHg (Windows Explorer, Nautilus)

Eclipse (Mercurial Eclipse), NetBeans ([50]), Visual Studio 2008 ([51]), Emacs, Vim (VCSCommand plugin), Komodo IDE

比较明显的是,Git获得的Linux下的支持那是非常多啊。。。。。。

最后,看的越多,越难选择,两者都是非常优秀的软件,相对来说,Git更加强大,但是因为自己还是需要在Windows下做一些工作的,另外,因为想在网上找个地方托管代码,Google支持Mercurial而不支持Git,因为这个缘故,选择Mercurial了。。。。。呵呵,最后的选择因为这么简单的原因。。。。。。。

       另外,对于E文不太好的人,提供一篇中文补充材料:

版本控制系统简介RCS/CVS/Subversion》向我们讲述了CVS的特性,SVNCVS强大的地方,以及一些SVN的缺点。

 

 

后记:经过使用后,对Mercurial有了一些更理性的认识,相对来说,Mercurial的库的建立因为是分布式的,建立起来更加方便,本地不需要任何服务器什么的,直接就能很好的享受版本控制的好处,仅仅需要了解几个命令就能胜任日常的使用。但是,事实上,目前来说,分布式的如Mercurial的版本控制系统还是有一些缺陷的,最重要的即是权限的控制功能比较弱,这点可能因为Mercurial的用户群主要在于开源世界,所以没有太过重视,实际上企业开发可能会比较恼火,相对来说SVN,特别是VSS就要好的多。然后Mercurial相对Git有个缺点,那就是分支的时候不能对单独的子目录进行,一次clone就是一个工程,这样希望在一个大工程中对一个小项目进行分支,会比较麻烦,这点也算是比较大的缺点了,总的来说,Mercurial还是小项目和小团队使用的版本控制的首选,使用方便,入门也简单,功能强大,但是可能大团队,就得有所考虑了,但是大团队还是想使用Mercurial的话,还是能通过其他版本控制住权限的,然后,通过划分较多小Mercurial项目的版本来解决一次只能分支一个完整Mercurial的问题。

最后,我后来还看到一篇洋洋洒洒的论文-_-!可见事情发生的猛烈和迅速。。。。《为什么软件项目从集中式迁移到分布式版本控制系统的热情持续不减?》
最后的最后,我为Mercurial写了一个简单的介绍及入门引导《分布式的,新一代版本控制系统Mercurial的介绍及简要入门

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 



阅读全文....

简单图形编程的学习(2)---点 (Qt实现)

简单图形编程的学习(2--- (Qt实现)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

一、   画点

Qt中画点的函数是QPainterdrawPoint函数,还是放在QPainter体现了Qt决心将所有的绘图指令放在一个对象中。(除了OpenGL)既然如此,使用方法上和drawText也就差不太多了。

开篇来个最简单的示例吧,画点世界的HelloWorld,随机的点。

这个工程的全部文件都贴出来,也作为Qt中实现动画的一种示例:

Main.cpp:

#include <QtGui/QApplication>

#include <QTest>

#include <QTime>

#include "pointwidget.h"

 

int main(int argc, char *argv[])

{

   QApplication a(argc, argv);

   PointWidget w;

   w.show();

   QTime timer;

   // 这是能随机绘点的关键,没有设置此属性,默认相当于每次Qt都会完整的将上一次的屏幕擦除,

   // 新版的Qt中已经没有了repaint(bool)接口了。

   w.setAttribute(Qt::WA_OpaquePaintEvent);

   while(true)

   {

      timer.start();

      // 调用此函数即相当于Windows中的GetMessage,系列函数,包括了tranlate,分发函数等的所有操作

      a.processEvents();

      w.repaint();

 

// 此处是控制帧数的关键点

      while(timer.elapsed() < 33)

      {

        QTest::qSleep(1);

      }

   }

}

 

Pointwidget.h

#ifndef POINTWIDGET_H

#define POINTWIDGET_H

 

#include <QtGui/QWidget>

#include <QPainter>

#include <QObject>

namespace Ui

{

       class PointWidget;

}

 

class PointWidget : public QWidget

{

       Q_OBJECT

 

public:

       PointWidget(QWidget *parent = 0);

       ~PointWidget();

 

protected:

       void paintEvent(QPaintEvent *event);

       //void keyPressEvent(QKeyEvent *event);

       //void closeEvent(QCloseEvent *event);

private:

       Ui::PointWidget *ui;

};

 

#endif // POINTWIDGET_H

 

Pointwidget.cpp

#include "pointwidget.h"

#include "ui_pointwidget.h"

 

PointWidget::PointWidget(QWidget *parent)

: QWidget(parent), ui(new Ui::PointWidget)

{

       ui->setupUi(this);

       qsrand(time(NULL));

}

 

PointWidget::~PointWidget()

{

       delete ui;

}

 

void PointWidget::paintEvent(QPaintEvent *event)

{

       QPainter painter(this);

 

       for(int i = 0; i < 1000; ++i)

       {

 

              int r = qrand() % 255;

              int g = qrand() % 255;

              int b = qrand() % 255;

              QPen pen(qRgb(r,g,b));

              painter.setPen(pen);

 

              int x = qrand() % 800;

              int y = qrand() % 800;

 

              // 其实核心内容就是调用这一个函数而已

              painter.drawPoint(x,y);

       }

}

 

同样还是比较简单,当然,得首先熟悉Qt的基本机制,其中控制循环的方式特别重要,虽然这里可以使用与Win32 Timer类似的定时器技术来实现这样简单的绘制,但是对于帧数的稳定控制还是这样的代码比较可靠,另外,w.setAttribute(Qt::WA_OpaquePaintEvent);一句够初学者找够久的了。。。(我就找了很久)

知道这些以后,剩下的也就是一个qrand函数+ drawPoint函数的理解量了。这里不放截图了,这么简单的东西放个截图我都觉得没有意思。

 

1.      老电视机雪花点的效果:

Main.cpp中用

PointWidget w;

QPalette palette;

palette.setColor(QPalette::Window, QColor(0,0,0));

w.setPalette(palette);

几行代码改变Widget的背景,这里说明一下,其实这样改变背景个人感觉属于Qt中面向对象过头的一个问题,事实上远远复杂于一个简单的SetBkColor函数,但是Qt将所有与界面颜色相关的东西封在QPalette中,以后同时改变多个属性的时候会稍微简单一点(其实也没有简单到哪去,多次调用SetTextColor,SetBkColor等语句也不见的复杂到哪去)

老电视机雪花效果中每次都需要擦除重绘避免点的叠加所以一下语句注释掉

//    w.setAttribute(Qt::WA_OpaquePaintEvent);

 

paintEvent实现:

void PointWidget::paintEvent(QPaintEvent *event)

{

       QPainter painter(this);

 

       for(int i = 0; i < 1000; ++i)

       {

              // 老电视机都是白色雪花点

              QPen pen(qRgb(255,255,255));

              painter.setPen(pen);

 

              int x = qrand() % 800;

              int y = qrand() % 800;

 

              painter.drawPoint(x,y);

       }

}

完整代码就不贴了。

 

2.      移动的星空:

主要实现代码:

 

PointWidget::PointWidget(QWidget *parent)

: QWidget(parent), ui(new Ui::PointWidget)

{

       ui->setupUi(this);

       qsrand(time(NULL));

 

       // 初始化星空中的点

       for(int i = 0; i < POINT_NUMBER; ++i)

       {

              marPoint[i].rx() = qrand() % 800;

              marPoint[i].ry() = qrand() % 800;

       }

}

 

PointWidget::~PointWidget()

{

       delete ui;

}

 

void PointWidget::paintEvent(QPaintEvent *event)

{

       QPainter painter(this);

 

       QPen pen(qRgb(255,255,255));

       painter.setPen(pen);

       for(int i = 0; i < POINT_NUMBER; ++i)

       {

              marPoint[i].rx()++;

              if(marPoint[i].x() > 800)

              {

                     marPoint[i].rx() = 0;

              }

 

              painter.drawPoint(marPoint[i]);

       }

}

 

思路还是与以前的一样,无非是西安初始化一些点,然后改变其x坐标,但是要说明的是,Qt为我们简化了很多操作,首先,默认情况下,会擦出每一帧,这样就不用我们手动通过覆盖上一个点的方式去完成,另外,默认使用了双缓冲方式显示图片(与上个特性其实是统一的。。。。)完全不闪。。。。

二、   小结

一个个简单的点就能够构成的效果比本文展示的要多的多,参考以前的文章,因为笔者对Qt的熟悉程度有限,重新实现程序费时费力,并且也不太适应目前所用的QtCreator工具(主要是vi功能弱的吐血,Eclipsevi模拟就够差的了,QtCreator虽然原生就带,但是几乎还不如不用,还是VS中的ViEmu强大),程序的其他效果及截图也请参考此系列相关其他文章了:

简单图形编程的学习(2--- (Windows GDI实现)

简单图形编程的学习(2--- (small basic实现)

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

设计Qt风格的C++API【转】

相当好的关于API设计的文章,Qt的创始人之一写就,很有启发,大规模的C++设计会碰到的问题很多,我们可以慢慢想办法减少这些问题。

原文来自:http://googollee.blog.163.com/blog/static/1159411200811321030894/

 

设计Qt风格的C++API

翻译   2008-02-13 19:39   阅读1210   评论21  
字号:    
这是Qt官方发布的文章,觉得对设计API很有借鉴意义。所以顺便拿来翻译了。原文的文内链接,因为blog不支持的关系,都没有起作用,想要html文件的直接问我要吧,或者到这里取。(顺便做广告:TopLanguage是个很好的讨论组,欢迎大家去讨论关于语言的感受、意见和使用,主持人是pongba


作者Matthias Ettrich,译者Googol Lee,原文地址在这里


在奇趣(Trolltech),为了改进Qt的开发体验,我们做了大量的研究。这篇文章里,我打算分享一些我们的发现,以及一些我们在设计Qt4时用到的原则,并且展示如何把这些原则应用到你的代码里。

设计应用程序接口,API,是很难的。这是一门和设计语言同样难的艺术。这里可以选择太多的原则,甚至有很多原则和其他原则有矛盾。

现在,计算机科学教育把很大的力气放在算法和数据结构上,而很少关注设计语言和框架背后的原则。这让应用程序员完全没有准备去面对越来越重要的任务:创造可重用的组件。

在面向对象语言普及之前,可重用的通用代码大部分是由库提供者写的,而不是应用程序员。在Qt的世界里,这种状况有了明显的改善。在任何时候,用Qt编程就是写新的组件。一个典型的Qt应用程序至少都会有几个在程序中反复使用的自定义组件。一般来说,同样的组件会成为其他应用程序的一部分。KDE,K桌面环境,走得更远,用许多追加的库来扩展Qt,实现了数百个附加类。(一般来说,一个类就是一个可重用组件,原文这里没有写清楚。)

但是,一个好的,高效的C++ API是由什么组成的呢?是好还是坏,取决于很多因素——比如,手头的工作和特定的目标群体。好的API有很多特性,一些特性是大家都想要的,而另一些则是针对特定问题域的。

好的API的六个特性

API是面向程序员的,用来描述提供给最终用户的GUI是什么样子。API中的P带表程序员(Programmer),而不是程序(Program),用来强调API是给程序员用的,给人类的程序员用的。

我们坚信API应该是最小化且完整的,拥有清晰且简单的语义,直觉化,容易记忆,并且引导人写出易读的代码。

  • 最小化:最小化的API是指一个类尽可能只拥有最少的公开成员且尽可能只拥有最少的类。这个原则可以让API更简单易懂,更好记,更容易除错,且更容易改变。
  • 完整的:完整的API是指要提供所有期望的功能。这个可能与最小化原则相冲突。另外,如果一个成员函数属于一个不应该属于的类,很多潜在的使用者都会找不到这个函数。
  • 拥有清晰且简单的语义:就像其他设计工作一样,你必须遵守最小惊奇原则(the principle of least surprise)。让常见的任务简单易行。不常见的工作可行,但不会让用户过分关注。解决特殊问题时,不要让解决方案没有必要的过度通用。(比如,Qt3中的QMimeSourceFactory可以通过调用QImageLoader来实现不同的API。)
  • 直觉化:就像电脑上的其他东西一样,API必须是直觉化的。不同的经验和背景会导致在判断什么是直觉而什么不是时不同的感觉。如果一个中级用户不读文档就可以使用(a semi-experienced user gets away without reading the documentation,没懂这里的get away该怎么翻译),并且一个程序员不懂API就可以理解缩写的代码,这种API就是直觉化的。
  • 易于记忆:让API易于记忆,使用统一且精确的命名方法。使用可识别的模式和概念,并且避免缩写。
  • 引导易读的代码(Lead to readable code):代码一经写就,会读(并且除错和修改)多次。易读的代码可能会花点时间来写,但是可以节省产品周期中的其他时间。

最后,记住,不同类型的用户会用到API的不同部分。虽然简单的实例化一个Qt类是非常直觉化的,让资深专家在试图子类化之前读一遍文档,是很合理的。

便利陷阱

这是个常见的误解:更好的API,用更少的代码完成一件事。永远记住代码一次写就,之后需要不断的阅读并理解。比如:

    QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,
0, "volume");

远比下面那样难读(甚至难写):

    QSlider *slider = new QSlider(Qt::Vertical);
slider->setRange(12, 18);
slider->setPageStep(3);
slider->setValue(13);
slider->setObjectName("volume");

布尔参数陷阱

布尔参数通常会导致不易读的代码。更进一步,给一个已经存在的函数加入一个布尔参数,这常常是个错误。在Qt里,一个传统的例子是repaint(),这个函数带有一个布尔参数,来标识是否擦除背景(默认擦除)。这让代码通常写成:

    widget->repaint(false);

初学者很容易把这句话理解成“别重画”!

这样做是考虑到布尔参数可以减少一个函数,避免代码膨胀。事实上,这反而增加了代码量。有多少Qt用户真的记住了下面三行程序都是做什么的?

    widget->repaint();
widget->repaint(true);
widget->repaint(false);

一个好一些的API可能看起来是这样:

    widget->repaint();
widget->repaintWithoutErasing();

在Qt4里,我们重新设计了widget,使得用户不再需要不重画背景的重画widget,来解决这个问题。Qt4原生支持双缓存,废掉了这个特性。

这里还有一些例子:

    widget->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Expanding, true);
textEdit->insert("Where's Waldo?", true, true, false);
QRegExp rx("moc_*.c??", false, true);

一个显而易见的解决方法是,使用枚举类型代替布尔参数。这正是我们在Qt4中QString大小写敏感时的处理方法。比较:

    str.replace("%USER%", user, false);               // Qt 3
str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4

静态多态

相似的类应该含有相似的API。在必要的时候——就是说,需要使用运行时多态的时候——这可以通过继承实现。但是多态依旧会发生在设计时期。比如,如果你用QListBox代替QComboBox,或者用QSlider代替QSpinBox,你会发现相似的API使这种替换非常容易。这就是我们所说的“静态多态”。

静态多态也使API和程序模式更容易记忆。作为结论,一组相关类使用相似的API,有时要比给每个类提供完美的单独API,要好。

(译注:C++ 0x将要引入的concept,就是静态多态的语法层实现。这个要比单独的函数名相似更强大且易用。)

命名的艺术

命名,大概是设计API时唯一最重要的问题了。该怎么称呼这个类?成员函数该叫什么?

通用的命名规则

一些规则通常对所有名字都是有用的。首先,就像我之前提到的,别用缩写。甚至很明显的缩写,比如“prev”表示“previous”从长远看也是不划算的,因为用户必须记住哪些词是缩写。

如果API本身不一致,事情自然会变得很糟糕,比如, Qt3有activatePreviousWindow()和fetchPrev()。坚持“没有缩写”的规则更容易创建一致的API。

另一个重要但更加微妙的规则是,在设计类的时候,必须尽力保证子类命名空间的干净。在Qt3里,没有很好的遵守这个规则。比如,拿QToolButton来举例。如果你在Qt3里,对一个QToolButton调用name()、caption()、text()或者textLabel(),你希望做什么呢?你可以在Qt Designer里拿QToolButton试试:

  • name属性继承自QObject,表示一个对象用于除错和测试的内部名字。
  • caption属性继承自QWidget,表示窗口的标题,这个标题在视觉上对QToolButton没有任何意义,因为他们总是跟随父窗口而创建。
  • text属性继承自QButton,一般情况下是按钮上现实的文字,除非useTextLabel为真。
  • textLabel在QToolButton里声明,并且在useTextLabel为真时显示在按钮上。

由于对可读性的关注,name在Qt4里被称作objectName,caption变成了windowsTitle,而在QToolButton里不再有单独的textLabel属性。

给类命名

标识一组类而不是单独给每个类找个恰当的名字。比如,Qt4里所有模式感知项目的视图类(model-aware item view classes)都拥有-View的后缀(QListViewQTableViewQTreeView),并且对应基于项目的类都用后缀-Widget代替(QListWidgetQTableWidgetQTreeWidget)。

给枚举类型及其值命名

当声明枚举时,时刻记住,在C++(不像Java和C#)中,使用枚举值不需要类型信息。下面的例子演示了给枚举值起个太过常用的名字所引起的危害:

    namespace Qt
{
enum Corner { TopLeft, BottomRight, ... };
enum CaseSensitivity { Insensitive, Sensitive };
...
};

tabWidget->setCornerWidget(widget, Qt::TopLeft);

str.indexOf("$(QTDIR)", Qt::Insensitive);

在最后一行,Insensitive是什么意思?一个用于命名枚举值的指导思想是,在每个枚举值里,至少重复一个枚举类型名中的元素:

    namespace Qt
{
enum Corner { TopLeftCorner, BottomRightCorner, ... };
enum CaseSensitivity { CaseInsensitive,
CaseSensitive };
...
};

tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);

当枚举值可以用“或”连接起来当作一个标志时,传统的做法是将“或”的结果作为一个int保存,这不是类型安全的。Qt4提供了一个模板类 QFlags<T>来实现类型安全,其中T是个枚举类型。为了方便使用,Qt为很多标志类名提供了typedef,所以你可以使用类型 Qt::Alignment代替QFlags<Qt::AlignmentFlag>。

为了方便,我们给枚举类型单数的名字(这样表示枚举值一次只能有一个标志),而“标志”则使用复数名字。比如:

    enum RectangleEdge { LeftEdge, RightEdge, ... };
typedef QFlags<RectangleEdge> RectangleEdges;

有些情况下,“标志“类使用了单数的名字。这时,枚举类使用-Flag做后缀:

    enum AlignmentFlag { AlignLeft, AlignTop, ... };
typedef QFlags<AlignmentFlag> Alignment;

(这里为啥不是把”标志“类用-Flag做后缀,而是把枚举值做后缀呢?感觉有点混淆……)

给函数和参数命名

给函数命名的一个规则是,名字要明确体现出这个函数是否有副作用。在Qt3,常数函数QString::simplifyWhiteSpace()违反了这个原则,因为它返回类一个QString实例,而不是像名字所提示的那样,更改了调用这个函数的实例本身。在Qt4,这个函数被重命名为QString::simplified()。

参数名是程序员的重要信息来源,虽然在使用API时,并不直接展示在代码里。由于现代IDE在程序员写代码时可以自动显示参数名(就是自动感知或者自动补全之类的功能),值得花时间给头文件里声明的参数一个合适的名字,并且在文档中也使用相同的名字。

给布尔值设置函数(Setter)、提取函数(Getter)和属性命名

给布尔属性的设置函数和提取函数一个合适的名字,总是非常痛苦的。提取函数应该叫做checked()还是isChecked()?scrollBarsEnabled()还是areScrollBarEnabled()?

在Qt4里,我们使用下列规则命名提取函数:

  • 形容类的属性使用is-前缀。比如:
    • isChecked()
    • isDown()
    • isEmpty()
    • isMovingEnable()

    另外,应用到复数名词的形容类属性没有前缀:

    • scrollBarsEnabled(),而不是areScrollBarsEnabled()
  • 动词类的属性不使用前缀,且不使用第三人称(-s):
    • acceptDrops(),而不是acceptsDrops()
    • allColumnsShowFocus()
  • 名词类的属性,通常没有前缀:
    • autoCompletion(),而不是isAutoCompletion()
    • boundaryChecking()

    有时,没有前缀就会引起误解,这种情况使用前缀is-:

    • isOpenGLAvailable(),而不是openGL()
    • isDialog(),而不是dialog()

    (通过调用dialogue()方法,正常情况下会期望返回一个QDialog*的实例。)

设置函数名字继承自提取函数名,只是移掉了所有前缀,并使用set-做前缀,比如:setDown()还有setScrollBarsEnabled()。属性的名字与提取函数相同,只是去掉了前缀。

指针还是引用?

传出参数的最佳选择是什么,指针还是引用?

    void getHsv(int *h, int *s, int *v) const
void getHsv(int &h, int &s, int &v) const

大部分C++书推荐在能用引用的地方就用引用,这是因为一般认为引用比指针更“安全且好用”。然而,在奇趣(Trolltech),我们倾向使用指针,因为这让代码更易读。比较:

    color.getHsv(&h, &s, &v);
color.getHsv(h, s, v);

只有第一行能清楚的说明,在函数调用后,h、s和v将有很大几率被改动。

例子:QProgressBar

为了展示如何实际应用这些概念,我们将学习Qt3中的API QProgressBar并和Qt4李相通的API做比较。在Qt3里:

    class QProgressBar : public QWidget
{
...
public:
int totalSteps() const;
int progress() const;

const QString &progressString() const;
bool percentageVisible() const;
void setPercentageVisible(bool);

void setCenterIndicator(bool on);
bool centerIndicator() const;

void setIndicatorFollowsStyle(bool);
bool indicatorFollowsStyle() const;

public slots:
void reset();
virtual void setTotalSteps(int totalSteps);
virtual void setProgress(int progress);
void setProgress(int progress, int totalSteps);

protected:
virtual bool setIndicator(QString &progressStr,
int progress,
int totalSteps);
...
};

API相当复杂,且不统一。比如,仅从名字reset()并不能理解其作用,setTotalSteps()和setProgress()是紧耦合的。

改进API的关键,是注意到QProgressBar和Qt4的QAbstractSpinBox类及其子类QSpinBoxQSliderQDial很相似。解决方法?用minimum、maximum和value代替progress和totalSteps。加入alueChanged()信号。加入setRange()函数。

之后观察progressString、percentage和indicator实际都指一个东西:在进度条上显示的文字。一般来说文字是百分比信息,但是也可以使用setIndicator()设为任意字符。下面是新的API:

    virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;

默认的文字信息是百分比信息。文字信息可以藉由重新实现text()而改变。

在Qt3 API中,setCenterIndicator()和setIndicatorFollowStyle()是两个影响对齐的函数。他们可以方便的由一个函数实现,setAlignment():

    void setAlignment(Qt::Alignment alignment);

如果程序员不调用setAlignment(),对齐方式基于当前的风格。对于基于Motif的风格,文字将居中显示;对其他风格,文字将靠在右边。

这是改进后的QProgressBar API:

    class QProgressBar : public QWidget
{
...
public:
void setMinimum(int minimum);
int minimum() const;
void setMaximum(int maximum);
int maximum() const;
void setRange(int minimum, int maximum);
int value() const;

virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;
Qt::Alignment alignment() const;
void setAlignment(Qt::Alignment alignment);

public slots:
void reset();
void setValue(int value);

signals:
void valueChanged(int value);
...
};

如何把API设计好(原文是How to Get APIs Right,我总想成We do APIs right……)

API需要质量保证。第一个修订版不可能是正确的;你必须做测试。写些用例:看看那些使用了这些API的代码,并验证代码是否易读。

其他的技巧包括让别的人分别在有文档和没有文档的情况下,使用这些API;或者为API类写文档(包括类的概述和独立的函数)。

当你卡住时,写文档也是一种获得好名字的方法:仅仅是尝试把条目(类,函数,枚举值,等等呢个)写下来并且使用你写的第一句话作为灵感。如果你不能找到一个精确的名字,这常常说明这个条目不应该存在。如果所有前面的事情都失败了并且你确认这个概念的存在,发明一个新名字。毕竟,“widget”、 “event”、“focus”和“buddy”这些名字就是这么来的。

阅读全文....

简单图形编程的学习(1)---文字 (Qt实现)


简单图形编程的学习(1---文字 (Qt实现)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

一、   全部简单图形编程的学习说在前面的话

      此系列文章均假设读者已经具备一定的对应的程序编写知识,无论是最简单的small basic,还是因为常用而被人熟知的Windows GDI,或者是Linux下用的更多的Qt(一般我用PyQt),甚至是现在国内知道的人并不多的Android,我都不准备讲太多基础的语法,或者与平台相关的太多背景知识,这些靠读者先行学习,我仅仅准备在自己学习的过程中找点乐子:)看看我用一些简单的接口都能想出干什么事情,然后展示给大家看看,图形程序实在是最好展示的一类程序了,不像其他程序一样,哪怕我讲了一堆的boost,真正见识到boost强大的又有几个呢?-_-!要知道,今天起,所有程序都是窗口程序,不再是命令行!!!!人类用了多久才走到这一步我不知道。。。。我用了25.......(从我出生算起)或者1年(从工作开始)

       另外,想要看怎么编写窗口应用程序的就不要走错地方了,这里不是想怎么描述怎么使用一个又一个的控件,这里都是讲绘图的-_-!

 

二、   谈谈Qt

由于今天是第一篇,所以谈谈Qt,Qt原来是奇趣(trolltech)公司的产品,目前奇趣已经被诺基亚收购。很久前学习Linux的时候就知道Qt了(可谓久仰大名,如雷贯耳),最重要的是,Qt以其良好的面向对象特性,可移植性著称,这也是我特别喜欢的特性,所以当年在学习Python并选择一个GUI的时候,综合考虑,最终没有选择PyGTKwxPython等一样优秀的开源产品,(见以前的一篇文章《pyqt学习 的开始,顺便小谈目前gui的选择...而是PyQtQt的原始版本是C++,(但是目前官方已经支持JAVA)而PyQt其实不是官方支持的产品,学到后来,因为PyQt没有好用的IDE(所以一直用Gvim代替着,Eric怎么看都不习惯,一堆按钮没有章法),而且没有合适的教程参考学习(大部分都是qt3时代的东西),最重要的是个人还是比较喜欢看纸质书籍,而国内很难看到PyQt书籍的出版了。。。。。。。。所以,其实PyQt在学习了一段时间其实慢慢的慢下来了,再加上那时候预知了OPhone将横空出世,将较大的经历转移到了JAVAAndroid的学习中去了。。。。可怜的Python啊。。。。呵呵,连一款完整的GUI都还没有学会-_-!Qt的网址目前是http://qt.nokia.com/。目前个人计划是考虑先学好Qt的根本,C++Qt吧,将来想做界面的时候用PyQt应该也快,毕竟接口还是一样的。因为在家,没有好用的Eclipse C++环境(在家用WindowsWindowsC++还是较为习惯VS,而装着Ubuntu + Eclipse + g++ + qt eclipse插件的笔记本在公司),暂时用qt creator来代替吧,虽然个人觉得qt creator还是太过于功能简单的IDE。。。。

 

三、   Qt的文字显示

Qt原始的设计是用于手工编码产生GUI的,所以简单的程序编码也比较简单,(不像Win32MFC,再简单的程序也是一大堆废物代码),一个简单的文字显示示例如下:(用qt creator的一个很大问题是没有语法高亮,所以我不得不将代码先拷贝到VS中然后再拷贝过来以达到语法高亮的效果)

// main.cpp(程序主程序)

#include <QtGui/QApplication>

#include "fontwidget.h"

 

int main(int argc, char *argv[])

{

   QApplication a(argc, argv);

   FontWidget w;

   w.show();

   return a.exec();

}

 

Fontwidget.h:

#ifndef FONTWIDGET_H

#define FONTWIDGET_H

 

#include <QtGui/QWidget>

 

namespace Ui

{

   class FontWidget;

}

 

class FontWidget : public QWidget

{

   Q_OBJECT

 

public:

   FontWidget(QWidget *parent = 0);

   ~FontWidget();

 

protected:

   void FontWidget::paintEvent(QPaintEvent *event);

 

private:

   Ui::FontWidget *ui;

};

 

#endif // FONTWIDGET_H

 

FontWidget.cpp:

#include "fontwidget.h"

#include "ui_fontwidget.h"

#include <QPainter>

#include <QObject>

FontWidget::FontWidget(QWidget *parent)

: QWidget(parent), ui(new Ui::FontWidget)

{

   ui->setupUi(this);

}

 

FontWidget::~FontWidget()

{

   delete ui;

}

 

void FontWidget::paintEvent(QPaintEvent *event)

{

   QPainter painter(this);

   QString text = tr("Hello World");

   painter.drawText(10,10, text);

}

 

说是简单,其实为了展示一个较为完成的自定义WidgetQt程序,还是牵扯了较多的额外代码,其实真正有意义的仅仅只有  QPainter painter(this);

   QString text = tr("Hello World");

   painter.drawText(10,10, text);

3句而已,这里,说明一下paintEvent,在QtEventsignal特别重要,可以理解为两套不同的消息机制,Event主要用于控件的底层,不直接受你控制,而signal的使用范围不限,并且你可以无限的扩展。Event这种不直接受控制的特性也是非常有用的,因为给出了一套较为容易理解的程序执行流程和规范,基本上,这里EventWin32MFC)中的消息较为类似,paintEvent类似Win32中的WM_DRAW消息的响应(在MFC中就是OnDraw),使用方法上与MFC也较为类似,即通过继承来改变。

需要特别注意的是,QPainterdrawTextWindowsDrawText设计思想上不太一样,这里的Y坐标指的是字符的Baseline位置(基线)。

这点我刚开始不太习惯,(因为很简单的原因),而QPainter的用rect为参数的drawText版本仍然与一般的drawText很类似,比如下面这样:

void FontWidget::paintEvent(QPaintEvent *event)

{

   QPainter painter(this);

   QString text = tr("Hello World");

   painter.drawText(QRect(0,0, 100, 20), text);

}

那么,此处的rect的左上角就代表了文字的左上角,所以个人感觉这里有点比较奇怪。更奇怪的是,不仅仅是这一个借口,drawText的接口只要是用x,y直接来表示位置的时候就是指文字的baseline,用QRect的时候就是top。。。。。。。。。。

总而言之,基本的文字显示方法在Qt中就很明显了,QPainter对象的drawText,需要注意y坐标的意思。。。。。。。。。。

 

四、   字体

其实字体是个更加复杂的问题。。。。有多少人知道在字体的现实问题上MS,Apple,Adobe的研究人员投入了多少精力啊。。。。今天的TrueType可不是一开始就存在的。。。。去简单图形编程的学习(1---文字 (Windows GDI实现)看看window版本中的字体结构有多少参数吧。。。。。。。要是人工去记就像记圆周率一样,相对来说,因为Qt引入了QFont这样较为面向对象的方式,简单了很多.

Qt中对字体也给与了重要支持(其实对于很多人来说PC就是一个文字处理的机器,所以字体相当重要,想想MS最赚钱的软件吧。。Office),

仅仅关于字体就有很多类:(链接直接指向nokiaclass reference

QFontQFontComboBox, QFontDatabase, QFontDialog, QFontEngineInfo, QFontEnginePlugin, QFontInfo

这里,我仅仅想改变QPainterdrawText的字体,所以仅仅使用QFont

另外,QPen可以用于改变drawText的颜色,而这些都是QPainter的属性,通过setXXX来设置。

示例如下:

void FontWidget::paintEvent(QPaintEvent *event)

{

   QPainter painter(this);

   QFont font("Times",50);

   QString text = tr("Hello World");

   painter.setPen(Qt::blue);

   painter.setFont(font);

   painter.drawText(QRect(0,0, 1000, 100), text);

}

效果如插图1.这里是第一次展示Qt的截图,顺便说明一下,QtWindow下并不是使用Windows的原生控件,而是通过Qt控件模拟而成。不要怀疑Qt有原生的控件啊,这也是我学习Qt的原因之一,Qt可是有它的原生平台KDE的(也是Qt的杀手级应用),因为学习Qt我甚至将以前用了好些年的Gnome经验和体验都放弃了,开始适应并改用KDE,ubuntuKDE版本还有特定的名字Kubuntu。。。。。。。。没有办法使用compiz还让我郁闷了好一阵子,因此还查看了一些KDE,Gnome的相关资料,KDEGnome之间的竞争估计也算是IT世界著名而漫长的战争之一了吧(类似的例子还有vim-emacs)。可怜的KDE盛极一时,因为Qt而慢慢被众多公司所抛弃,但是竟然因为Qt是某家公司的产品(虽然开源)而被冷落。。。可见开源世界的人们对自由的极高标准。。。。

 

五、   旋转的字体

按照来自Charles Petzold的创意,见简单图形编程的学习(1---文字 (Windows GDI实现)中的例子,自然我也想要在Qt中实现旋转的字体,并且,这一次不会再有抄袭Charles Petzold的嫌疑了lol,呵呵,虽然还是抄袭了创意,对比Windows中的例子,你会很惊讶的发现Qt的实现实在太简单了,简单的可怕。

 

void FontWidget::paintEvent(QPaintEvent *event)

{

   QPainter painter(this);

   QFont font("Times",50);

   QString text = tr("   Rotation");

   painter.setFont(font);

   int x = (width() / 2);

   int y = (height() / 2);

   painter.setViewport(x, y, width(), height());

 

   for(int i = 0; i < 12; ++i)

   {

      painter.rotate(30);

      painter.drawText(0, 0, text);

   }

}

QPainter::setViewport调整后Viewport后,painter有现成的函数rotate使用,看看实现效果:)见插图2

 

还是老样子,让它旋转起来,这里我还是用两种方式,一种是定时器,一种是手动接管事件循环的方式(Windows的实现是在消息循环外接管消息循环)。

 

1.      用定时器的版本:

Fontwidget.h:

#ifndef FONTWIDGET_H

#define FONTWIDGET_H

 

#include <QtGui/QWidget>

#include <QPainter>

#include <QObject>

 

namespace Ui

{

   class FontWidget;

}

 

class FontWidget : public QWidget

{

   Q_OBJECT

 

public:

   FontWidget(QWidget *parent = 0);

   ~FontWidget();

 

protected:

   void paintEvent(QPaintEvent *event);

   void showEvent(QShowEvent* event);

   void timerEvent(QTimerEvent *event);

 

private:

   Ui::FontWidget *ui;

   int myTimerID;

   unsigned int miOritation;

   QFont *pFont;

   QString *pText;

};

 

#endif // FONTWIDGET_H

 

 

Fontwidget.cpp:

#include "fontwidget.h"

#include "ui_fontwidget.h"

 

FontWidget::FontWidget(QWidget *parent)

: QWidget(parent), ui(new Ui::FontWidget)

{

   ui->setupUi(this);

   miOritation = 0;

   pFont = new QFont("Times",50);

   pText = new QString(tr("   Rotation"));

 

}

 

FontWidget::~FontWidget()

{

   delete ui;

}

 

void FontWidget::showEvent(QShowEvent* event)

{

   myTimerID = startTimer(33);

}

 

void FontWidget::timerEvent(QTimerEvent *event)

{

   if(event->timerId() == myTimerID)

   {

      miOritation += 1;

      repaint();

   }

 

}

 

void FontWidget::paintEvent(QPaintEvent *event)

{

   QPainter painter(this);

   int x = (width() / 2);

   int y = (height() / 2);

   painter.setViewport(x, y, width(), height());

 

   painter.setFont(*pFont);

 

   for(int i = 0; i < 12; ++i)

   {

      painter.rotate(30+miOritation);

      painter.drawText(0, 0, *pText);

   }

}

 

按照正常的思路就是这样做了,但是会发现严重的问题(虽然效果也很有意思),那就是一个循环内多次的rotate后的文字竟然速度不一样,实现了奇怪的后面文字追前面文字的效果。。。。。。。。如插图3所示。呵呵,还好有宝典在手。。。。。C++ GUI Qt4编程》第二版第八章中有类似的描述:“for循环中的代码有一个小缺陷,如果执行了更多的迭代,这一问题会变得很明显。每次调用rotate(),就高效地用一个旋转矩阵去乘当前的世界变换,从而创建一个新的世界变换。浮点数的舍入误差不断的累积,得到了越来越不准确的世界变换。”就是这个原因,才有了我们这样的效果。我们这里比原书中的例子rotate了更多次,因为这里是动画(每秒30帧左右),原书是静态画面。原书的解决方案是使用QPaintersave(),restore()函数为每次迭代保存和加载原始的矩阵。

正确的做法是:

void FontWidget::paintEvent(QPaintEvent *event)

{

   QPainter painter(this);

   int x = (width() / 2);

   int y = (height() / 2);

   painter.setViewport(x, y, width(), height());

 

   painter.setFont(*pFont);

 

   for(int i = 0; i < 12; ++i)

   {

      int liOritation = 30 * i + miOritation;

      painter.save();

      painter.rotate(liOritation);

      painter.drawText(0, 0, *pText);

      painter.restore();

   }

}

这样的效果就正常了,而且正常的惊人,因为我们没有为图形的显示进行任何优化以防止闪烁,(简单图形编程的学习(1---文字 (Windows GDI实现)》中类似的例子闪烁的就非常明显)在《C++ GUI Qt4编程》中说明了Qt4竟然会自动为我们对图形的现实进行双缓冲处理-_-!呵呵,甚至不用我们通过编程手动指定就能实现这样的效果,这也算是Qt框架设计之人性化的又一个体现吧,我很享受这样的人性化^^

 

2.      手动接管事件循环的版本:

这个版本就类似于《简单图形编程的学习(1---文字 (Windows GDI实现)》中使用PeekMessage的版本了,以后要想用Qt做游戏并达到很好的动画效果估计也就靠这种方式来实现了。

Main:

#include <QtGui/QApplication>

#include "fontwidget.h"

#include <QtTest/QTest>

#include <QTime>

 

 

int main(int argc, char *argv[])

{

   QApplication a(argc, argv);

   FontWidget w;

   w.show();

   QTime timer;

   while(true)

   {

      timer.start();

      a.processEvents();

      w.addOritation();

      w.repaint();

 

      while(timer.elapsed() < 33)

      {

        QTest::qSleep(1);

      }

   }

}

 

fontwidget.h

#ifndef FONTWIDGET_H

#define FONTWIDGET_H

 

#include <QtGui/QWidget>

#include <QPainter>

#include <QObject>

 

namespace Ui

{

   class FontWidget;

}

 

class FontWidget : public QWidget

{

   Q_OBJECT

 

public:

   FontWidget(QWidget *parent = 0);

   ~FontWidget();

 

   void addOritation() { miOritation++; }

 

protected:

   void paintEvent(QPaintEvent *event);

   void keyPressEvent(QKeyEvent *event);

   void closeEvent(QCloseEvent *event);

private:

   Ui::FontWidget *ui;

   int myTimerID;

   unsigned int miOritation;

   QFont *pFont;

   QString *pText;

};

 

#endif // FONTWIDGET_H

 

fontwidget.cpp:

#include "fontwidget.h"

#include "ui_fontwidget.h"

#include <QtGui>

 

FontWidget::FontWidget(QWidget *parent)

: QWidget(parent), ui(new Ui::FontWidget)

{

   ui->setupUi(this);

   miOritation = 0;

   pFont = new QFont("Times",50);

   pText = new QString(tr("   Rotation"));

 

}

 

FontWidget::~FontWidget()

{

   delete ui;

}

 

void FontWidget::keyPressEvent(QKeyEvent *event)

{

   // 响应Esc键以退出程序

   if(event->key() == Qt::Key_Escape)

   {

      exit(0);

   }

}

 

void FontWidget::closeEvent(QCloseEvent *event)

{

   exit(0);

}

 

void FontWidget::paintEvent(QPaintEvent *event)

{

   QPainter painter(this);

   int x = (width() / 2);

   int y = (height() / 2);

   painter.setViewport(x, y, width(), height());

 

   painter.setFont(*pFont);

 

   for(int i = 0; i < 12; ++i)

   {

      int liOritation = 30 * i + miOritation;

      painter.save();

      painter.rotate(liOritation);

      painter.drawText(0, 0, *pText);

      painter.restore();

   }

}

 

效果非常好:)相对来说会比定时的版本动画平稳很多,并且,Qt还是让这样的动画没有任何闪烁,学习过的GUI不算不可计数,但是真正让人感觉很愉快的是在仅仅发现Qt一种,控制能力还是很强大,灵活,并且很为程序员着想,不愧是以前专门卖框架的公司开发的。。。。

 

六、   参考:

1. C++ GUI Qt4编程》第二版(原版名《C++ GUI Programming with Qt4,Second Edition》,Jasmin Blanchette,Mark Summerfield著,电子工业出版社

 

 

插图

插图1:

插图2:

插图3:

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com



阅读全文....

变“从U盘/硬盘安装Linux ”的痛苦为享受的工具,UNetbootin

遥想当年,因为没有刻录机,所以尝试过从硬盘安装ubuntu。。。。那也算是个痛苦的经历,google了好几篇无效的文章,导致焦头烂额,启动文件不匹配,找不到地方下载,grup,grupfordos等等东西都用尽了,最后才成功安装,但是今天再次尝试从U盘安装ubuntu的时候,轻松了就不止一星半点了,因为有了UNetbootin,实在佩服很多人的才智,我感觉到一个事情麻烦的时候,去抱怨,人家呢?将其变得简单。。。。。。。。。。。。。佩服,特别在Linux的时候,开源的世界,配合网络,几乎无所不能。。。。。新立得,apt等就是最佳例证,当年redhat9时代,rpmfind网站没有少去,血没有少吐,现在已经进入享受的时代了。。。。。。。。。。。

 

UNetbootin的使用方法就不多说了,那是怀疑大家的智商,还是感谢UNetbootin的制作者,因为他们,世界更加精彩。

 

UNetbootin的主页是:http://unetbootin.sourceforge.net

 

支持的Linux列表之长更加让人感叹:

 

 

 

再感谢sourceforge给了我们一个这样好的平台。。。。。怎么好像去领奖似的-_-!呵呵,让我们从郁闷中恢复过来,开始享受生活。

阅读全文....

Qt Creator的库依赖问题

Qt Creator的Qt库中途添加依赖的问题

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

 

这里说一下,Qt Creator实在不是个什么好产品,在创建工程的时候可以用GUI选择你需要依赖的Qt模块,这样你可以仅仅通过包含头文件名即可正确包含头文件,但是没有选择的话,你需要包含相对目录,比如假如我开始选择了依赖QtTest模块,我仅仅需要

#include <QTest>即可

不过我开始没有选择QtTest(没有想到sleep在这里面),这个时候我必须

#include <QtTest/QTest>

最奇怪的是,我没有办法制定工程添加QtTest的库依赖。。。。。。导致连接总是无法成功,(ld报错),搞了一会都没有发现Creator中有哪个地方可以添加(希望有高人可以指点),pro工程中也没有任何相关的地方可以编辑,最后没有办法,在工程目录下的makefile中自己手动添加了(要是没有在Unix/Linux下搞过开发,学过Makefile+gcc的人估计要吐血了)。另外,也理解了为啥没有包含模块就必须要添加相对路径了,MakefileIncludes中没有指定相应的目录。指定后就完全等同于开始就选择了库依赖了。

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

简单图形编程的学习(2)---点 (Windows GDI实现)


简单图形编程的学习(2--- (Windows GDI实现)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

一、   又一顿牢骚

虽然知道不应该老是说些与技术无关的话。。。。但是有的时候又总是想说。。。。难怪有同事说我最近已经有点像唐僧了-_-!总而言之,因为相对来说书看的太快,(现在租的房子离公司太远,老是坐地铁,导致有了非常固定的看书时间),而因为工作一直太忙,一直加班回家太晚的原因,所以实际的实践太慢(基本上现在就是以写博客的形式),所以QtAndroid的部分要是同步跟上我DirectX的书都要看好几本了,这样的方式好像不太好,所以目前暂时还是以Windows的为主了。。。。这也体现了一点理想与现实的差距-_-!虽然初期项目目标过大,项目中即时调整起码还能保证项目完成吧。。。。。(扯的远了)。

 

二、   画点

很简单的一个Windows GDI函数,SetPixel,原型如下:

COLORREF SetPixel(
  HDC hdc,           // handle to DC
  int X,             // x-coordinate of pixel
  int Y,             // y-coordinate of pixel
  COLORREF crColor   // pixel color
);

 

也许属于最最简单的Windows GDI 之一了,但是如同在Small Basic画点中所讲解的一样,画点这样简单的函数可以实现无限的效果。还是那句话,能够设置一个像素点,就能够描绘一个世界。。。。。。唯一限制程序实现的就是编写程序人的思维。

因为Windows代码本身的复杂性,不能如Small Basic那样直击要害,要是每次给出完整代码太占用空间,这里给出《Tricks Of the Windows Game Programming GURUS》一书中的框架代码,方便以后代码的添加。

// T3D Game Console, creates a game console application

 

// INCLUDES ///////////////////////////////////////////////

#define WIN32_LEAN_AND_MEAN  // just say no to MFC

 

#include <tchar.h>

#include <windows.h>   // include important windows stuff

#include <windowsx.h>

#include <mmsystem.h>

#include <iostream> // include important C/C++ stuff

#include <conio.h>

#include <stdlib.h>

#include <malloc.h>

#include <memory.h>

#include <string.h>

#include <stdarg.h>

#include <stdio.h>

#include <math.h>

#include <io.h>

#include <fcntl.h>

 

// DEFINES ////////////////////////////////////////////////

 

// defines for windows

#define WINDOW_CLASS_NAME _T("WINCLASS1")

 

// MACROS /////////////////////////////////////////////////

 

#define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)

#define KEYUP(vk_code)   ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

 

// GLOBALS ////////////////////////////////////////////////

HWND      main_window_handle = NULL; // globally track main window

HINSTANCE hinstance_app      = NULL; // globally track hinstance

 

char buffer[80];                     // general printing buffer

 

#define FRAME_PER_SECOND (20)

#define TIME_IN_FRAME (1000/FRAME_PER_SECOND)

#define WIDTH 800

#define HEIGHT 600

 

 

// FUNCTIONS //////////////////////////////////////////////

LRESULT CALLBACK WindowProc(HWND hwnd,

                    UINT msg,

                    WPARAM wparam,

                    LPARAM lparam)

{

   // this is the main message handler of the system

   PAINTSTRUCT   ps;     // used in WM_PAINT

   HDC           hdc;  // handle to a device context

   char buffer[80] = {0};        // used to print strings

 

   // what is the message

   switch(msg)

   { 

   case WM_CREATE:

      {

        // do initialization stuff here

        // return success

        return(0);

      } break;

 

   case WM_PAINT:

      {

        // simply validate the window

        hdc = BeginPaint(hwnd,&ps);

 

        // end painting

        EndPaint(hwnd,&ps);

 

        // return success

        return(0);

      } break;

 

   case WM_DESTROY:

      {

 

        // kill the application, this sends a WM_QUIT message

        PostQuitMessage(0);

 

        // return success

        return(0);

      } break;

 

   default:break;

 

   } // end switch

 

   // process any messages that we didn't take care of

   return (DefWindowProc(hwnd, msg, wparam, lparam));

 

} // end WinProc

 

///////////////////////////////////////////////////////////

 

int Game_Main(void *parms = NULL, int num_parms = 0)

{

   DWORD dwStartTime;

 

   dwStartTime = GetTickCount();

   // this is the main loop of the game, do all your processing

   // here

 

   // for now test if user is hitting ESC and send WM_CLOSE

   if (KEYDOWN(VK_ESCAPE))

      SendMessage(main_window_handle,WM_CLOSE,0,0);

 

   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)

   {

      Sleep(1);

   }

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Main

 

////////////////////////////////////////////////////////////

 

int Game_Init(void *parms = NULL, int num_parms = 0)

{

   // this is called once after the initial window is created and

   // before the main event loop is entered, do all your initialization

   // here

 

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Init

 

/////////////////////////////////////////////////////////////

 

int Game_Shutdown(void *parms = NULL, int num_parms = 0)

{

   // this is called after the game is exited and the main event

   // loop while is exited, do all you cleanup and shutdown here

 

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Shutdown

 

// WINMAIN ////////////////////////////////////////////////

int WINAPI WinMain( HINSTANCE hinstance,

              HINSTANCE hprevinstance,

              LPSTR lpcmdline,

              int ncmdshow)

{

 

   WNDCLASSEX winclass; // this will hold the class we create

   HWND     hwnd; // generic window handle

   MSG        msg;    // generic message

   HDC        hdc;      // graphics device context

 

   // first fill in the window class stucture

   winclass.cbSize         = sizeof(WNDCLASSEX);

   winclass.style        = CS_DBLCLKS | CS_OWNDC |

      CS_HREDRAW | CS_VREDRAW;

   winclass.lpfnWndProc  = WindowProc;

   winclass.cbClsExtra   = 0;

   winclass.cbWndExtra   = 0;

   winclass.hInstance    = hinstance;

   winclass.hIcon        = LoadIcon(NULL, IDI_APPLICATION);

   winclass.hCursor    = LoadCursor(NULL, IDC_ARROW);

   winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);

   winclass.lpszMenuName = NULL;

   winclass.lpszClassName = WINDOW_CLASS_NAME;

   winclass.hIconSm        = LoadIcon(NULL, IDI_APPLICATION);

 

   // save hinstance in global

   hinstance_app = hinstance;

 

   // register the window class

   if (!RegisterClassEx(&winclass))

      return(0);

 

   // create the window

   if (!(hwnd = CreateWindowEx(NULL,                  // extended style

      WINDOW_CLASS_NAME,     // class

      _T("Show Point 0.1"), // title

      WS_OVERLAPPEDWINDOW | WS_VISIBLE,

      0,0,    // initial x,y

      WIDTH,HEIGHT,  // initial width, height

      NULL,   // handle to parent

      NULL,   // handle to menu

      hinstance,// instance of this application

      NULL))) // extra creation parms

      return(0);

 

   // save main window handle

   main_window_handle = hwnd;

 

   // initialize game here

   Game_Init();

 

   // enter main event loop

   while(TRUE)

   {

      // test if there is a message in queue, if so get it

      if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))

      {

        // test if this is a quit

        if (msg.message == WM_QUIT)

           break;

 

        // translate any accelerator keys

        TranslateMessage(&msg);

 

        // send the message to the window proc

        DispatchMessage(&msg);

      } // end if

 

      // main game processing goes here

      Game_Main();

 

   } // end while

 

   // closedown game here

   Game_Shutdown();

 

   // return to Windows like this

   return(msg.wParam);

 

} // end WinMain

 

///////////////////////////////////////////////////////////

 

 

 

1.      随机在屏幕上画随机颜色点

int Game_Main(void *parms = NULL, int num_parms = 0)

{

   DWORD dwStartTime;

   HDC hdc;

 

   dwStartTime = GetTickCount();

   // this is the main loop of the game, do all your processing

   // here

 

   // for now test if user is hitting ESC and send WM_CLOSE

   if (KEYDOWN(VK_ESCAPE))

      SendMessage(main_window_handle,WM_CLOSE,0,0);

 

  

   hdc = GetDC(main_window_handle);

 

   // draw 1000 pixels

   for (int index=0; index < 1000; index++)

   {

      // get random position

      int x = rand()%WIDTH;

      int y = rand()%HEIGHT;

 

      COLORREF color = RGB(rand()%255,rand()%255,rand()%255);

      SetPixel(hdc, x,y, color);

 

   } // end for index

 

   // release the dc

   ReleaseDC(main_window_handle, hdc);

 

   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)

   {

      Sleep(1);

   }

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Main

 

同样还是简单,当然,前提是框架存在并且熟悉了一大堆Windows的特性以后,说实话,Windows的死消息机制也不是一天两天就熟悉的了的,我当年学MFC的时候那是一愣一愣的,用了较旧以后才能大概知道怎么回事儿。知道这些以后,剩下的也就是一个Rand函数+SetPixel函数的理解量了。这里不放截图了,这么简单的东西放个截图我都觉得没有意思。

 

2.      老电视机雪花点的效果:

int Game_Main(void *parms = NULL, int num_parms = 0)

{

   DWORD dwStartTime;

   HDC hdc;

 

   dwStartTime = GetTickCount();

   // this is the main loop of the game, do all your processing

   // here

 

   // for now test if user is hitting ESC and send WM_CLOSE

   if (KEYDOWN(VK_ESCAPE))

      SendMessage(main_window_handle,WM_CLOSE,0,0);

 

  

   hdc = GetDC(main_window_handle);

 

   // draw 1000 pixels

   for (int index=0; index < 500; index++)

   {

      // get random position

      int x = rand()%WIDTH;

      int y = rand()%HEIGHT;

 

      COLORREF color = RGB(255,255,255);

      SetPixel(hdc, x,y, color);

 

   } // end for index

 

   // release the dc

   ReleaseDC(main_window_handle, hdc);

 

   Sleep(10);

   InvalidateRect(main_window_handle, NULL, TRUE);

   //while(GetTickCount() - dwStartTime < TIME_IN_FRAME)

   //{

   // Sleep(1);

   //}

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Main

 

这样基本能够达到效果,但是实际上还有点问题,为了统一程序的架构方便讲解才这样写代码,这里因为利用Windows原有消息去擦除原有点,放在WM_DRAW中去做画点效果会更好一点。

 

3.      闪烁的星空:

#define POSITION_COUNT (500)

int gaWidth[POSITION_COUNT];

int gaHeight[POSITION_COUNT];

 

///////////////////////////////////////////////////////////

 

int Game_Main(void *parms = NULL, int num_parms = 0)

{

   DWORD dwStartTime;

   HDC hdc;

 

   dwStartTime = GetTickCount();

   // this is the main loop of the game, do all your processing

   // here

 

   // for now test if user is hitting ESC and send WM_CLOSE

   if (KEYDOWN(VK_ESCAPE))

      SendMessage(main_window_handle,WM_CLOSE,0,0);

 

  

   hdc = GetDC(main_window_handle);

   for (int index=0; index < POSITION_COUNT; index++)

   {

      SetPixel(hdc, gaWidth[index], gaHeight[index], RGB(255,255,255));

      gaWidth[index] += 1;

      gaHeight[index] += 1;

   } // end for index

 

   Sleep(1000);

 

 

   Sleep(10);

   // release the dc

   ReleaseDC(main_window_handle, hdc);

   //while(GetTickCount() - dwStartTime < TIME_IN_FRAME)

   //{

   // Sleep(1);

   //}

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Main

 

////////////////////////////////////////////////////////////

 

int Game_Init(void *parms = NULL, int num_parms = 0)

{

   // this is called once after the initial window is created and

   // before the main event loop is entered, do all your initialization

   // here

 

   // 一次初始化一个随机POSITION_COUNT大小的数组用于固定位置

   for (int index=0; index < POSITION_COUNT; index++)

   {

      // get random position

      gaWidth[index] = rand()%WIDTH;

      gaHeight[index] = rand()%HEIGHT;

 

   } // end for index

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Init

 

Small Basic实现的方式我发现闪烁效果没有Small Basic好,原因可能是CWin32程序速度比Small Basic快太多了,以至于几乎看不到闪烁,于是,按上面这样将闪烁实际的用Sleep停留一下才能看到较好的效果。也算是不同语言实现同一个算法(大概这么称呼实现这个效果的方法吧)需要注意的不同之处。

 

 

4.      屏幕刮花效果

///////////////////////////////////////////////////////////

 

int Game_Main(void *parms = NULL, int num_parms = 0)

{

   DWORD dwStartTime;

   HDC hdc;

 

   dwStartTime = GetTickCount();

   // this is the main loop of the game, do all your processing

   // here

 

   // for now test if user is hitting ESC and send WM_CLOSE

   if (KEYDOWN(VK_ESCAPE))

      SendMessage(main_window_handle,WM_CLOSE,0,0);

 

  

   hdc = GetDC(main_window_handle);

   for (int index=0; index < POSITION_COUNT; index++)

   {

      SetPixel(hdc, gaWidth[index], gaHeight[index], RGB(255,255,255));

 

      // 这算是实现的一种,横向刮花

      gaWidth[index] += 1;

 

      // 这算是实现的另一种,斜向刮花

      //gaWidth[index] += 1;

      //gaHeight[index] += 1;

 

      // 这算是实现的又一种,纵向刮花,纵向刮花建议配合血红色颜色观看效果。。。。。。

      //gaHeight[index] += 1;

 

   } // end for index

 

   // release the dc

   ReleaseDC(main_window_handle, hdc);

   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)

   {

      Sleep(1);

   }

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Main

 

////////////////////////////////////////////////////////////

 

int Game_Init(void *parms = NULL, int num_parms = 0)

{

   // this is called once after the initial window is created and

   // before the main event loop is entered, do all your initialization

   // here

 

   // 一次初始化一个随机POSITION_COUNT大小的数组用于固定位置

   for (int index=0; index < POSITION_COUNT; index++)

   {

      // get random position

      gaWidth[index] = rand()%WIDTH;

      gaHeight[index] = rand()%HEIGHT;

 

   } // end for index

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Init

 

一个屏幕刮花效果,不同的方向给人感受完全不同,最有意思的是纵向向下的红色效果,如插图1.让我想起某游戏死亡时的结束画面,满眼都是向下流动的血液。。。。。。。。

 

5.      移动的星空:

本来用我在Small Basic中的方法实现也可以出现移动的星空的,但是我发现《Tricks of the Windows Game Programming GURUS》一书中的例子更加有趣,这里引用一下了。

///////////////////////////////////////////////////////////

 

void Init_Stars(void)

{

// this function initializes all the stars

 

for (int index=0; index < NUM_STARS; index++)

    {

    // select random position

    stars[index].x = rand()%WINDOW_WIDTH;

    stars[index].y = rand()%WINDOW_HEIGHT;

 

    // set random velocity  

    stars[index].vel = 1 + rand()%16;

 

    // set intensity which is inversely prop to velocity for 3D effect

    // note, I am mixing equal amounts of RGB to make black -> bright white   

    int intensity = 15*(17 - stars[index].vel);

    stars[index].col = RGB(intensity, intensity, intensity);

 

    } // end for index

 

} // end Init_Stars

 

////////////////////////////////////////////////////////////

 

void Erase_Stars(void)

{

// this function erases all the stars

for (int index=0; index < NUM_STARS; index++)

    SetPixel(global_dc, stars[index].x, stars[index].y, RGB(0,0,0));

 

} // end Erase_Stars

 

////////////////////////////////////////////////////////////

 

void Draw_Stars()

{

// this function draws all the stars

for (int index=0; index < NUM_STARS; index++)

    SetPixel(global_dc, stars[index].x, stars[index].y, stars[index].col);

 

 

} // end Draw_Stars

 

////////////////////////////////////////////////////////////

 

void Move_Stars(void)

{

// this function moves all the stars and wraps them around the

// screen boundaries

for (int index=0; index < NUM_STARS; index++)

    {

    // move the star and test for edge

    stars[index].x+=stars[index].vel;

 

    if (stars[index].x >= WINDOW_WIDTH)

        stars[index].x -= WINDOW_WIDTH;

   

    } // end for index

 

} // end Move_Stars

 

////////////////////////////////////////////////////////////

 

int Game_Main(void *parms = NULL, int num_parms = 0)

{

// this is the main loop of the game, do all your processing

// here

 

// get the time

DWORD start_time = GetTickCount();

 

// erase the stars

Erase_Stars();

 

// move the stars

Move_Stars();

 

// draw the stars

Draw_Stars();

 

// lock to 30 fps

while((start_time - GetTickCount() < 33));

 

// for now test if user is hitting ESC and send WM_CLOSE

if (KEYDOWN(VK_ESCAPE))

   SendMessage(main_window_handle,WM_CLOSE,0,0);

 

// return success or failure or your own return code here

return(1);

 

} // end Game_Main

 

////////////////////////////////////////////////////////////

 

int Game_Init(void *parms = NULL, int num_parms = 0)

{

// this is called once after the initial window is created and

// before the main event loop is entered, do all your initialization

// here

 

// first get the dc to the window

global_dc = GetDC(main_window_handle);

 

// initialize the star field here

Init_Stars();

 

// return success or failure or your own return code here

return(1);

 

} // end Game_Init

 

/////////////////////////////////////////////////////////////

 

int Game_Shutdown(void *parms = NULL, int num_parms = 0)

{

// this is called after the game is exited and the main event

// loop while is exited, do all you cleanup and shutdown here

 

// release the global dc

ReleaseDC(main_window_handle, global_dc);

 

// return success or failure or your own return code here

return(1);

 

} // end Game_Shutdown

 

书中例子利用不同的亮度,及不同的移动速度来模拟一种类3D的效果。。。。。。。。。。。。估计不是一般的人能想到的,呵呵,要知道,仅仅利用了一个SetPixel函数。。。。。。。

 

Have Funaha?在有了一个大概的思路以后,其实Window下面的GDI编程也没有那么难吧,的确是没有那么难吧?毕竟我们专注的也就一个SetPixel函数而已了 ,越专注反而能让我们有更多新的想法。

这里用Windows GDI来实现我从Small Basic学到的一招显示文字的华丽技巧,你以前要是从来没有看过类似例子(也不是个在图形编程领域混过很多年的骨灰级程序员)你别说看了没有感觉惊艳,要知道其实仅仅是利用了画点和文字输出两个如此平常而简单的特性。

6.      星空中的文字

///////////////////////////////////////////////////////////

 

int Game_Main(void *parms = NULL, int num_parms = 0)

{

   DWORD dwStartTime;

 

   dwStartTime = GetTickCount();

   // this is the main loop of the game, do all your processing

   // here

 

   // for now test if user is hitting ESC and send WM_CLOSE

   if (KEYDOWN(VK_ESCAPE))

      SendMessage(main_window_handle,WM_CLOSE,0,0);

 

  

   for (int index=0; index < 1000; index++)

   {

      // get random position

      int x = rand()%WIDTH;

      int y = rand()%HEIGHT;

 

      COLORREF color = RGB(rand()%255,rand()%255,rand()%255);

      SetPixel(ghDC, x,y, color);

 

   } // end for index

 

   TextOut(ghDC, 100, 100, CHAR_OUT, _tcslen(CHAR_OUT));

 

   // release the dc

   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)

   {

      Sleep(1);

   }

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Main

 

////////////////////////////////////////////////////////////

 

int Game_Init(void *parms = NULL, int num_parms = 0)

{

   // this is called once after the initial window is created and

   // before the main event loop is entered, do all your initialization

   // here

   HFONT hfont;

 

   ghDC = GetDC(main_window_handle);

 

   hfont = CreateFont( 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _T("新宋体"));

 

   SelectObject(ghDC, hfont);

 

   SetTextColor(ghDC, RGB(0,0,0));

   SetBkColor(ghDC, RGB(0,0,0));

 

   DeleteObject(hfont);

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Init

 

/////////////////////////////////////////////////////////////

 

int Game_Shutdown(void *parms = NULL, int num_parms = 0)

{

   // this is called after the game is exited and the main event

   // loop while is exited, do all you cleanup and shutdown here

 

   ReleaseDC(main_window_handle, ghDC);

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Shutdown

按照以前的实现思想,我很快写出了上述程序,结果效果和我想象的不太一样,反而有点像个面具。。。。。留出了眼睛的观察窗口。。。-_-!见插图2。实际的原因想了一下才知道,背景前景都是黑的,当时的第一想法是不能让星空给盖了,但是这样不都黑了啊?。。。。呵呵,改成下面这个样子,效果就出来了,多的行用红色标明,看了就明白了,让背景透明,能够被星空给盖了,这样才能显示出文字。

///////////////////////////////////////////////////////////

 

int Game_Main(void *parms = NULL, int num_parms = 0)

{

   DWORD dwStartTime;

 

   dwStartTime = GetTickCount();

   // this is the main loop of the game, do all your processing

   // here

 

   // for now test if user is hitting ESC and send WM_CLOSE

   if (KEYDOWN(VK_ESCAPE))

      SendMessage(main_window_handle,WM_CLOSE,0,0);

 

  

   for (int index=0; index < 300; index++)

   {

      // get random position

      int x = rand()%WIDTH;

      int y = rand()%HEIGHT;

 

      COLORREF color = RGB(rand()%255,rand()%255,rand()%255);

      SetPixel(ghDC, x,y, color);

 

   } // end for index

 

   TextOut(ghDC, 100, 100, CHAR_OUT, _tcslen(CHAR_OUT));

 

   // release the dc

   while(GetTickCount() - dwStartTime < TIME_IN_FRAME)

   {

      Sleep(1);

   }

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Main

 

////////////////////////////////////////////////////////////

 

int Game_Init(void *parms = NULL, int num_parms = 0)

{

   // this is called once after the initial window is created and

   // before the main event loop is entered, do all your initialization

   // here

   HFONT hfont;

 

   ghDC = GetDC(main_window_handle);

 

   hfont = CreateFont( 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _T("新宋体"));

 

   SelectObject(ghDC, hfont);

 

   SetTextColor(ghDC, RGB(0,0,0));

   SetBkMode (ghDC, TRANSPARENT);

 

   DeleteObject(hfont);

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Init

 

/////////////////////////////////////////////////////////////

 

int Game_Shutdown(void *parms = NULL, int num_parms = 0)

{

   // this is called after the game is exited and the main event

   // loop while is exited, do all you cleanup and shutdown here

 

   ReleaseDC(main_window_handle, ghDC);

 

 

   // return success or failure or your own return code here

   return(1);

 

} // end Game_Shutdown

 

我第一次看到这个例子的时候真的感叹作者是个天才-_-!也许是自己太笨了所以想不到用这样的方式去显示文字吧。这次来个系列效果,可以参考《简单图形编程的学习(2--- (small basic实现)》文中的插图效果,或者简单的自己运行一下就好了。

 

三、   小结

一个个简单的点就能够构成如此繁多的效果,简直有点不可思议,但是其实,能够绘制一个点,就能够绘制整个世界,要知道,整个屏幕不过也就是一个一个像素构成的,呵呵。其实,从另外的角度来说,一连串连续的点就能构成一条直线,一排排直线就能构成一个面,有了点,线,面,还有什么不够构成的?你可以表达整个世界。另外,就我看的老的游戏编程书籍介绍,DirectDraw的原始接口也仅仅是能画点/位图而已,游戏的开发的先驱们还不是用这样简单的接口实现了那么多画面丰富,效果华丽的2D游戏啊?

 

插图

插图一:

插图2:

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....