如何⽤C++从零编写GUI?
,中⽼年程序员
、、
GUI库可⼤可⼩,⼤可以是Qt WPF这种数以百万⾏计的代码,⼩的可以是WTL这种只有⼏个头⽂件。
对⼀般⼈来说,不要奢望能做出⼤GUI库,写⼀个⼩⼀点的,满⾜⾃⼰的需求,针对某类应⽤就好了。
我曾经遇到⼀个需求,需要⼀个⼩型的GUI库来写个安装程序。
安装程序是⽐较特别的,对于互联⽹下载安装的软件,要满⾜以下要求:
1. 不能带DLL,必须是静态链接,对系统的依赖越⼩约好。
2. 可执⾏代码必须⾜够⼩,⼀般来说要500KB左右最好。
离别不舍的句子
3. 有⽐较好看的图形效果,⽐如安装过程的过场动画,窗⼝要有个半透明阴影光圈什么的。
⽤Qt写显然不合适,虽然我在知乎多次说过Qt库其实不⼤,但是对于写安装程序还是真的有点⼤了,Qt
的静态链接出的Exe有2MB左右。 ⽤MFC也不合适,MFC静态链接出来有400KB左右,算上安装程序⾃⾝的代码和资源肯定突破500KB了。
⽤VC++ 6.0的MFC去写,可以⼩很多,但是⽤这种古董不符合我的品味。
⽤WTL写,这个肯定很⼩,只创建⼀个窗⼝的程序静态链接只有50KB左右,但是什么功能都没有啊,只能创建使⽤基础的标准悾件,做个透明窗⼝都要⾃⼰再⽤其他API实现。
骨干申请书还是⾃⼰写⼀个吧,⼩⼀点实⽤⼀点,就⽤来做安装程序好了,不追求有多么⾼⼤上的能⼒。
跨平台就不追求了,只解决Windows问题就好了。
实现GUI库,有⼏个基本的⼦系统:
1. 窗⼝管理系统,这个代码就是封装Win API,但是这个⼯作很⽆聊,⼜很⿇烦,我索性⽤WTL实现了,把⾃⼰的窗⼝类去聚合WTL的CWindow,拿WTL做后端帮我创建管理窗⼝,对外是看不到WTL的,我没有⽤派⽣是因为不想让WTL污染我的接⼝设计。
我⽤私有类的⽅法,把WTL封装起来了,外⾯看不到
//伪代码
class RWindow : public RObject
某天某时
{
private:
RWindowPrivate *d;
}
class RWindowPrivate
{
public:
CWindow m_wnd;
谈开头的成语}
2. 事件系统,WTL的消息映射宏太丑了,我喜欢Qt的signal/slot,但是实现⼀个Qt那样的signal/slot可
不容易,相当于发明⼀种C++扩展语法,还要⾃⼰实现⼀个MOC这种编译预处理器,⼯作量太⼤了。⽤Boost::signal 也太笨重了,boost会引⼊⼀个很⼤的依赖库,我还希望这个GUI库可以⽤默认的VC++就能编译呢,不想依赖太多其他库,⽽且boost的function会带来编译困难。网络品牌
我选择了⽤⼀个轻量级的sigslot库,
基于C++ template实现的,功能简单,实现也很简单,只有⼀个头⽂件,很符合我的要求,本来就不需要那么复杂的功能。
class RWindow : public RObject
{
sigslot::signal0<> Clicked
}
class MyApp
{
void on_clicked()
{
}
void init()
{
m_t(this, &MyApp::on_clicked);
}
RWindow m_win;
}
3. 图形系统
既然是GUI库,总不能还⽤GDI函数往hDC上绘制吧,好⽍要弄个FrameBuffer,⽀持RGBA,渲染好
了可以通过UpdateLayeredWindow 更新到窗⼝上,以实现半透明异形窗⼝图形效果,⽐如实现个阴影边缘什么的。
⾃⼰写⼀个还是很⿇烦的,光基本的点线圆绘制,基本的Alpha混合就要写上万⾏,更别提⽂字输出了。⽤第三⽅库的话,2D图形库就没有⼩的,光图形库就突破500KB的限制了,⽤Direct2D是不是有点⼩题⼤做了?还是⽤GDI+吧,虽然这个函数库不太受待见,但好⽍是标准库,所有Windows都内置,⽽且我要求的基本图形功能都是有的。
⾃⼰写个 RPainter 包装GDI+的函数,顺便把 PNG JPG的编解码也解决了。
4. 布局系统
GUI库总不能让⽤户⾃⼰⼀个⼀个创建控件然后⽤绝对坐标摆放吧。基本的UI描述⽂件,Layout⽀持还是要有的。
但是我没有⽤XML,⽽是⽤了JSON,这两种格式描述能⼒是差不多了,仅是我个⼈偏好JSON,另外JSON库⽐较⼩,我⽤的是这个
根据JSON的描述来构建窗⼝控件的对象树。
我没有去实现复杂的布局,只实现了Anchor Layout,基本可以保证够⽤了。
给每个控件设定好object name,在C++⾥提供
template<typename T>
炖鸭子T *findObject(const RString &name)
搞⾃动绑定可不容易,开发者⾃⼰⼿⼯绑定吧,好在⼩程序控件也不多。
5. 基本数据类型和容器类型
⾝为⼀个Qt粉当然要⾃⼰实现⼀套string类和泛型容器,向Qt致敬啦。
我没觉得⾃⼰有能⼒重写⼀遍STL,就是⽤系统的STL做后端,聚合STL的类,实现COW(copy-on-write),实现统⼀内存池。后来我把vc++的STL换成了 eastl
这个STL的实现⾮常好,解决了代码膨胀问题,编译出来的代码⽐⽤VC STL⼩得多。
RString是我⾃⼰写的,但是很多代码是照抄QString的。
但是实现string和数据容器不是GUI库必须做的,只是我个⼈偏好。
6 ⼀些杂项 utility:
基本算法,MD5 SHA1 ZIP 7Z
⽹络⽀持,TCP UDP HTTP,没搞太复杂⽹络模型就是简单的lect,HTTP是封装的WinHTTP。
IO⽀持,RFile RStream
7. ⾄于基本控件,早期只提供了RButton RLabel RTextEdit,其他的按需求⽤到哪个就实现哪个。
好了,差不多了吧,有这些做个安装程序基本算够了。
这个GUI库写⼤程序还是不⾏,格局太⼩,只能做⼩玩意⼉,⽽且GUI库要有配套的⼯具链,这个很⿇烦⼯作量⼜⼤,所以开发⼤⼯程还是
推荐⽤Qt。
这个库早期的基础版本写下来也就两万⾏左右代码,基本只有⾃⼰⽤,想到如果要给别⼈⽤的话还要写⽂档,脑袋瞬间⼤了⼀圈圈。
后来有个同事把Lua集成进去了,做了脚本绑定,⽀持拿Lua脚本写程序,有点QML的感觉了,不过没有在真正的产品⾥⽤到。
,
数学爱好者,喜欢平⾯设计、户外运动
、、
伪专业⼈⼠过来怒答⼀记。由于是想到哪写到哪,所以可能写的有点乱。
⼤概2009年的时候,我就沉醉在编写⼀款能跨时代的GUI上,从哪时候开始,我开始⼤量研究国内外所有开源⾮开源的GUI。
最开始的时候,每个写GUI的⼈都会经历那么⼏个阶段(windows下),
使⽤MFC,windows控件 ->发现windows控件不⾜,开始改装->发现改的多了,重复造轮⼦,开始思考从头完全⾃⼰实现控件->GUI的雏形->总结各种gui,写出⾃⼰的DirectUI->发现光是DirectUI还不够满⾜⽇益增长的界⾯需求,开始思考更合理的设计理念
所以你会发现,要从头实现⼀款⾃⼰的界⾯库,你需要了解界⾯库是如何运作的,这就牵扯到消息机制、绘图机制,这算界⾯库的最底层。在这层其实就有⼤量细节。⽐如在windows上,在⽤GDI的过程中,会发现GDI有各种各样极其不爽的地⽅,不⽀持alpha通道、抗锯齿效果不好、⽮量功能太弱、图像处理功能太弱等等。所以在这⼀步,你需要⼀个好⽤的渲染框架。个⼈⾮常推荐skia,效果强⼤,速度极快。 有了渲染层,你要设计⼀套消息循环机制,你需要设计你的各个控件是如何接受消息,
并做出反应。然后之上到了控件层,你需要设计你的控件体系。⽐如你要怎么分类你的控件,各种控件之间怎么协作。控件层之上,你⼜需要布局,就是说你需要有⼀种⽅式,让你的控件⽅便的放置在应该在的位置。
写完了这些,你会需要⼀个更⽅便的配置系统。这时候你会想到xml。这种结构化的语⾔对你来说很适合描述控件的各种信息。于是你⼜加⼊xml配置控件。
等等等等,写完这些,你基本上已经算半个界⾯专家了。但其实这⼀阶段,才是真正界⾯⾼⼿和界⾯熟⼿的分界点。到这⼀阶段,如果你对界⾯有更⾼的要求,你会发现⽤xml配置那些写好的控件,到底还是太⿇烦。产品会给你提⽆数乱七⼋糟的需求,这种需求不是⼀个通⽤控件可以搞定的。这时候,你会有所思,想怎么才能更加⽅便的开发。当年,在这个阶段,我苦思了很久,最后直到在公司的某个公共⽬录,发现⼀份对迅雷界⾯库的分析,我才恍然⼤悟。原来你少了⼀种打破控件系统的勇⽓和创意。这个时候,你会发现,市⾯上许许多多的js 库,其实就代表未来界⾯库的发展⽅向:不再使⽤传统的控件体系,⽽是⽤⽐控件更⼩粒度的原⼦控件---就⽐如迅雷界⾯库⾥的各种原⼦控件,⼜如js库⾥所使⽤的那些html元素。你会发现,只使⽤基本的⼏个元素,然后强化他们的拼装⽅式,能更⽅便快捷的实现产品提出的那些奇怪需求。
有了原⼦控件,你拼装出异性控件更加⽅便,你要实现控件各种动画也更加简洁。当你还是觉得有点
不⾜,你会发现C++写这种异步逻辑的⼯作,太tm⿇烦了。你会发现写⽹页的那群⼈永远写出炫酷界⾯的时间⽐你短。这时候你会再次醒悟,⽤脚本来实现这⼀切,⽤脚本去黏合这些逻辑。你会发现,⽤脚本去写动画,写消息响应,由于脚本天⽣⾃带闭包,写这种异步逻辑简直爽到爆…………
写完这些,我的库基本也被公司所⽤。⼩⼩得瑟⼀下,现在毒霸的加速球悬浮窗就是⽤这套逻辑写的,使⽤的库叫kdgui :-)
未完待续,有时间还想讲下kdgui的设计思路,当时可是把10多m的webkit,给完全解剖了,变成2m的kdgui:-)
,就是⼀死写界⾯的
、、
其实很少逛知乎,今天被同事在QQ上邀请,作为⼀个⼯作⼗年的⼀个死写界⾯的程序员,我想我应该还是有些可以分享的。当然,有很多观点或许和我经历的时代有关,现在的程序员不见得百分之百适合,姑且当作抛砖引⽟吧。
从0开始学GUI编程,但是不⽤库,这件事本⾝是个伪命题。GUI编程是个复杂的知识体系,并不像⼤多数⼈想像中那么简单。所以如果题主真的想对Win32下(我猜的)的GUI编程有⽐较深刻的理解,
建议还是从使⽤库开始。
先简单的说下,⼀个能⽤的界⾯程序员需要储备哪些知识:
1.各种系统内置控件的使⽤⽅法及特性
2.界⾯相关的消息机制。⽐如Windows下的消息循环,iOS中的RunLoop机制等
3.消息及事件派发机制
4.简单的绘图⽅法
优秀的界⾯程序员需要具备的进阶技能包括但不限于:
1.熟悉进程、线程调度机制,各种内核对象的应⽤
2.基本的图形学和图像处理技能,位图基础
3.图像编辑⼯具的使⽤,熟练使⽤PhotoShop、AI、mspaint等⾄少⼀种
4.有过多个平台的界⾯开发经验
回到题⽬,从0开始,当然先要成为能⽤的界⾯程序员。熟悉系统内置控件的最佳⽅式莫过于使⽤各种成熟framework来实现需求。⾄于是MFC还是WTL还是别的什么,倒不是什么⼤不了的事。
还是简单说下MFC和WTL(ATL)的区别。以前经常会看到有⼈说MFC如何如何渣,如何如何误导观众,其实这也是⼀个误解。
MFC
优点:易上⼿,对于界⾯订制不⾼的需求更容易做到快速实现
苹果锁屏密码怎么设置缺点:不够灵活,效率略低,运⾏库体积较⼤且版本太多
关于廉洁的诗歌
WTL(ATL)
优点:灵活,代码执⾏效率⾼(其实就是Win32 API的简单封装,当然快),运⾏库⼩,⽼版本甚⾄有个minicrt版本,⼀个简单的helloworld只有80k且不依赖任何运⾏库
缺点:封装的不够完全,很多功能需要⾃⼰实现;细节暴露太多,控制能⼒较差的程序员很容易把代码写的乱七⼋糟;WTL的⽀持者⼤多对范型这种东西有着阶段性盲⽬崇拜(顺便怀念⼀下当年那青葱的⾃⼰_(:зゝ∠)_)
OK,我的意图已经很明显了,如果完全没有基础,先看看MFC吧。如果能够做到熟练运⽤(如果悟性好,这个时间段可以⾮常⾮常短,所以不要着急),并且开始接触Win32原⽣API,再开始试着⽤下WTL,你顿时会发现世界美妙多了。
当你已经可以熟练使⽤框架进⾏界⾯开发之后,就可以开始探讨⼀些原理性质的东西了,这个时候可以试着读⼀下界⾯框架的源代码,然后你就能了解为什么同样是API封装,不同的框架使⽤起来会有那么多的区别。
举个例⼦,⽐如消息机制,以上两个框架的消息循环封装都写的⾮常漂亮,MFC的消息注册机制、WTL的thunk机制,都是⾮常值得⼀读的。
如果你已经⾛到这⼀步,恭喜你,你已经是个不错的界⾯程序员了,如果这个时候你仍然觉得⾃⼰⾮常喜欢写界⾯,你就有机会成为⼀个很优秀的界⾯程序员。那么,接下来会发⽣些什么呢?
1.你会嫌弃各种系统控件,什么都想⾃⼰画。你⼿上会有⼀套常⽤的⾃绘控件代码
2.你会开始崇拜DirectUI,对spy++抓不到的窗⼝怀有深深的敬意
3.你开始尝试⾃⼰写⼀个简单的界⾯库
4.你会后悔⾼中没学好解析⼏何,⼤学没学好线性代数
5.觉得写界⾯的程序员好不受重视啊,要不要也去做⼏个⽜逼的功能?
于是那年我辞职出去创业了,为了公司的产品,我花了⼀个多星期写了bkwin的原型,基于WTL实现了⼀套基本的DirectUI体系,⽤XML 描述界⾯布局,并实现了⼀个RelativeLayout和⼀个FrameLayout(以⾄于⼀年后当我看到AndroidSDK⾥描述界⾯的⽅法时,各种即视感,然后感慨⾃⼰道⾏尚浅)。后来⼜断断续续的增加了⼀些复杂控件实现,以及缓存优化、换肤⽀持等等。现在这个库的早期版本已经被公司开源,并且应⽤在有上亿⽤户量的产品中。(好久没得瑟了,⼀下没忍住 ´▽`)
越往后写,越发现⾃⼰知识的匮乏。当GDI+解码PNG的效率成为瓶颈的时候,我开始学习⾃⼰处理DIB;当C++代码处理DIB的效率已经不能再提升的时候,我开始看SSE的流⽔线指令...当我觉得我已经实在改不动的时候,有幸读到了WebKit的代码,被WebCore彻底击溃;然后我转⾏做了iOS程序员,于是三观⼜被毁了...
话题扯的有点远,其实我的根本意思还是想说,学⽆⽌境,既然打算从0开始,就认认真真打好基础,每向前多⾛⼀步,都要夯实,尝试去了解⼀些原理性的东西,不能浅尝辄⽌。 不求甚解是成为⼀个优秀程序员的⼤忌讳。
最后推荐两本书:《Windows核⼼编程》、《Windows图形编程》,其中《Windows图形编程》早已绝版,不过⽹上可以买到影印版的,⾮常值得细读。希望题主以后有机会成为⼀个⽜逼的界⾯程序员。