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库,http://sigslot.sourceforge.net/
基于C++ template实现的,功能简单,实现也很简单,只有一个头文件,很符合我的要求,本来就不需要那么复杂的功能。
class RWindow : public RObject
{
sigslot::signal0<> Clicked
}
class MyApp
{
void on_clicked()
{
}
void init()
{
m_win.Clicked.connect(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库比较小,我用的是这个
http://sourceforge.net/projects/mjson/
根据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
paulhodge/EASTL · GitHub
这个STL的实现非常好,解决了代码膨胀问题,编译出来的代码比用VC STL小得多。
RString是我自己写的,但是很多代码是照抄QString的。
但是实现string和数据容器不是GUI库必须做的,只是我个人偏好。
6 一些杂项 utility:
基本算法,MD5 SHA1 ZIP 7Z
网络支持,TCP UDP HTTP,没搞太复杂网络模型就是简单的select,HTTP是封装的WinHTTP。
IO支持,RFile RStream
7. 至于基本控件,早期只提供了RButton RLabel RTextEdit,其他的按需求用到哪个就实现哪个。
好了,差不多了吧,有这些做个安装程序基本算够了。
这个GUI库写大程序还是不行,格局太小,只能做小玩意儿,而且GUI库要有配套的工具链,这个很麻烦工作量又大,所以开发大工程还是推荐用Qt。
这个库早期的基础版本写下来也就两万行左右代码,基本只有自己用,想到如果要给别人用的话还要写文档,脑袋瞬间大了一圈圈。
后来有个同事把Lua集成进去了,做了脚本绑定,支持拿Lua脚本写程序,有点QML的感觉了,不过没有在真正的产品里用到。
— 完 —
本文作者:姚冬
【知乎日报】
你都看到这啦,快来点我嘛 Σ(▼□▼メ)
此问题还有 11 个回答,查看全部。
延伸阅读:
如何用c语言编写2+22+222+……+22222222222?
如何用C语言画一个“心形”?