随笔-341  评论-2670  文章-0  trackbacks-0
 
    圣诞节人山人海啊,根本不能出去玩什么的,妹纸第二天还要考试,所以就在家里呆着,顺便把TreeView给做了。源代码都在Vczh Library++ 3.0(Candidate\GUI\GUIDemo\GUIDemo.sln)。下面是Direct2D渲染出来的带反锯齿的TreeView。





    这个TreeView是通过把ListView进行扩展,替换IItemProvider和IItemStyleProvider而完成的。在这里TreeView仍然使用Virtual List模式。程序员将有机会在用户展开某个TreeNode的时候才开始加载里面的内容。这次仍然可以修改template(控件皮肤)。当程序员提供了一个INodeProvider之后,GacUI已经准备了两个现成的类,将INodeProvider转成IItemProvider,将INodeItemStyleProvider转成IItemStyleProvider,从而将树形结构转换成列表结构而使用ListView进行显示和操作。这个过程也封装成了一个GuiVirtualTreeListControl。如果不需要提供自己的虚拟化算法的话,直接提供INodeProvider和INodeItemStyleProvider将可以直接使用GuiVirtualTreeListControl显示树形结构。

    将一个虚拟化的树形结构转换成一个虚拟化的列表结构是比较复杂的,特别是当需要高性能的情况下。因此GacUI内置了这个算法的支持。一般情况下程序员不需要修改这个过程,直接使用GuiVirtualTreeListControl就可以实现。这一次内置的Windows7皮肤同时实现了跟Windows7资源管理器非常相似的外观(特别是那些三角形)。DirectX版本有反锯齿,而GDI版本则没有。

    现在要完成的功能已经剩下下面所示(顺序与优先级无关):
    1、Ribbon
    2、Visual Studio那样子的Dock Panel
    3、Canvas
    4、完善键盘操作
    5、提供类似简化后的XAML一样的结构来保存界面
    6、基于XML(见5)的界面编辑器,类似简化后的Expression Blend
    7、具有跟XML内一样外观的,封装在DLL里的接口(带反射,以便实现XML,但是被设计成C++使用时将没有有性能损失)
    8、内置FreeScript3.0的支持,用于方便实现各种高级的皮肤,并且如果愿意的话,可以将整个程序用FreeScript3.0写
    9、UIAutomation
posted @ 2011-12-25 06:08 陈梓瀚(vczh) 阅读(3324) | 评论 (15)编辑 收藏

(很荣幸被华南理工大学软件学院邀请撰写此文,关于毕业那会儿找工作的一些事情)

前些日子被华南理工大学软件学院邀请回去参加一些活动,其中包括跟一些师弟师妹们进行座谈。期间就有一个人问,要怎么样才可以去微软。其实我从来没有想过这个问题,所以那个时候的答案自然就是微软的广告(编程好,数学好,态度好)了。09年大四那会儿,刚好碰上了美帝的次贷危机,令我们这些想去美帝的公司被剥削的这帮人倍感艰辛。所幸后来还是过五关斩六将,最后在实习结束之后成功留了下来。这其中的因果,显然不是面试的那几天所能够决定的,因此还得从hello world讲起。

 

我有幸从初二开始就学习编程。那个时候世界已经处于一个现代化的程度了,操作系统都有虚拟内存,有图形界面,有因特网,开发软件还有集成开发环境可用,跟一些老前辈所描述的编译一个程序还要换几次磁盘的日子已经完全不一样了。那个时候正值购买电脑半年,处于看见什么东西都感到十分好奇的时候,再加上父亲那个时候不太同意我玩游戏,所以我就在想什么时候也自己做几个游戏,就可以光明正大的玩了。所以在听到汕头华侨中学开Visual Basic 5.0的课的时候,感到比较兴奋。但是其兴奋程度比起初一为了上第一节电脑课兴奋过度,骑自行车超速以至于留了一大堆血没了几颗牙的那一天,已经可以忽略了。

 

那个时候还是21世纪的第一年,正处于上网费用巨贵无比、Google还刚起来没多久基本没人知道的时候,学习编程要比现在困难很多。当时想寻找什么知识,因特网基本上是没什么指望的,所以我就有了一个没事去书店的爱好。没过多久我就找到了一本《Visual Studio高级图形程序设计教程》。这本书我很喜欢,插图十分漂亮,而且还是使用Visual Basic编程绘制的,更是爱不释手。可惜内容过于高深,所以后来就有了初三的时候自学学会初步的立体解析几何,以及高三上课不听讲仅凭自己看数学分析后来还被我看明白了的故事。中间因为试图使用编程绘制很多复杂的图形和对图像进行各种复杂的变换,于是每当写程序之前都要在纸上推导长长的公式。如果程序的运行结果不对了,根本无从调试,只好重新推导,借以希望可以发现公式的几个bug以解释为什么会出现错误。从此以后我对符号运算就十分拿手。而且做数学物理作业也好,为了编程推导公式也好,需要计算的东西太多懒得到处寻找废纸,从而便获得了心算复杂过程的本领(可惜现在已经丧失了)。这顺带还给我带来了一个好处,就是高考数学选择题在发卷后不许动笔的10分钟内就被我全部心算出答案,而且全对了。

 

图形编程做久了,就想起了当初的理想,于是就搞游戏去了。那会儿看到了成都金点工作组开发的《圣剑英雄传》,点燃了我开发RPG的热情。在经历了几次失败之后,我终于在高二的正月初一那一天完成了《天地传》的所有编码工作,没过多久就上传到了GameRes的网站上。这是我第一个行数过万的程序。为了顺利完成它,我悟到了很多道理,包括为什么要面向对象,为什么要划分模块减少互相依赖。这也成为我后来开发自绘图形界面和脚本引擎的契机。后来我试图用OpenGL3D游戏,但是由于很难找到有共同爱好的美工跟我一起做,便作罢了。但是这却让我获得了很多时间,可以投入到图形界面和脚本引擎之中去。

 

后来我就萌发了解释高级语言的想法。这是我整个编程历史上的第一个转折点。那个时候我数据结构只会用链表,而且编译原理也好,设计模式也好,都还没听过。那个时候去解释高级语言自然是比较困难的。因此我经过很多天的苦思冥想自己想出了一个如今称之为一遍编译(也就是很烂)的方法来把一个简单的高级语言重新处理成一个简单的指令集语言,就跟汇编长得差不多。那个时候已经高三了,所以其实也没多少时间可以投入在编程上面,因此做出来的第一个原型是一个简化后的Pascal的解释器,用Delphi开发的。现在想起来,里面肯定有巨多内存泄露和性能问题,不过当时根本不知道这些东西是什么。在高中毕业之后的三个月无所事事的日子里,我就重新把这个东西设计了一遍,得到了一个几十页的计划。由于后来没来得及做完,就打印出来带去了华南理工大学。

 

刚进了大学没几天,就听一个大四的师姐说我们的班主任陈健老师是教编译原理的,于是我就把这一叠纸拿给了她看。她什么也没说(现在回想起来,只能是那一份设计实在是不堪入目……),就给了我一本编译原理的课本。我很快就看完了,然后用了里面的知识做了第一个真正意义上的脚本引擎,语法山寨了Java语言的一些简单的部分,还添加了一个编译的时候自动把模板参数都改成Object类型的语法,起了个名字叫JoveScript。后来上了Java的课,发现Java竟然真的这么干了,让我觉得好生奇怪。

 

后来我陆陆续续写了很多脚本引擎。大一的时候做的JoveScript是第一个我觉得还能见人的脚本引擎。第二个就是大二失败了一整年吸取了很多教训之后,于大三开发出的动态语言,名字叫FreeScript(可以在我的博客http://www.cppblog.com/vczh上找到)。最近正在打算将其更新到3.0来配合一个正在开发中的显卡加速的GUI类库GacUI。接下来就是在去微软上海的WCF Tools组实习的那一段时间里面,利用每天晚上的时间完成的一门纯函数式语言叫KernelFP,这后来成为了我的毕业设计。提交了毕业设计之后,我又在毕业前的几个月时间里面完成了CMinus。这不是编译原理课程设计上的那个简单到没法再简单的CMinus,而是一个完整的C语言编译器(其中函数指针的语法被我改掉了,但是仍然支持)。其编译结果是保存到内存中的一段X86二进制代码,可以将函数的起始地址强制转换成函数指针直接在C++程序中使用,这是因为我在生成指令的时候遵守了Visual C++中的一些在MSDN里描述得很清楚的约定。毕业后我又雄心勃勃地做了NativeX,是一个带泛型以及concept mappingC语言。前几个月我又试图山寨C#,但是无奈C#实在是太复杂,所以转而去做GacUI。图形界面(GUI)类库我也写了不少。继高中的时候为RPG而开发的两个控件类库之后,在上大学的过程中使用OpenGL开发的两次GUI类库均告失败。后来还封装了一次WindowsAPIVczh GUI),试图让其易用性接近VCLWinForm。毕业后我又尝试发了若干次基于渲染的GUI,换了几次架构,一直到现在正在开发的GacUI才感觉走上了正轨。我在这个过程中得到的一个结论就是:Windows Presentation Foundation的设计实在是太完美了……在做这些东西之余,我还开发了三次三维物体的软件渲染程序,前两个是在毕业前做的,最后一个是一年前因为一下子不知道要如何利用业余时间来充实生活而开发出来的,目的是用于打发时间。

 

在这里我想可以回答一个月前不能很好地回答师弟师妹们的一个问题了。如何能够在微软找到工作?因为我把我上面做的这些东西都写进了简历。同时如果你们到了大四才来问这个,就已经太迟了……

 

值得一提的是,我从大三开始指导一名基础几乎是零的、比我低两个年级的软件学院的一位学生学习编程。为了让对方在接受我为期3年的训练之后有扎实的C++基础、熟练的单元测试编写水平以及能够靠直觉给出一些不算太差的设计,我回顾了许多关于C++的内容,特别是给指针的几节课备课了好几天,并且每一天都要出一个作业。在这个过程中我深刻的感觉到,如果要快速提高自己的编程水平的话,你必须总是去做一些你做得出来,但是难度大到只要再难一点点你就做不出来的事情。再这么坚持好些年之后,肯定会进入高手的行列。因此我在安排作业的过程中,有意推迟了关于指针的内容。首先让对方接受变量和分支循环,然后要养成一个好的风格(譬如说不能老是用一个字母给变量命名之类),然后学会操作数组,接下来才是关于没有强制类型转换的指针的一些操作,并且在一个月之内做出一个带单元测试的字符串类。指针的重点是要对方深刻的理解,“指针本身就是一个指向位置的数字”这么一个概念。为此我特别设计(但没有实现)了一门只带有一个全局无限长数组的汇编语言来讲述指针背后一些复杂的概念。之后就是一些关于面向对象的知识、设计模式的知识、还有跟脚本引擎有关的一些东西。该学生的毕业设计是一个简单的动态语言的脚本引擎,并且该脚本引擎的实现正确地运行了我在上面模仿Linq的一个列表处理函数库。这个实现闭包一层套一层,到处都在给一个物体添加删除函数,创建各种延迟执行的迭代器,很是能够考验一个脚本引擎的实现。对方毕业后被网易招去了,并且在待遇上给予了一些人文关怀。

 

自己的编程历程不仅包括自己在业余时间内做的这些程序,而且也包括在微软实习和工作的过程。高中的时候就听说了华南理工大学有微软俱乐部的事情,再加上自己对微软也持有一定的向往,因此在入学之后,除了学院的学生会以外,我就一直在密切关注着微软俱乐部的招新,并且忽略其它所有社团。不过说实话在学生会和微软俱乐部的工作也纯属打酱油,没干过什么正事儿。大二的时候微软搜索技术中心(STC)来微软俱乐部收简历的时候,我在路上碰到了陈健老师,也就是之前提到的班主任,就跟她说了这个事情。后来由于对方说我年龄太小而作罢,因为其它人全部都是研究生。到了大三的时候,陈健老师就跟我提到她可以找老同学帮我投微软的实习简历,因此我于20083月份接到了微软上海的电话面试。电话面试有两次,第一次对方是一位HR,第二次则是一位软件工程师。在第二次电话面试的过程中,我们聊了上面提到的FreeScript,还针对一些数据结构和框架设计的问题进行了热情洋溢的讨论。没过几天,我就收到了面试通知,前往上海闵行区的紫竹数码信息港面试。那是我人生中的第一次面试。

 

微软的面试安排精确到秒,这跟某些公司比起来要人性化许多,不会动辄浪费别人数个小时的时间。实习的面试一共有三轮,对话全部使用英语,尽管里面只有一个是外国人。我还依稀记得被那个年轻的老外面试的时候由于过于紧张,而导致一道简单的问题没有给出最优解的事情。不过他们最终还是让我进入微软位于上海的一个WCF Tools小组实习。

 

这个小组有一位让我十分尊敬的软件开发主管。主管先生是一位热爱敏捷并且经常投身于实践中的人。他在我长达4.5个月的实习过程中,教给了我很多软件工程上的东西,而其中最重要的、让我受益匪浅的则是关于单元测试的内容。除此之外,我也体验了快速迭代、Scrum会议、结对编程以及基于源代码版本管理系统(我们使用的是TFS)进行多人协作开发的流程。在经历了为TechEd大会修改PetShop制作WCFDemo、为Visual Studio 2010WCF开发工具修bug和开发一个具有高度可扩展性的配置文件编辑器之后,我于200812月份结束了在微软的实习。经过了这次实习,我对源代码的掌控能力也得到了提高,并且直接体现在我利用业余时间开发的项目的代码质量上。

 

在实习结束之前,我获得了一次面试全职员工(FTE)的机会。当时形势十分严峻。2008年美国的次贷危机于10月份正式影响微软上海,公司在那一段时间决定减少全职员工的招聘数量。而我是11月份进行转正的面试,结果这件事情令我十分紧张。后来主管先生表示他的个人建议是希望我毕业后留下来继续工作,让我吃了一颗定心丸。实习生转全职员工的面试一共有五轮。其中令我印象非常深刻的是有一轮的面试官问了我很多非常复杂的问题,最后还考了我一道关于线索二叉树在线更新的问题,不过我已经记不清楚具体是什么内容了。我只记得我花了很长时间终于想到了一个正确的算法之后,时间就结束了,根本来不及在白板上写代码。后来我终于通过了面试,少数的几个名额里面终于被我拿走了一个。不过听说几个月后限制开始放宽,没有我面试的时候那么困难了。

 

在实习和面试的过程中,我觉得华南理工大学软件学院开设的很多课程其实都是十分有用的,特别是关于数据结构、设计模式和软件测试的内容。这些都是在工作中十分有用的知识,并且也需要在今后的工作中继续积累这些东西的经验。只不过因为学院学生人数众多,而一个新的学院总是免不了缺乏一些师资力量,所以我有很多同学都表示很难体会到课本中所提到内容的作用。想必如今应该比我们那几年要改善许多了。

 

面试结束到获得offer中间隔了几十天,最后HR的通知在除夕的那一天终于到来了。之后的半年时间我就在学校里面继续做自己的事情,偶尔参加几个活动介绍经验等等,还有就是跟一些人出去游玩。毕业后动身前往上海微软。中间发生了一些事情,因为名额变动的问题,我虽然拿的是WCF Toolsoffer,但是最后却被安排到SQL Server组,在此之前我并没有收到通知。由于我比较不喜欢数据库,对SQL Server了解很浅,所以我做了一年半的SQL Server Management Studio(也就是传说中的“界面”)的开发。在这期间我跟同事们传播了一些关于单元测试、界面开发、设计模式、Linq和语法分析器的知识。

 

这一年半的经历让我成长了许多,主要是比起实习,正式工作的时候总是免不了经常要跟别的团队、公司、民族、国家和物种进行热情洋溢的广泛交流,而且还占用了不少的时间。有些时候还要坐飞机前往美帝,感受一下社会主义的优越性。正式软件的界面部分十分复杂,不仅要在操作系统的DPI变动以及本地化(大部分内容是把界面上的文字翻译成别的语言)的过程中界面的布局需要自动调整,以便不让一些文字或者按钮只显示一半,还要照顾各式各样的残疾人(特别是失去视力的人群),并且对于某些自绘的复杂内容还要提供一些运行时的接口,使得自动测试团队可以完成他们的工作。这个经历让我感受到了开发一个严谨的界面是多么地不容易。另一个感受是关于需求变更的。设计模式的存在就是为了抵御需求变更,这个真理我直到工作之后才能明白。你必须把一个软件的架构设计得如此之好,才能在需求大规模变更之后,还能在整体上让你的代码是漂亮的、易于修改的、高性能的、并且是安全的。每一次改动都不能是打补丁,你总是需要重构来使得你的代码在任何一刻都在整体上是好的。为了达到这个目标,就需要熟练掌握并使用设计模式来开发项目。

 

微软的跟别的公司比起来罕有一个好处就是他会给你很多时间,让你慢慢把软件做好。而这个好的定义,当然是以功能和可维护性为重点。倘若一段代码以非常精妙的方法来高速完成一个任务,但是却复杂到哪怕写遍了注释也不能让后续维护的人看懂的话,那这段代码是没有实用价值的。一段好的代码,不在于它的设计有多么巧妙,不在于它的算法有多么高深,而在于它可以被几千个人同时开发10年,并且在持续添加功能的过程中,不会因为过于混乱而导致出现了重写的需要。

 

后来我因为一些原因申请了到微软亚洲研究院(MSRA)的人事调动。20111月份我在获得了经理的批准之后,从上海前往北京参加研究院的面试。这一次面试仍然有五轮。这次面试很难,其中一个面试官因为在我的简历上发现了很多跟编译器有关的东西之后,决定让我实现一个strncpy函数,要求是CPU对内存的访问次数要最少。这包含了很多诸如带宽、对齐和二进制字节位移操作等各种问题。方法本身就已经很繁琐,再加上纸上写代码总是免不了要犯错误,所以我依然没有时间把整个程序写完。另一个面试官老外在年轻的时候也做过一些编译器的事情,让我出乎意料的是他在面试的过程中没有跟我出题目,反而就编译器的各种算法和问题聊了整整一个小时,基本上我会的知识全部都因为要回答问题而说了出来。之后我跟这个人产生了深厚的友谊。

 

不久之后我就获得了调动的批准。在做了一些包括给上海的SQL Server团队建立单元测试标准之类的收尾工作之后,我于2011年的4月份前往北京,正式成为微软亚洲研究院的一员,做一些跟分布式系统相关的研究。

 

过往的这些事情给了我很多的启示。在程序员的生涯里面,最重要的就是保持对编程的热情,不要被生活的琐事所磨灭。其次是要给自己不断地创造一些足够困难但是又有办法完成的挑战,这样才可以总是让自己保持着一个快速前进的状态。最后,记得要感谢国家。
posted @ 2011-12-16 07:34 陈梓瀚(vczh) 阅读(17123) | 评论 (27)编辑 收藏

    今天给ListControl重构了一下,现在数据源、样式、排版和坐标轴变成了正交的四个模块。举个例子,只需要写一个针对同等行高从上到下的排版算法,那么通过坐标轴变换功能,就可以同时获得四种方向的排版了。这种功能是相当常见的。对于ListView那种块状排版来说,坐标轴变换可以获得八种方向的排版。最新的代码发布在了Vczh Library++3.0(Candidate\GUI\GuiDemo\GuiDemo.sln)贴图如下:









    GacUI的列表控件通过替换数据源、样式、排版和坐标轴变换等方式,可以被配置成普通列表、ListView、TreeView、甚至是更复杂一点的带折叠的ListView等等。而且它们都是基于Virtual List的,因此可以无压力显示超大数据源的内容。当然,这需要排版功能的配合,所以我最近一直在给列表控件编写预定义的很多数据源、样式、排版和坐标轴变换。这样就可以立刻拥有基本的控件了。如果要扩展的话,只需要把各种组合替换一下,或者自己编写一个样式,就可以得到很多好用的列表控件了(譬如说添加一个checkbox到ListViewItem上面)。

    Win7资源管理器那个ListView的ColumnHeader的样式已经开发完毕了,最后只要通过编写一个样式,就可以补上ListView剩下的最后那一个Detail模式了。

posted @ 2011-12-15 07:08 陈梓瀚(vczh) 阅读(2377) | 评论 (7)编辑 收藏
    (2011.12.5再次更新) 经过了两个星期的开发,GacUI添加了TabControl和ListView的一部分视图。这次的ListView的设计的目标是要跟win7的资源管理器相似,TabControl也是依照win7的本地样式设计的。代码仍然保存在Vczh Library++3.0(Candidate\GUI\GuiDemo\GuiDemo.sln)里,看图:





    Direct2D的对于文字的高级渲染效果还没有实现,除此之外,List、Detail、Tile和Information也还没有做。ListView有两个模式,一个是跟普通的ListView一样可以自行添加ListViewItem的,另一个是virtual list模式,需要自己实现一个支持IListViewItemView的GuiListControl::IItemProvider,然后使用ListView的那六个ContentProvider,就可以做出跟ListView一样的效果,但是性能巨快无比的列表了。因为添加的ListViewItem很多的话,性能的瓶颈会在添加到列表的过程中,而ListView本身丝毫没有区别。因此内容太多的话建议使用virtual list模式,好处是不需要每一个项目都new一个对象,ListView会用一个item index来回调出具体的数据内容。

    根据上一篇文章的评论,我决定以后发布GacUI的时候同时提供dll和一对h/cpp两种选项,其中h/cpp会根据功能提供几个裁剪的结果,而dll总是包含所有功能。使用dll的话,可以做到跟C#的WinForm和WPF一样,把应用程序的插件写在另一个dll里面。而h/cpp因为合并的文件太大,因此需要打开Visual C++的/bigobj选项,好处是可以深入使用到内部的所有功能,还能根据自己的需要进行修改,而不局限于dll所提供的GUI部分。

    为了配合GacUI的使用,跟QT的QML一样,我会升级以前开发的FreeScript2.0,做出一个3.0的版本来跟GacUI配合使用(这个功能是可选的,裁剪的h/cpp文件对将不强制包含脚本代码,但是dll总是包含脚本功能)。关键的功能是直接支持json(合法的json数据即合法的FreeScript3.0代码),容易使用的异步IO功能,还有跟linq一样好用的list comprehension,最后当然是跟javascript的语法想当接近但是直接支持class,不会跟javascript一样曲线救国。相信学习起来的难度非常小。这样就可以把一部分用C++写起来比较麻烦的代码用FreeScript3.0实现,并且这部分代码将可以在GacUI Editor内部编辑。GacUI会跟WPF一样提供两种调用方法,一种是C++自己new那些类,另一种就是使用GacUI Editor产生的xml了。我会尽量让xml方法简单易用,就跟XAML一样,不过因为我的对象模型显然没有XAML(主要是C#)那么复杂,相比会比XAML容易阅读和使用。

=============================
P.S. 经过若干天的艰苦奋斗终于完成了三个View和DirectX的支持,就剩下Detail一种最复杂的View了。放图:









posted @ 2011-12-03 07:09 陈梓瀚(vczh) 阅读(3598) | 评论 (27)编辑 收藏
    因为GacUI文件渐渐增多,为了让大家便于使用库,因此有下列三个选项,如果大家有空就评论,针对喜爱程度排序之:

    1、直接引用源代码。一个文件夹下有很多.h和.cpp,给出文档,告诉大家需要什么功能的时候需要引用什么东西,然后自行添加自己的工程文件。
    2、存在dll,并且对于一些因为模板的原因无法直接放进dll的功能将失去,需要使用则引用源代码。
    3、存在dll,并且使用比模板丑陋的方法来将本来使用模板做接口的功能暴露出来。
    4、只有一个.h和一个.cpp文件。我将类库通过功能切分(只有GUI,只有脚本,包含GUI和脚本和他们的互联等等若干选项),给出若干对.h和.cpp。大家选择需要的那一对添加进工程文件。这样的话,直接#include那个.h文件就直接拥有了所有功能。而且Visual C++拥有预编译头(precompiled header),可以自行将该.h和.cpp进行预编译,迅猛提高编译速度(使用stl的时候大家肯定丝毫没有感觉太多头文件带来的问题)。这样还可以分别清晰地提供windows版本和linux版本等文件对。

    我自己的倾向是使用4,因为某些原因,一旦把代码写进dll,有一些优美的模板实现的接口将被迫打散(譬如说stl,这些只是例子,我没有使用stl),所以觉得有些不爽。而且只添加两个文件(一个.h和一个.cpp),无论使用makefile还是工程文件,无论直接备份或者源码版本管理(codeplex、source forge、google code、github)都十分方便。
posted @ 2011-11-23 06:22 陈梓瀚(vczh) 阅读(4139) | 评论 (20)编辑 收藏
    GacUI今天终于支持菜单了。Windows7的菜单渐变样式十分复杂,让我端详了半个小时才搞清楚他的具体的绘图方法。代码上传到了Vczh Library++ 3.0的Candidate\GUI\GuiDemo\GuiDemo.sln。先看插图。



    制作菜单十分复杂,里面涉及到了很多黑暗的Windows API的知识,譬如说如何设置一个window的parent又不让他真正成为子窗口啦(所以parent总是在下面,但是却不包含它),譬如说如何设置全局鼠标钩子以便在合适的时候关掉菜单啦,如何不让菜单在单击的时候接受焦点啦,很多麻烦的事情。INativeWindow框架的windows实现在Candidate\GUI\NativeWindow\Windows\WinNativeWindow.cpp,可以看到很多处理这种事情的代码。

    现在GacUI还没有键盘遍历焦点控件和触发快捷键的功能,这个等到大部分控件都做完了再添加。下面需要做的有:
    1、带ItemTemplate的ComboBox
    2、带ItemTemplate的ListView和TreeView
    3、Ribbon
    4、ToolBar
    5、DocumentView和Canvas
    6、其他杂碎控件

    设计中的Accessibility的功能:
    1、键盘操作焦点
    2、不知道要不要提供UIAutomation,听说在美帝做软件没这个的话会很麻烦
    3、根据Windows的色盲选项来自动更换皮肤

    设计中的其他功能:
    1、控件专用反射功能
    2、因此可以提供UI Editor以及XML启动的功能
posted @ 2011-11-12 23:27 陈梓瀚(vczh) 阅读(3196) | 评论 (9)编辑 收藏
    自从我发现Windows XP开始就支持Windows Image Component之后,我发现读取图片这个问题瞬间就变简单了。因此我给GacUI添加了绘制图片的功能,并做了一个小小的GIF动画demo。代码仍然更新在了Vczh Library++3.0上的Candidate\GUI\GUIDemo\GUIDemo.sln上。F5可以直接运行,不过如果要双击打开exe的话,则要把GUIDemo\Resources目录复制到exe目录下。下面的效果图展示了一个GIF动画上覆盖若干个带有alpha通道的png图片:

(实际效果是会动的)

    因为GacUI试图隔离Windows API的所有信息,以便可以让程序员自由添加对操作系统(Windows或其他)和渲染技术(GDI、Direct2D或其他)的支持,因此我不得不暂时封装了一下。现在的这个接口只能读,不能做一些高级操作,以后会添加上。使用GacUI编写上述Demo的代码如下(具体实现可以去下载并阅读):
  1 #include "..\..\GUI\GacUI.h"
  2 
  3 GuiBoundsComposition* CreateImageFrame(Ptr<INativeImage> image, int frameIndex=0)
  4 {
  5     GuiImageFrameElement* element=GuiImageFrameElement::Create();
  6     element->SetImage(image, frameIndex);
  7 
  8     GuiBoundsComposition* composition=new GuiBoundsComposition;
  9     composition->SetOwnedElement(element);
 10     composition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement);
 11     return composition;
 12 }
 13 
 14 class GifAnimation : public Object, public IGuiGraphicsAnimation
 15 {
 16 protected:
 17     unsigned __int64            startTime;
 18     GuiImageFrameElement*        element;
 19 public:
 20     GifAnimation(GuiImageFrameElement* _element)
 21         :element(_element)
 22         ,startTime(DateTime::LocalTime().totalMilliseconds)
 23     {
 24     }
 25 
 26     int GetTotalLength()
 27     {
 28         return 1;
 29     }
 30 
 31     int GetCurrentPosition()
 32     {
 33         return 0;
 34     }
 35 
 36     void Play(int currentPosition, int totalLength)
 37     {
 38         unsigned __int64 ms=DateTime::LocalTime().totalMilliseconds-startTime;
 39         int frameIndex=(ms/100)%element->GetImage()->GetFrameCount();
 40         element->SetImage(element->GetImage(), frameIndex);
 41     }
 42 
 43     void Stop()
 44     {
 45     }
 46 };
 47 
 48 void SetupGifWindow(GuiControlHost* host)
 49 {
 50     host->GetBoundsComposition()->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren);
 51     INativeImageProvider* imageProvider=GetCurrentController()->GetImageProvider();
 52     Ptr<INativeImage> imageNew=imageProvider->CreateImageFromFile(L"Resources\\New.png");
 53     Ptr<INativeImage> imageOpen=imageProvider->CreateImageFromFile(L"Resources\\Open.png");
 54     Ptr<INativeImage> imageSave=imageProvider->CreateImageFromFile(L"Resources\\Save.png");
 55     Ptr<INativeImage> imageGif=imageProvider->CreateImageFromFile(L"Resources\\Gif.gif");
 56 
 57     GuiBoundsComposition* image1=CreateImageFrame(imageNew);
 58     GuiBoundsComposition* image2=CreateImageFrame(imageOpen);
 59     GuiBoundsComposition* image3=CreateImageFrame(imageSave);
 60     GuiBoundsComposition* image4=CreateImageFrame(imageGif);
 61     
 62     host->GetContainerComposition()->AddChild(image4);
 63     host->GetContainerComposition()->AddChild(image1);
 64     host->GetContainerComposition()->AddChild(image2);
 65     host->GetContainerComposition()->AddChild(image3);
 66 
 67     image1->SetBounds(Rect(Point(1010), imageNew->GetFrame(0)->GetSize()));
 68     image2->SetBounds(Rect(Point(5010), imageOpen->GetFrame(0)->GetSize()));
 69     image3->SetBounds(Rect(Point(9010), imageSave->GetFrame(0)->GetSize()));
 70 
 71     Ptr<GifAnimation> animation=new GifAnimation(image4->GetOwnedElement().Cast<GuiImageFrameElement>().Obj());
 72     host->GetGraphicsHost()->GetAnimationManager()->AddAnimation(animation);
 73 }
 74 
 75 /***********************************************************************
 76 GuiMain
 77 ***********************************************************************/
 78 
 79 void GuiMain()
 80 {
 81     INativeWindow* window=GetCurrentController()->CreateNativeWindow();
 82 #ifdef GUI_GRAPHICS_RENDERER_GDI
 83     window->SetTitle(L"Vczh GUI Demo (GDI)");
 84 #endif
 85 #ifdef GUI_GRAPHICS_RENDERER_DIRECT2D
 86     window->SetTitle(L"Vczh GUI Demo (Direct2D)");
 87 #endif
 88     window->SetClientSize(Size(800600));
 89 
 90     INativeScreen* screen=GetCurrentController()->GetScreen(window);
 91     Rect windowBounds=window->GetBounds();
 92     Rect screenBounds=screen->GetClientBounds();
 93     window->SetBounds(Rect(
 94         Point(
 95             screenBounds.Left()+(screenBounds.Width()-windowBounds.Width())/2,
 96             screenBounds.Top()+(screenBounds.Height()-windowBounds.Height())/2
 97             ),
 98         windowBounds.GetSize()
 99         ));
100 
101     GuiControlHost host(new win7::Win7WindowStyle);
102     SetupGifWindow(&host);
103     host.SetNativeWindow(window);
104 
105     GetCurrentController()->Run(window);
106 }


posted @ 2011-11-06 01:00 陈梓瀚(vczh) 阅读(2218) | 评论 (5)编辑 收藏
    GacUI完成了文本框控件和列表控件。本着可以替换Template的原则开发的列表控件,ItemTemplate也是可以替换的。下面的Demo展示了运行时替换ItemTemplate的效果。GacUI的列表控件都是强制VirtualList的,因此数据跟数据的表现被分开处理。如果不使用默认提供的ItemTemplate的话,可以开发自己的ItemTemplate替换进去。







    在点击左边的列表的时候,右边的控件并不是重新创建的,仅仅替换了一个ItemTemplate。替换了之后,数据会立刻作用在新的ItemTemplate上面,并显示新的列表的样式。下面两个图是Direct2渲染的普通控件和文本框控件:





    Demo的代码可以在Vczh Library++3.0的Candidate\GUI\GUIDemo\GUIDemo.sln下找到。
posted @ 2011-11-02 03:08 陈梓瀚(vczh) 阅读(2441) | 评论 (10)编辑 收藏
    大家肯定还记得上次我给NativeX开发IDE(参见这里这里)的事情。IDE使用C#进行开发,然后遇到了一个瓶颈,其中的一个问题就是GDI+渲染那么多各种颜色的文字实在是太慢了。再加上我也一直很想用C++开发界面,但是又找不到一个可以跟WinForm或者WPF一样既简单易用又灵活的东西,所以决定自己写。名字就叫GacUI好了,GPU Accelerated C++ User Interface。

    当然这并不是我第一次开发GUI了,开发GUI是一个惨痛的历史。在我还在读高中的时候,为了做一个RPG游戏就用Delphi开发了一套基于GDI的GUI。高三的时候升级了它。大一的时候使用OpenGL为了同样的目的开发了一次,大三的时候封装了API但是发现API真是超级复杂结果无法封装所有必须的功能,去年试图抄WPF但是由于我处理得不好性能太慢失败了。因此现在总结了之前开发GUI的所有经验教训,重新开发一套C++的GUI系统。

    这个系统的特征如下:
    1、排版灵活。现在Left, Top, Width, Height已经不是控件的属性了,转而放在了另一个层次里面。简单来说就是,GacUI支持在一个窗口上放很多的排版线,可以做各种复杂的排版,最后一个控件必须依附在排版线的矩形区域上面。可想而知,并不是所有的控件都是按照Left, Top, Width, Height来确定位置的。不过大家也不用害怕,只是这些属性已经被封装到特定的一组对象。
    2、样式灵活。简单来说就是换皮肤,但是这并不像VCLSkin那样只是重绘,这里的样式允许你把整个控件的外观都换掉(譬如说跟MacOS一样滚动条的两个方向按钮都挨在一起),就跟WPF更换控件的template一样。
    3、支持多渲染器。目前我内置了GDI和Direct2D渲染,大家根据需要可以开发自己的渲染器——不过我觉得一般都用不上。
    4、跨平台。这里指的是我把CreateWindow那样子的东西给隔离了,转而去实现一个INativeWindow(包括创建删除打开绘图设备等)。我默认提供了一个基于CreateWindow的实现,然后可选创建GDI或者Direct2D绘图设备。哪怕是将来大家想将GUI移植到Windows8 Metro设备(因为直接支持DirectX渲染,把控件的样式改成MetroUI那种感觉就行了)、在游戏里面创造虚拟窗口、或者干脆放到Linux上,仅需提供一套INativeWindow并挂载Linux上的高性能绘图设备(我知道linux上面的某些绘图api性能巨差,应该避免使用,个人推荐OpenGL)。
    5、渲染方便。渲染的方法是,我提供了很多预定义图元,可以将图元绑定到排版线上来决定其尺寸,最后每一个图元都会有渲染器安排一个图元渲染算法来渲染出来。如果你需要的外观刚好可以用预定义的图元表达的话,那可以直接拼装。否则,开发新的图元即可。

    控件的逻辑、样式、排版、渲染四套工序已经完全隔离。举个例子,我们都知道GroupBox和Panel除了是容器以外没有任何区别,因此我给GuiControl类编写了Win7GroupBoxStyle和Win7WindowStyle两套样式,使用不同的组合就可以创造出不同的外观,不需要所有的东西都因为外观有一点点变化就写成两个控件了。因此我们可以知道,如果你需要特别的外观,那么就写一个自己的Style。GacUI是开源的(不过暂时没有自己的项目首页,我把代码放在了Vczh Library++ 3.0的Candidate\GUI\GUI\下面),因此开发样式的时候还可以借鉴我预定义提供的Win7外观样式包的代码。

    因为没开发完,所以先贴个图。下面的图因为“可编辑文本元素”的Direct2D渲染代码还没写,所以只给出GDI得结果。Direct2D因为自带全屏幕去锯齿,所以比这个要好看很多。下一个IDE我就用GacUI来开发了,C#开发IDE难度很高啊,膜拜Mono开发SharpDevelop那帮人,我还是用C++就好了。



posted @ 2011-10-22 19:34 陈梓瀚(vczh) 阅读(5025) | 评论 (14)编辑 收藏

    之前一边做脚本引擎,一边山寨一个自绘的native C++的GUI框架并且可以切换GDI或者Direct2D渲染模式。因为抄了WPF的那种高级自动布局功能,所以必然需要知道如何测量文字大小。Direct2D测量文字大小比较麻烦,不像GDI有直接函数,并且用中英文搜好像都没人直接给出结果,还有人在博客上写“这种事情好像办不到”这样的文字。不过经过我遍历MSDN,还是找到了一个曲线救国的方法的,直接上代码:

    首先是创建IDWriteTextFormat:

 1                     IDWriteFactory* dwriteFactory=GetDirectWriteFactory();
 2                     IDWriteTextFormat* format=0;
 3                     HRESULT hr=dwriteFactory->CreateTextFormat(
 4                         fontProperties.fontFamily.Buffer(),
 5                         NULL,
 6                         (fontProperties.bold?DWRITE_FONT_WEIGHT_BOLD:DWRITE_FONT_WEIGHT_NORMAL),
 7                         (fontProperties.italic?DWRITE_FONT_STYLE_ITALIC:DWRITE_FONT_STYLE_NORMAL),
 8                         DWRITE_FONT_STRETCH_NORMAL,
 9                         (FLOAT)fontProperties.size,
10                         L"",
11                         &format);
12                     if(!FAILED(hr))
13                     {
14                         format->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
15                         return format;
16                     }
17                     else
18                     {
19                         return 0;
20                     }

    fontProperties是我自定义的一个结构就不用去管它了,参考MSDN就知道CreateTextFormat如何使用了。其中fontProperties.fontFamily是字体的名字。然后IDWriteTextFormat就扮演着GDI的“字体对象”的角色,可以用ID2D1RenderTarget进行绘制。ID2D1RenderTarget除了用IDWriteTextFormat当字体以外,还可以用IDWriteTextLayout当“添加多余信息的更复杂的字体”。测量文字的关键正是在这里。

    接下来我们借助IDWriteTextFormat来创建IDWriteTextLayout:

 1                     IDWriteTextLayout* textLayout=0;
 2                     HRESULT hr=GetDirectWriteFactory()->CreateTextLayout(
 3                         oldText.Buffer(),
 4                         oldText.Length(),
 5                         textFormat,
 6                         0,
 7                         0,
 8                         &textLayout);
 9                     if(!FAILED(hr))
10                     {
11                         DWRITE_TEXT_METRICS metrics;
12                         hr=textLayout->GetMetrics(&metrics);
13                         if(!FAILED(hr))
14                         {
15                             minSize=Size((int)ceil(metrics.widthIncludingTrailingWhitespace), (int)ceil(metrics.height));
16                         }
17                         textLayout->Release();
18                         return;
19                     }

    这里看minSize就知道如何测量文字的字体了。

    在这里放一张暂时的图片。我抄了WPF的那种方法,从布局和绘图元素直接开始可以构造GUI,因此演示了如何使用这些东西来创造一个Win7的按钮(带动画的哦)打开Vczh Library++3.0,下载代码并打开Candidate\GUI\GuiDemo\GuiDemo.sln,按F5就可以看到了效果了:



    这一个是Direct2D渲染的结果(我在工程文件指定了DXSDK的绝对路径,如果你们安装的地方不同改掉它既可编译了)。按钮基本上跟win7的效果一摸一样,但是这里使用Direct2D进行渲染。当按钮尺寸变化的时候,那个复杂的边框和里面的两个渐变和文字都可以自动对齐——但是这并不是hard code的,而是GUI的“布局功能”可以配置成这个样子。像这两个按钮一直处于右下角也是“布局功能”可以提供的功能。大家可以理解为这东西类似于C#的TableLayoutPanel。

    现在刚刚做好了按钮——跟WPF一样可以更换template,不过因为反正没人需要动态更换template所以我把template写在了构造函数里面,因此换肤这种事情就变得相当简单了——只要用布局功能跟图元拼凑成一个复杂的图形,然后实现各个控件所规定的“template接口”响应外观控制的消息就行了,内置动画支持(这个要运行的时候才能观察到)。

    为了方便,我在工程里面除了Debug和Release以外还加入了DebugDirect2D和ReleaseDirect2D两个配置,可以自由切换观看demo。


posted @ 2011-10-11 07:42 陈梓瀚(vczh) 阅读(5384) | 评论 (11)编辑 收藏
仅列出标题
共35页: First 4 5 6 7 8 9 10 11 12 Last