今天打开Windows 8的时候发现来自了Windows Essentials 2012的更新,发现里面已经包含了一个Windows Live Writer。以前每次打开cppblog直接写博客的时候都很苦逼,又懒得下载这些软件,结果今天发现既然已经装了,那就来试试看。用着的时候还挺舒服的。
插个图片试试:
posted @
2012-10-23 18:43 陈梓瀚(vczh) 阅读(2049) |
评论 (4) |
编辑 收藏
摘要: 昨晚终于发布了GacUI 0.4.0.0,也就是第五个release了。现在GacUI的源代码可以在三个地方找到,分别是:
Codeplex:http://gac.codeplex.com
Github:https://github.com/vczh/gac
主页:http://www.gaclib.net
这两个月在开发GacUI的对象的反射系统之外,一直在做Windows 8的皮肤。现在的GacUI已经拥有了两个皮肤,一个是模仿Windows 7的,另一个是模仿Windows 8的。GacUI在启动的时候,会根据Windows的版本来自己选择要用哪个皮肤做缺省皮肤。使用g::NewXXX()函数创建的控件都会直接使用当前的皮肤(如果没有用SetCurrentTheme替换掉的话,就是缺省皮肤)来创建控件,否则,直接new控件类的话,要传入一个创建好的皮肤对象。
阅读全文
posted @
2012-10-18 22:16 陈梓瀚(vczh) 阅读(3746) |
评论 (7) |
编辑 收藏
所有关于渲染的部分的代码可以在http://gac.codeplex.com下载下来之后,在\Libraries\GacUI\Source\GraphicsElement目录下面找到。
整个渲染系统的主要思想就是,图元(IGuiGraphicsElement)和渲染器(IGuiGraphicsRenderer)分开,而且粒度根据性能的要求粗细都有。为什么要这么设计呢?在前言里面说过,不同的渲染设备,譬如GDI和DirectX,需要的渲染策略和cache资源的方法都不太一样。因此为了让各个渲染设备的渲染器可以充分自定义渲染的策略,于是做出了这样的设计。
但是具体是怎么做的呢?在GacUI里面,首先可以用GetGuiGraphicsResourceManager来获取一个全局的资源管理器(GuiGraphicsResourceManager)对象。这个对象的主要作用就是注册各种创建图元和渲染器的工厂对象。为了让整个渲染系统运行起来,首先我们要把各种图元工厂(IGuiGraphicsElementFactory)注册进去。每一个图元工厂有自己的一个全局的名字。这样当你把一个图元工厂注册金资源管理器之后,从此就可以用图元的名字从资源管理器里面取出注册进去的图元工厂对象了。
其次,因为在运行的时候,每一个图元对象都会在内部保存一个专门给这个图元对象用的渲染器对象,具体的渲染设备的渲染器可以在这个渲染器对象里面cache一些资源,就可以达到为某个图元cache特殊的资源的目的了。因此为了给图元对象创建合适的渲染器对象,我们还需要将图元工厂的名字和一个渲染器工厂(IGuiGraphicsRendererFactory)关联起来。当这一步完成之后,我们就可以通过下面的代码来给一个图元关联上正确的渲染器对象:
IGuiGraphicsElement* element = xxxx;
IGuiGraphicsElementFactory* elementFactory = element->GetFactory();
IGuiGraphicsRendererFactory* rendererFactory = GetGuiGraphicsResourceManager()
->GetRendererFactory(elementFactory->GetElementTypeName());
IGuiGraphicsRenderer* renderer = rendererFactory->Create();
renderer->Initialize(element);
这样我们就从一个IGuiGraphicsElement对象构造出了对应的IGuiGraphicsRenderer对象,并且将这个渲染器对象和这个图元对象关联了起来。这一步完成之后,渲染器对象就会开始根据需要cache被关联的图元对象所需要的资源。然后我们只需要把渲染器对象的指针告诉图元对象,那么图元对象就可以在自己被更新的时候,通过调用renderer->OnELementStateChanged()适当通知一下渲染器对象,而且也可以用renderer->GetMinSize()来说的显示这个图元所需要的最小的矩形尺寸了。为什么尺寸要通过渲染器来计算呢?主要是因为具体怎么渲染是渲染器来控制的,所以尺寸当然也是需要让渲染其计算的,其中一个例子就是文字渲染了。
接下来就是如何规划图元的问题了。目前GacUI所有的图元如下所示:
Gui3DBorderElement
Gui3DSplitterElement
GuiGradientBackgroundElement
GuiImageFrameElement
GuiPolygonElement
GuiRoundBorderElement
GuiSolidBackgroundElement
GuiSolidBorderElement
GuiSolidLabelElement
GuiColorizedTextElement
我们可以看到,大部分的图元都是很简单的。GuiSolidLabelElement就稍微复杂一点,具有了一些诸如自动换行啊省略号这样的设置。而最复杂的就是GuiColorizedTextElement了,里面按行保存了文本之后,还按行给每一个字符分配了存放颜色的缓冲区,然后实现了字符串修改的时候缓冲区的分配释放更新等操作。为什么不设计一个GuiCharElement,而是做成了这两个东西呢?因为在普遍情况下,渲染器都支持对复杂的文字一次性渲染完成,如果我们把每一个字符都设计成一个图元,让排版引擎去渲染字符串的话,性能低下不说,效果可能还不如渲染器自己渲染出来的好。关于这里的一个典型的例子就是Windows所支持的可以连笔的OpenType技术了。另一个原因就是,在开发着色文本框的时候,如果所有的渲染过程不包含在一个图元,而是分散在各个字符图元的话,那更新文字和颜色的时候,无疑十分浪费内存,并且操作起来非常的麻烦,为了灵活性牺牲了太多的性能,得不偿失。
说完了图元和渲染器,最后一个要介绍的就是渲染目标对象(IGuiGraphicsRenderTarget)了。尽管渲染目标可以指向很多种地方,但是在一般情况下,渲染目标所指向的都是一个窗口的客户区域(client area)。尽管在设计上这样看起来仅仅是很自然,但是实际上这么一个对象却是必须的,因为Direct2D的一个render target创建出来的画刷等资源不能直接用在另一个render target上面,而且当render target挂掉的时候,那些资源要全部干掉,重新创建render target,并且重新创建资源。这一步作为一个bug登记在了GacUI里面,还没实现,所以现在Direct2D渲染的时候,把窗口最小化再打开,有时候会变黑。
渲染目标对象的另一个功能就是计算clipping了。在形成父子关系的排版对象绑定的图元在渲染的时候,子图元是不能超出父排版对象的矩形范围的。而且鉴于大量的对象可能处于不可见的位置,所以外围的驱动渲染的代码要在渲染对象完全被clip没了的时候(譬如说在一个具有滚动条的容器里面,一个因为滚动条的关系看不见的按钮),停止渲染看不见的那颗子树,加速渲染过程。而且各个渲染设备也需要处理类似于一个文字只有上半部分能看见这样的情形。所以排版对象就可以通过提供他自己的矩形范围给渲染目标对象,从而让渲染目标对象自己计算可见的矩形范围,从而配合整个渲染流程的进行。鉴于有一部分的渲染器需要的资源是从渲染目标对象来的,因此IGuiGraphicsRenderer还有一个叫做SetRenderTarget的函数,用于在渲染对象发生变化的时候,譬如说因为窗口最小化从而造成Direct2D的render target的时效,需要重新创建的时候,通知每一个图元绑定的渲染器说,整个渲染目标对象已经换掉了,一些资源可能要重新创建。
当然在这里需要提出的就是,在GacUI的GDI和Direct2D渲染器的实现里面,是有一些依靠引用计数全局cache的资源。譬如说在同一个渲染目标对象里面渲染的两个同样颜色的矩形,他在内部使用的具体的画刷就不会真的重复创建两次。尽管GDI和Direct2D的策略不同,GDI的画刷是全局的,而Direct2D的话刷只对一个render target有效,GacUI还是提供了一个通用的资源cache算法模板,让实现类似的功能更加方便。
有关渲染系统的内容就说到这里了,下一篇文章将会具体讲排版对象的内容。
posted @
2012-10-08 07:40 陈梓瀚(vczh) 阅读(3722) |
评论 (5) |
编辑 收藏
说起GacUI(www.gaclib.net,gac.codeplex.com),其实这个想法在我还在上大三的时候就已经有了。但是由于经验不足,在当时并没能够把这个东西给做出来,直到去年(2011)的国庆节为止。想想到现在也做了快一年了,GacUI也可以用来写一些不是特别残暴的C++GUI程序了。前几天有人问道,为什么在PC都快完蛋了并且大部分GUI都已经用C#来做的时候,我还要做这个东西呢?其实,这有两个原因:第一个我喜欢折腾C++;第二个C++好像也没什么特别好的GUI,因此也想尝试一下,如果做成了就维护下去,做不成了好歹还可以提高自己的水平,总之是不会浪费时间的。所以我就在想,GacUI写到现在也快一年了,并且我最近也看到cppblog上面有几个人也想搞搞GUI,因此我想把GacUI的一些设计思想,和我得到这些思想的过程写出来,顺便也介绍一下GacUI的架构,让一些有兴趣的人(特别是装配脑袋)也可以来折腾折腾。
GacUI的架构的最重要一点就是要跨平台。当然这不一定意味着我将来一定会把GacUI移植到别的什么操作系统去,但至少Windows的Classic Desktop和Metro的两套API就毫无相似之处,同时搞定他们,也算是跨平台了。而且就算是基于同一种API,上面还有不同的渲染器的API,譬如说GDI,譬如说Direct2D,他们也是截然不同。GacUI的设计至少要可以屏蔽掉他们的区别。当然,这在技术上有一个很好的方法来保证,就是GacUIIncludes.h里面不包含Windows.h的任何内容——因此至少在头文件里面,所有的东西都是跟Windows无关的。当然在非GUI的部分,我们还是需要Windows.h的,并且有些人喜欢对GacUI做点hack的操作,因此我还是在GacUI.h里面提供了几个额外的依赖于Windows.h的函数来暴露一些内部细节。那这样如何跨Classic Desktop和Metro呢?有一个简单的方法,就是可以在编译的时候给些宏开关,譬如说GACUI_WINDOWS_CLASSIC_DESKTOP(缺省)或者GACUI_WINDOWS_METRO之类的东西,来屏蔽掉不需要的部分。当然这部分在移植到Metro之前我不会加进去。
基于这个想法,如果大家阅读了GacUI的代码的话,会发现在文件\Libraries\GacUI\Source\NativeWindow\GuiNativeWindow.h里面定义了一个INativeController接口,而且目前只有Windows Classic Desktop一个实现。INativeController的内容很多,提供了跟具体的平台有关的操作,譬如说读写图片文件啦、创建消灭窗口啦、显示器操作啦、还有各种其他的输入输出等等。实现一个从头INativeController还是比较繁琐的,因为GUI这种对操作系统重度依赖的东西,想剥离开来,就会发现他依赖了一大坨API。这也解释了为什么INativeController的各个XXXService函数返回的对象的方法的总和有上百个。不过从Classic Desktop移植到Metro还是相对比较简单的,因为大部分内容还是可以共享的。
其次就是渲染器了。渲染器跟平台是交叉依赖的。譬如说OpenGL在linux上和Classic Desktop上都可以用,Direct2D在Classic Desktop上和Metro上都可以用,GDI只能在Classic Desktop上面用。因此这就是为什么我最终没有把渲染器也写在INativeController里面,而是把渲染器整个给屏蔽掉了,根本没有在GacUIIncludes.h里面给出他的接口。但是考虑到GacUI是一个支持换肤的GUI库,因此肯定需要让皮肤来自己决定如何绘图。后来我就想了一个办法,把渲染器的结构整个拿掉,替换成各种各样的图元(IGuiGraphicsElement)。所谓的图元就是类似于方形啊,圆形啊,填充啊,渐变啊,文字之类的东西。皮肤自己把图元按照一定的排版关系(在下文中有描述)拼装好,然后GacUI内部的一个小系统会利用Bridge和Abstract Factory两个模式的结合体(参考\Libraries\GacUI\Source\GraphicsElement\GuiGraphicsElement.h)来为这些图元分配好渲染器对象(IGuiGraphicsRenderer)。然后图元和渲染器之间用了Listener模式在交换信息。这样的好处是,当图元受到改动的时候,这个图元对象的专用渲染器对象可以选择cache一些信息,然后在窗口渲染的时候,只需要访问所有的渲染器对象(在排版对象GuiGraphicsComposition的组合项形成了一棵树),让他们渲染自己就可以了。
图元包含了所有需要渲染的数据,但是唯独没有把尺寸写进去,因为尺寸这种东西不应该让渲染器来负责,而应该让排版对象来负责。排版对象自己是一棵树,然后节点根节点之间有一些关系,这样就可以实现堆栈排版、表格排版、对齐(到某一些边上的)排版等等具体的排版算法。一个排版对象可以放置一个图元对象并让这个图元充满他,所以显而易见,有一些排版对象仅仅是用来计算尺寸的中间结果,上面不一定有图元对象的。当渲染开始的时候,排版对象首先跟图元对象获取数据,然后递归计算好整棵排版树的尺寸,最后把尺寸交给附着在上面的图元对象的专用渲染器对象来渲染。
大家可能会想,如果渲染一次都需要调用成千上万个虚函数的话,会不会性能低下啊?当然编译成Release运行会发现GacUI的性能还是相当高的。原因有两个。第一个是我对排版对象做了一些优化。举个例子,一个对象的尺寸至少要大于所有子对象的尺寸,这个事情计算起来是相当快的,不需要做cache。但是一个表格排版里面的所有小格子会互相挤来挤去,这个东西计算起来相当复杂(复杂度大越是平方,而且系数也不笑),所以结果要做cache。但是什么时候需要重新计算呢?度量方法很简单,就是每一个格子的最小尺寸发生了变化的时候。而且事实上大部分皮肤都是用表格来排版的,所以等于说大部分结果都有cache。所以排版部分的尺寸在每一次渲染的时候只需要做一些小计算就可以了。复杂的排版每一个排版对象相互之间都是有关系的,一个排版对象发生了变化,有可能导致另一个排版对象的尺寸需要修改,所以最简单的方法就是,不保存尺寸,每一次都直接重新算一次就可以了。在这个基础上,表格排版做一下cache,整个计算过程就会变得飞快。所以尽管每一次拖动窗口,或者鼠标滑过一次窗口,都要进行相当多的计算,但是因为有一个智能的cache,使得不仅运算速度变快,而且在添加新的排版对象类型的时候也根本不需要考虑自己会不会被cache的问题,开发起来也相当愉悦。
所以上面的三大模块(操作系统API隔离、渲染器、排版对象)已经足以让我在系统里面开一个窗口然后在上面放各种各样的东西了,譬如说组合成一个非常接近Windows7的按钮外观的一个矢量图。那控件要怎么办呢?其实一个控件,就是通过接收用户的输入,对一个排版对象上承载的一大堆图元进行更改。用户的输入和控件(GuiControl)本身的状态进行互动,然后控件把状态的变更提交给控件的皮肤(GuiControl::IStyleController),最后皮肤通过修改图元来把状态变更最终展现给用户。一个典型的例子就是,在使用Windows7皮肤的时候,鼠标移动到按钮上面去,他会触发一个动画慢慢变成蓝色。
GacUI的大体架构就是这个样子了。在接下来的几篇文章里面,我会详细介绍每一个子系统的内部结构,顺带做以下代码导读,大家敬请期待。
posted @
2012-09-17 22:31 陈梓瀚(vczh) 阅读(4439) |
评论 (24) |
编辑 收藏
Visual Studio 2012发布的那一天我就把它搞到手了。新的C++ IDE真的是劲爆了,写代码的感觉毫不亚于C#。我最喜欢的部分是智能补全和着色部分。如今C++的宏被渲染成屎红色,类型被渲染成屎绿色,参数被渲染成屎灰色,这样基本不需要要编译,看着颜色都知道有没有写对。智能补全已经赶超VAX,而且还实现了“缩写过滤”,譬如说输入PNT就可以在弹出列表里面显示所有大写字母为PNT的对象(譬如说ParsingNodeTransition)等等。这样做的好处是,我只要打有限几个字符就可以补完一整句了,输入速度大大提高。
所以我升级了几乎所有工程。文档生成部分由于还在使用DIA100,所以暂时没有升级到2012,不过这是迟早的事情。不过这次升级遇到了几个小问题。
第一个是,对于没有capture任何外部变量的lambda expression,它可以隐式转换成一个函数指针。这个功能VS2010是没有的,结果升级了之后造成了我几个重载函数的问题,不过解决这个东西还是很简单的,只要把lambda表达式先保存在一个vl::Func变量里面就好了。
第二个是WICImagingFactory。在Windows SDK 7.0里面,CLSID_WICImagingFactory指向了WIC的唯一一个版本。在Windows SDK 8.0里面,出现了CLSID_WICImagingFactory1和CLSID_WICImagingFactory2,并且CLSID_WICImagingFactory等于CLSID_WICImagingFactory2。问题就来了,Windows 7里面并没有WICImagingFactory2,结果我CoCreateInstance就是败了。一开始觉得很奇怪,后来想了想,直接用VS那强大的Go To Definition功能跳到了定义CLSID_WICImagingFactory的地方,然后发现了这个事情。因此我就把代码改成了,如果sdk用的是高级版本,就强制使用1.0的。
VS2012对模板语法的检查更加严格了。以前还可以写typename A<T>::B<T>,现在不行了,得写成typename A<T>::template B<T>。其实后面那个才是标准的,而且VS2010也支持。只是VS2010也允许你省略template。
VS2012对于C++的改进已经跟C#几乎一模一样了,而且VS2012还支持C++的单元测试项目。总的来说,我十分喜欢。
posted @
2012-08-30 05:29 陈梓瀚(vczh) 阅读(10986) |
评论 (25) |
编辑 收藏
为了给自己写的C++ GUI库做宣传,在几个月前我决定要给他做一个网站,经历了这几个月不断的重构,现在网站的架构终于定型了。考虑到在这之前我几乎没有开发网站的经验,所以在这里做点小总结来介绍自己一路走来发生的事情。
一开始为了制作这个网站购买了万网的域名和一个很便宜的一年500块钱的空间。这个空间支持低版本的asp.net,不过当时还没学会这方面的东西。后来我就开始学习HTML和CSS,然后做出了几个页面。不同的浏览器有不同的bug,导致HTML和CSS搞起来相当复杂,特别是div和float的结合,更是焦头烂额。后来索性整个网页都用表格布局。虽然表格布局并不是什么流行的做法,但是鉴于GacUI的网站并没有多么复杂的内容,因此最后这么做,避开了很多跨浏览器的问题。当然以后如果网页内容变得太复杂的话总是要改回div的吧。对于常年写图形和编译器方面的我来说,刚开始深入接触HTM和CSS的时候就对他的杂乱无章所震惊。使用programming language领域的经验和标准来看,HTML、CSS和Javascript作为编程的工具实在是烂到不能再烂了。不过事物的存在总是有其合理性的,纵观这三样东西的发展和历史,我们会发现其过程充满了各种巧合,而且当初这些东西在发明的时候就仅仅是为了解决一些简单的问题。如今流行了,就跟COBOL、Java和C语言一样,全世界的程序员都只能接受其不可忽视的弱点而就这么开发下去了。
在熟悉了简单的HTML和CSS之后,我就开始给GacUI做网站了。这个网站的主要目的就是用来介绍GacUI的特点、展示一些Demo、告诉人们如何下载并使用,最后就是提供一个在线文档。文档就跟MSDN一样,当然搜索功能还不存在。刚开始就遇到了美工问题。技能书都点在了系统软件上,自己的美工自然是连自己都不能满足的。后来找了几个例子,看来看去觉得还是
http://www.codeplex.com最顺眼。但是它的代码复杂到吐血,我便只好依样画葫芦,自己慢慢复刻。最后复刻的结果还是令我比较满意的。在做完了导航条之后,我就发现了一个问题。如果不使用如今的框架来做网页,而采用手写的方法来生成HTML文件的话,势必是无法DIY的。不过那个时候还没有意识到这个问题的严重性,于是就采用了简单粗暴的复制代码的方法来做好那五个页面。这也为我之后转向Windows Azure埋下了伏笔。
之后做Demo的展示页面比较顺利。因为目前展示Demo的方法就是先贴效果图,然后贴C++代码,十分的简单粗暴。于是立刻就遇到了一个新的障碍,要如何写一个类似MSDN的HTML文档。其实这件事情跟做网站本身是没什么关系的,但是自从决定了要提供在线文档之后,这个问题也就无法避免了。C++生成文档地方法之前略有研究,毕竟关于这个功能的一个简单的原型就是我跟几个朋友在大一的时候参加学校的软件竞赛的作品。后来还考虑过诸如Doxygen这样的工具。但是由于Doxygen生成的文档很难调整风格,使其整体融入我的网站的样式里,因此最终就放弃了。然后我便想起了Visual Studio的XML注释功能。在略为研究之后,我便给我的整个GacUI的类库的public class编写了XML注释。当我最终要执行生成文档的这一步的时候,我才发现所有的工具都不支持原生C++程序和XML注释的文档生成功能。不过想来其实也有道理。Visual Studio在编译了XML注释之后提供的一个xml文件只包含符号和注释的对应关系。至于符号究竟是什么内容,则完全没有。因此.net的程序是怎么生成文档的呢?自然是利用反射了。C++的苦逼就在这里啊,除了直接写代码,就没有任何方法。但是写了这么多的XML文档要放弃实在是太可惜了,所以我就不断的找呀找,然后发现Visual Studio在安装的时候提供了一个叫做DIA的库,可以让我阅读pdb文件!
这让我欣喜若狂啊。既然Visual C++的调试器可以通过阅读pdb就得到了一切的信息(看那个完美的调试器界面就知道了!),那我自然也可以从pdb里面找到所有东西的。抱着这个想法,我开始研究原生C++的pdb文件的符号的语义结构,后来就把我的经验写成了这两篇博客:
http://www.cppblog.com/vczh/archive/2012/03/10/167538.html和
http://www.cppblog.com/vczh/archive/2012/03/10/167539.html。虽然PDB并没有包含模板类的直接信息,不过这暂时不成问题,因为GacUI的大部分类也不是模板类。经过了这些研究,我就得到了一个相当于静态反射的功能了。之所以说是静态,是因为我没办法跟.net程序一样通过反射来调用函数。但是这对于生成文档来说已经足够了。后来我做了一件事情,就是写了个程序,读pdb获得所有的符号dump出一份xml,然后再写一个程序把xml里面的符号和Visual Studio产生的那个XML总注释文件的符号联系起来(这个过程有点复杂,因为两边的表示方法不一样……),得到了一份既包含符号的完整内容又包含对应的注释的这么个几十M的XML文件。之后我设计了一个简单的文档格式,写了个程序把那个几十M的XML文件转换成用那个简单的文档格式表达的文档。之所以这样做是考虑到将来说不定除了HTML还要生成其他格式的文档,于是就做了那么个程序可读的中间格式。最后一步当然是读这些文件产生HTML文件了。整个流程如下所示:
PDB(VC++编译器提供) -> Symbols.XML (因为DIA是一个COM组件,所以这一步我用C++写,下面所有的步骤都用C#写。C#用起来还是更容易啊……)
Symbols.XML + Comment.XML(VC++编译器提供) -> FullSymbols.XML
FullSymbols.XML -> *.docitem.txt
*.docitem.txt -> *.html
经过了这些步骤,我就得到了整整一个文件夹的一千五百多个HTML文件了。然后我把这些文件跟我的网站合并在一起上传,就得到了第一个版本的GacUI网站了:
http://www.gaclib.net。当然现在已经看不到第一个版本的网站了。做完这些步骤之后,我就暂停了下来,继续开发GacUI。一边开发一边产生Demo,添加Demo页面,产生新的文档,消灭一些没用的函数的文档。写代码的时候还比较容易,再把代码的更改反映到这个网站的时候,就体现出了手写纯静态网站的弱点:维护起来真TMD麻烦啊!虽然上面的这些步骤已经被我合并到了一个bat文件,每次双击就能自动完成,但是修改Demo页面的时候还是人肉的。这让我十分不爽。
一个偶然的机会,我用了信用卡注册了一个Windows Azure的空间。这个空间其实并不powerful,只给了我相当于六颗CPU的计算能力。不过用来做这个网站已经足够了。想到每次修改网站都要复制HTML代码,修改了结构的话还要动所有的HTML文件,烦了大概一个月之后我就下定决心要把HTML做成动态生成的。在看了Windows Azure的一些介绍之后,我觉得ASP.NET MVC3加上Windows Azure的简单存储功能十分适合用来作这种东西。
刚开始接触MVC3还是让我觉得比较困难,不过最大的困难还是在于理解router的机制那里。GacUI的网站内容简单,所以并不需要ASP.NET的其他高级技术。结果所有的困难都出现在router机制里面。在经过了两天的学习之后,我初步的掌握了它的使用方法。MVC3的router基本上就是一个pattern matching的过程,把你的url映射到一个对controller的调用上面。你不仅可以映射controller的类名和函数名,还可以从url抽取一些参数。在掌握了它的原理之后,操作router的感觉就跟写haskell一样,又直接又清爽。接下来就是razor模板的事情。从programming language的观点上看,razor是一个设计的相当出色地模板语言。第一个特点就是和宿主语言C#融合的十分紧密,第二个特点就是几乎一点语法噪音都没有。用过原始asp和php的人都知道代码里面充满了<%%>是一件十分令人讨厌的事情。每一处代码和HTML的切换都要<%%>,整个文件一眼望去就是一坨屎。razor很好地解决了这个问题。他采用了复杂的判断方法来分辨哪些东西是C#,哪些东西是HTML。小部分C#和HTML的切换至需要一个@符号就搞定了,大部分的切换都是自动的。虽然偶尔razor会有分析错误的情况,但是他仍然提供了@:操作符来让我们workaround这个分析过程。整个模板语言下来毫无语法噪音,写起来十分直接,十分干净。
在网站差不多做完之后,我往生成HTML文档的程序添加了一个功能:生成一个包含HTML文档内容和元数据的XML。然后我写了一个程序把这一大堆XML灌入了Windows Azure的Blob Storage里面。Blob Storage就跟一个硬盘一样,可以用来存放大量的不需要计算(SQL数据库就属于那种需要计算的)数据。然后我给每一个文档页面建立了一个统一的Model,Model里面包含了“读取和分析这些XML文件”的功能,controller则做一个简单的转发,最后在model里面把所有被Model标记出来的需要改写的URL都用@Url.Action来处理。在这个过程中我学到了一个razor的小技巧:虽然不是很安全,但是在razor里面使用MvcHtmlString可以绕过html encoding的功能,把存储在变量里面的HTML代码直接嵌进页面。这么用的时候需要有清醒的意识。
如今GacUI终于有了镜像网站:
http://asia.gaclib.net和
http://us.gaclib.net了。万网的破烂空间自然不可能直接访问Windows Azure Storage了。所以我采用了一个看起来比较傻逼的方法。首先我修改好网站之后,上传到EastAsia和West US两个服务器,然后我写了一个程序再把每一个生成好的HTML页面下载下来。下载的过程其实就和写爬虫差不多,每获得一个新的HTML文件就去分析里面的链接,然后继续下载。写完了之后我发现这个小程序还有了发现死链的功能,直接找出了网站代码的几个bug。下载完之后FTP到万网的空间里面。这个服务器在杭州。因此网站就有三个服务器了。
整个过程零零碎碎耗费了大约半年的时间,都是利用每天下班后的时间完成的。自己又点了技能树的一个新子树,涨了点经验值,觉得这些经验对于某些人来说可能还有参考的价值,于是就写了这篇博客,大家共勉。
posted @
2012-07-09 10:27 陈梓瀚(vczh) 阅读(4676) |
评论 (9) |
编辑 收藏
博客光写GacUI(
www.gaclib.net)的Demo更新也好无聊啊。所以今天先换换口味,胡扯点别的。
一年一度的高中毕业生填简历的日子即将就要到了,又有很多人问计算机专业的事情。其实我从心底里觉得,高考后才来问这个,已经完了一大半了。当然另一小半十分有前途的人可以在大学四年赶上来,不过估计他们还是要读个研究生,才能把自己训练成能用的码农。
编程是一件很难的事情。当然我的意思跟那篇著名的《编程是一件很难的事情》不一样。想把代码写好,本来就是一件非常困难的事情。我大三的时候训练一个大一的老乡,就光是C++,长达四年后她还搞不清楚模板元编程究竟是什么。而且还有C语言学会了转C++会把坏习惯带进来啦,C++的人转做C#之后发现很多C++的好技巧到了C#都只会让程序变得更慢啦,很多写动态语言的人不理解类型的好处还在那里胡扯啦,还有C#和javascript明明放着大好的函数式风格不用,非要把代码写的超长(本来光是这样没什么问题的,只是有某些人不肯学习新知识)。可见,就算把自己训练了好多年,最终进入了工作岗位,想把代码写好,也是一件非常困难的事情。
当然有些人说,如今只有产品做得好才能赚钱,代码写的好有个屁用。这只能是人各有志,有些人就不喜欢钻研代码,这本来也没什么。但是这些人老是跳出来忽悠别人,也只会让编程变得更难。只是幸好,我的单位并不会跟某些单位一样说一些“把代码写得那么好有什么用,搞到我们还非得学东西才能看你的代码,赶紧做点新feature啦”的这种话,我已经觉得很好了。
写得好这个东西还是比较抽象。我认为其中一条就是代码要好维护。我一直以为,只有代码写得好维护,好改,清晰易懂,这样加新的功能才会容易,不出事情,顺利发布软件。后来我发现我错了,腾讯不也是QQ一版一版的发吗,原来加班也是一种方法,啊哈哈哈。如果在一个单位里面,不加班别人就会找你麻烦的话,我相信你也不会花心思把代码写好的,反正都要加班。
不过对于志向就是写代码的那一些人,最好还是不要受到这些外来信息的干扰。最近跟我们组里的一个test manager聊天,他是一个菲律宾人,说是从纸带时代开始就写代码了(不过看起来好年轻……),工作的时候还觉得C语言是一个崭新的语言。后来他跟我说,如果一个人有志向与,代码一条路走到黑,最好就去学习一下怎么当architect。他说道,Architect的知识架构是由各种pattern组成的,然后就说了自己年轻的时候的很多故事来作证这个道理。然后还讲了微软的其中一个创始人到现在还坚持一线写代码的事情,不过没告诉我是谁。
在这之前,刚好MSR的Daan Leijen因为来北京参加programming language相关的conference,就来我们这里参观了一下。后来我看他做过GUI,做过parser combinator,发明实现过语言,就前去搭讪,结果发现他读书的时候的导师竟然是Erik Meijer。按照他的话说,“then we are connected”,如果说成中文,就是有缘分吧。接着就跟他讨论了一些parser combinator和类型系统之类的东西。我说我之前也搞过这些东西,最后还贡献了一部分给公司,换了个组之后还开了讲座什么的。他讲到他读书的时候,也是学校没教自己自学的这些东西,后来周围也没什么人做,但是并没有让他丧失动力。然后就说了一句话让我印象很深刻:“原来你也做这些东西啊,我应该可以看到为什么你要从产品组跳到MSRA来了。”他直到今天,头发都基本上掉光了,还在那里继续研究programming language的东西,还给了我几篇论文。我觉得很好,人就该像他那样。
有些时候,人就得有那个信念,才能把可行但是难度大的东西,也最终搞出来。我自己写了11年的程序,其实并没有接触过十分广泛的东西,因为很多时间都花在重写我的一些idea上面了。譬如说编译器就写了五六个,GUI库就写了八遍,还有些杂七杂八的。不过从这个过程之中,可以明显感觉到自己什么时候比以前更进一步。这种signal有很多,譬如说当你决定要添加一个比较复杂的功能,也可以迅速知道怎么做而不用动到架构啦;譬如说你觉得你的代码越来越顺眼啦;譬如说你因为架构不行决定重写的时候,你发现前一个版本的代码可以捡起来继续用的部分越来越多啦。
写到这里,我想起很多人都问过我,程序要怎么写才能写得好,或者说设计模式要怎么写,之类的问题。如果把学习编程花费的精神代价做标准的话,捷径是没有的。但是如果仅仅把时间作为标准的话,捷径显然是有的。怎样才能加速你学习的过程呢?答案就是,先写再看书。对于像编译原理这种略微高深的知识,总要自己写过几遍,吃了一些苦头,才能知道为什么书里非要把算法那么设计结构那么安排。对于像设计模式这种需要大量经验才可以领悟到的知识,如果你从来没独立写过一个上万行的程序,你觉得你能理解设计模式在讲什么吗?我觉得这种时候能做的也就是背下来,理解什么的都是扯淡。诸如此类,学习程序,如果要加速那个过程,肯定要花大量的时间写代码。当你把项目做得越大、越复杂、算法越扭曲、界面越华丽、尺寸已经大到你觉得不学习新的方法论就肯定会让代码失控的时候,这个时候你来看设计模式的书,保证是每看到一个模式都觉得人家说到你心坎里去了。那你不仅可以迅速理解,而且以后还可以不由自主的想起来使用它。
当然,如果你不是一个喜欢写代码的人,那这个方法肯定没有用,因为中途放弃什么的太多了。这种时候,只能怪你没缘分,设计模式不渡你了。如果你最后撑下来了,虽然你自己觉得你也花费了相当的努力,但是别人反正是看不到你的努力的,就会开始觉得你有捷径了。为什么呢?因为效率高啊,时间花得短啊。
光写代码也是没用的。同人于野一篇讲成年人还能不能进步的博客说得很好,知识分为舒适区,学习区和恐慌区。舒适区的意思就是,你很容易就可以做完。学习区的意思就是,你需要花费大量的智力才可以做完。恐慌区的意思就是,你根本不知道如何下手。当你在为了练习编写大量的代码的时候,你要尽量把题目都安排在学习区这里,这样才能让你进步快的同时,还不会被问题打倒,可以继续积累成就感了。
学生做这个最方便了,工作之后,如果刚好遇上个黑心公司要你天天加班,你反而没时间做学习区的内容了,公司给你的肯定是舒适区的苦力活。
说到这里,如果你还有时间练习的话,千万不要去想:“我每一个程序都要跨平台”,“我只做这个语言”等等。反正将来,语言你都要会,平台的差异你都要知道,为什么要断送自己了解这些东西的机会呢?你真的以为不知道垃圾收集的原理,和一些底层的可以通过C++的练习而得到的的操作,你真的可以在某些关键时刻操纵好C#吗?当然有些人会觉得,我估计一辈子不会遇到这些问题的,所以我还是不管他了。人各有志嘛,C#不渡你,也是你自己的事情。如果你真的可以一辈子都在一个平台上用一种语言做同一种程序做到退休,那真是幸福的生活啊。
胡扯到这里也差不多了,这就是月经贴,时不时,总是要发一下的。
posted @
2012-06-21 09:59 陈梓瀚(vczh) 阅读(10346) |
评论 (16) |
编辑 收藏
摘要: GacUI的ListView支持Windows 7资源管理器的六种View,并且在默认的皮肤下表现的跟资源管理器十分类似。这个Demo也使用了一些Shell API来获得资源管理器使用的文件的图标、文件类型的字符串等等。完整的代码可以在http://www.gaclib.net/Demos/Controls.ListView.ViewSwitching/Dem...
阅读全文
posted @
2012-06-04 09:15 陈梓瀚(vczh) 阅读(7673) |
评论 (8) |
编辑 收藏
GacUI的所有列表控件都支持虚拟模式。虚拟模式是一种不需要为每一个列表项分配内存的一种显示方法。在开始的时候,需要高速列表一共有多少个列表项。之后,列表控件在渲染的时候,会跟数据源要求获取某一个下标所包含的数据,并且在这个数据一直处于屏幕上的时候,只会跟数据源获取一次。完整的代码可以在
http://www.gaclib.net/Demos/Controls.ListBox.VirtualMode/Demo.html看到。先上图:
先看创建界面的代码。一般来说,所有可以随着窗口的变化自动排版的控件组织方法,都是使用一个或多个GuiTableComposition来实现的。
class VirtualModeWindow : public GuiWindow
{
private:
GuiVirtualTextList* listBox;
GuiButton* buttonIncrease;
GuiButton* buttonDecrease;
DataSource* dataSource;
void buttonIncrease_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
dataSource->SetCount(dataSource->Count()+100000);
}
void buttonDecrease_Clicked(GuiGraphicsComposition* sender, GuiEventArgs& arguments)
{
dataSource->SetCount(dataSource->Count()-100000);
}
public:
VirtualModeWindow()
:GuiWindow(GetCurrentTheme()->CreateWindowStyle())
{
this->SetText(L"Controls.ListBox.VirtualMode");
GuiTableComposition* table=new GuiTableComposition;
table->SetRowsAndColumns(3, 2);
table->SetCellPadding(3);
table->SetAlignmentToParent(Margin(0, 0, 0, 0));
table->SetRowOption(0, GuiCellOption::MinSizeOption());
table->SetRowOption(1, GuiCellOption::MinSizeOption());
table->SetRowOption(2, GuiCellOption::PercentageOption(1.0));
table->SetColumnOption(0, GuiCellOption::PercentageOption(1.0));
table->SetColumnOption(1, GuiCellOption::MinSizeOption());
this->GetContainerComposition()->AddChild(table);
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 0, 3, 1);
dataSource=new DataSource;
listBox=new GuiVirtualTextList(GetCurrentTheme()->CreateTextListStyle(), GetCurrentTheme()->CreateTextListItemStyle(), dataSource);
listBox->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
listBox->SetHorizontalAlwaysVisible(false);
cell->AddChild(listBox->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(0, 1, 1, 1);
buttonIncrease=g::NewButton();
buttonIncrease->SetText(L"Increase 100000 Items");
buttonIncrease->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
buttonIncrease->Clicked.AttachMethod(this, &VirtualModeWindow::buttonIncrease_Clicked);
cell->AddChild(buttonIncrease->GetBoundsComposition());
}
{
GuiCellComposition* cell=new GuiCellComposition;
table->AddChild(cell);
cell->SetSite(1, 1, 1, 1);
buttonDecrease=g::NewButton();
buttonDecrease->SetText(L"Decrease 100000 Items");
buttonDecrease->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0));
buttonDecrease->Clicked.AttachMethod(this, &VirtualModeWindow::buttonDecrease_Clicked);
cell->AddChild(buttonDecrease->GetBoundsComposition());
}
// set the preferred minimum client size
this->GetBoundsComposition()->SetPreferredMinSize(Size(480, 480));
// call this to calculate the size immediately if any indirect content in the table changes
// so that the window can calcaulte its correct size before calling the MoveToScreenCenter()
this->ForceCalculateSizeImmediately();
// move to the screen center
this->MoveToScreenCenter();
}
};
GuiVirtualTextList就是只有虚拟模式的GuiTextList。事实上GuiVirtualTextList是GuiTextList的基类,而GuiTextList.GetItems()返回的对象也是一个数据源。因此非虚拟模式其实也是通过虚拟模式来实现的。在数据比较少的时候,非虚拟模式操作起来十分的简单,而在数据比较多的时候,虚拟模式可以带来很好的性能。上面的代码创建了一个DataSource类来做数据源,并且有一个SetCount的函数用来更改列表里面的数量的总量,然后每一个列表项的内容都是Item xxx。这是怎么做到的呢?我们来看数据源的代码:
class DataSource : public list::ItemProviderBase, private list::TextItemStyleProvider::ITextItemView
{
protected:
int count;
public:
DataSource()
:count(100000)
{
}
void SetCount(int newCount)
{
if(0<=newCount)
{
int oldCount=count;
count=newCount;
// this->InvokeOnItemModified(affected-items-start, affected-items-count, new-items-count);
// this function notifies the list control to update it's content and scroll bars
if(oldCount<newCount)
{
// insert
this->InvokeOnItemModified(oldCount, 0, newCount-oldCount);
}
else if(oldCount>newCount)
{
// delete
this->InvokeOnItemModified(newCount, oldCount-newCount, 0);
}
}
}
// GuiListControl::IItemProvider
int Count()
{
return count;
}
IDescriptable* RequestView(const WString& identifier)
{
if(identifier==list::TextItemStyleProvider::ITextItemView::Identifier)
{
return this;
}
else if(identifier==GuiListControl::IItemPrimaryTextView::Identifier)
{
return this;
}
else
{
return 0;
}
}
void ReleaseView(IDescriptable* view)
{
}
// list::TextItemStyleProvider::ITextItemView
WString GetText(int itemIndex)
{
return L"Item "+itow(itemIndex+1);
}
bool GetChecked(int itemIndex)
{
// DataSource don't support check state
return false;
}
void SetCheckedSilently(int itemIndex, bool value)
{
// DataSource don't support check state
}
// GuiListControl::IItemPrimaryTextView
WString GetPrimaryTextViewText(int itemIndex)
{
return GetText(itemIndex+1);
}
bool ContainsPrimaryText(int itemIndex)
{
return true;
}
};
对于GuiVirtualTextList来说,只需要实现vl::presentation::controls::list::TextItemStyleProvider::ITextItemView就可以了。GacUIIncludes.h里面已经有了using namespace vl::presentation::controls,所以在这里只需要从list::开始写。list::TextItemStyleProvider::ITextItemView还要求实现GuiListControl::IItemPrimaryTextView。在目前的GacUI里面,IItemPrimaryTextView是专门为下拉框准备的。因为下拉框允许接受任何一种列表对象当做下拉内容,所以GacUI的列表数据源默认都要求实现IItemPrimaryTextView。
实现数据源的时候,其实并不要求数据源类继承自ITextItemView和IItemPrimaryTextView。因为GacUI都是通过RequestView来获取一个View的接口指针的,代码如上。实现这两个View也很简单,在这里就不赘述了。
GuiTextList就介绍到这里了,接下来的几个Demo都将是关于ListView的。下一个Demo是ListView山寨Windows 7的资源管理器界面,可以在
http://www.gaclib.net/Demos/Controls.ListView.ViewSwitching/Demo.html看到。具体内容将在下一篇博客中阐述。
posted @
2012-05-30 07:19 陈梓瀚(vczh) 阅读(2467) |
评论 (1) |
编辑 收藏
摘要: 趁此机会做个广告,http://www.gaclib.net终于上线啦!
GacUI的列表控件的第二个Demo是关于列表项的多选的。跟Windows一样,我们可以通过鼠标和方向键,配合CTRL和SHIFT选择列表的多个内容。因此这次我实现了一个简单的“名字选择窗口”,就跟QQ邀请好友入群的界面一样...
阅读全文
posted @
2012-05-25 21:54 陈梓瀚(vczh) 阅读(3618) |
评论 (12) |
编辑 收藏