您的位置:首页 > 编程语言 > C语言/C++

如何用 C++ 从零编写 GUI?

2016-11-01 09:33 239 查看
作者:姚冬

链接:https://www.zhihu.com/question/24462113/answer/27888139

来源:知乎

著作权归作者所有,转载请联系作者获得授权。

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的感觉了,不过没有在真正的产品里用到。
https://www.zhihu.com/question/24462113
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: