使用Qt开发桌面程序,经常会有标绘的需求,一般有以下几点:
- 新建:圆、矩形、椭圆、文字标注,插入图像等;
- 编辑:指对已标绘内容的属性编辑修改功能;
- 删除:指对已标绘内容的删除功能;
- 浏览:指提供对已标绘内容的平移、放大缩小等浏览操作。
包含标绘功能的最典型的应用是地图标绘系统。一个简易的地图标绘系统demo如下图所示:
Qt中,标绘功能有不同的实现方式,通常有以下几种。
基于QPainter的面向过程的实现方式
此种方式下,实现一个标绘系统,通常需要创建一个QWidget子类,在子类中实现鼠标、键盘等事件响应以及界面刷新显示。
具体的做法为:
- 新建QWidget子类
- 根据需求,选择重载鼠标移动mouseMoveEvent、鼠标点击mousePressEvent、鼠标释放mouseReleaseEvent等鼠标事件响应函数,选择重载按键按下keyPressEvent、按键释放keyReleaseEvent等按键事件响应函数。
- 重载paintEvent界面刷新响应函数,根据输入实时绘制图元,刷新界面,响应用户的操作,实现流畅的交互过程。
此种实现方式最大的优点是简洁,仅仅新建一个类以及重载几个必要的函数即可实现简单的标绘功能。但是它的缺点也是显而易见的,主要有以下几点:
- 维护问题。当标绘元素种类增多时,所有的标绘元素的实现代码都糅合在一个QWidget子类中,这会导致此类逐渐难以维护。如果代码结构不清晰,容易出现bug。
- 功能问题。一些高级功能特性,例如坐标计算/转换、仿射变换、层叠特性、裁剪优化、图元命中/索引算法,需要自己来实现,实现难度大,工作量也很大。
- 性能问题。如果对性能要求较高,就需要做大量优化,自己实现难度大。
所以基于QPainter的面向过程的实现方式比较适用于构建小规模、功能简易的标绘系统。
为了克服以上缺点,可以尝试采用基于QGraphicsView的面向对象的实现方式。
基于QGraphicsView的面向对象的实现方式
基于QGraphicsView的面向对象的实现方式,是指使用Qt库内置的QGraphicsView模块构建标绘系统的方法。文章开始部分介绍的地图标绘系统demo就是基于QGraphicsView实现的。
QGraphicsView库对标绘业务进行了建模,抽象出了场景画布及图元类,并提供了常用的高级特性。采用面向对象的方式,可以将不同部分的业务代码分散到不同的类中,并通过对象间通信完成协作,从而构建出结构清晰,易于维护的系统。
基于QGraphicsView,使用者可以把更多的精力放在业务实现上。在构建比较复杂的标绘系统时,使用QGraphicsView是首选方案。
QGraphicsView具体在性能、功能方面的特点,在官方文档中有详细描述。除文档外,官方还提供了“40000 Chips”、“Diagram Scene Example”等完整可用的demo供学习。
QGraphicsView相对于QPainter的区别在于,它需要一定的学习成本,对面向对象的编码能力有一定的要求。
结语
通过系统地分析,我们研究了两种标绘的实现方案。不同实现方案适用于不同的使用场景,实际使用时根据需求权衡方案即可。
本文原创首发于微信公众号“Qt未来工程师”。