Qt 与 MVC

2018-11-14

   MVC 软件架构模式出现的很早,80年代随着可视化操作系统的兴起就开始发展了。“MVC模式最早由Trygve Reenskaug在1978年提出[1],是施乐帕罗奥多研究中心(Xerox PARC)在20世纪80年代为程序语言Smalltalk发明的一种软件架构。”在那个8086的年代直接从语言层面为UI显示考虑,我真的不得不佩服项目负责人开拓创新的精神。但此文并不讨论那些宏观的问题,也不像谈MVP、MVVM等架构,只是想从Qt如何实现MVC来讨论一下UI编程的问题。我不怎么喜欢UI编程,从学校时代就是如此。很简单,因为做这事儿太麻烦了,而且吃力不讨好,做的再漂亮、精细、易用,又有多大的好处呢?能用不就行了。CAD软件涉及到二维、三维操作,其架构的复杂度,是WEB软件架构所不能比的,甚至游戏架构复杂度都比不上它。最核心的功能在于显示、交互、算法的结合。
  Qt 对于MVC架构模式做出了些改动,称之为 Model/View 编程。弱化了Controller 概念,把Controller和View 放在一起了。同时,还新增了一个概念:delegate。Models, views, 和 delegates之间的通信,依靠增强的C++ 语言(Signal/ slot 机制,moc编译器)实现的。Qt提供了这三类物体的 abstract class,我们extend 这些class 就能实现常规UI的功能。90年代提出的”观察者模式“对于 MVC架构的实现提供了标准描述。而Qt的元编译器则从语言层面实现了观察者模式。

QStandardItemModel[link]
  一个UI控件对应的数据的抽象模型。这是Model/View中的Model。
  它的职责就是给UI控件提供显示相关的数据。但是,对于显示数据的更改,如何通知View对象呢。在Web开发中,View一般是主动刷新,数据修改并不会主动通知View。但是,对于Native UI程序,我们一般希望任何数据的修改就直接反映到View上面。Qt对于Model以提供了很多signal/slot,对数据改动感兴趣的View对象,都可以订阅该消息。而Qt提供的moc增强了C++语言,解耦了View对象与Model对象,从而导致Controller对象存在性降低了很多。

QAbstractItemView[link]
  这个基础class类型提供了显示相关的功能抽象(View),如样式、显示字体、事件处理。一般来说,消息绝大多数情况下是由View发送到Model层,而且,我们需要输入消息做各种处理,才能发送给Model。故,UI编程大部分工作量便集中于处理从View从发出的消息,少数情况处理从Model层发出的消息。View class一般提供了signal/slot,我们可以使用它们在View 子类中订阅这些消息,这便是Controller的实现。如果消息处理非常复杂,那我们可能不让View 子类承担controller职责,而是使用专门的Controller。

QStandardItem[link]
   Items usually contain text, icons, or checkboxes.
   You can store application-specific data in an item by calling setData(). 
   这是文档上的关键阐述。

 qt_model_view_class.png (544×464)

  上图便是Qt Model/View 的UML 类图。各种复杂的显示控件及效果都是靠上面的基础架构实现的。使用Qt进行UI编程,相较于其他UI lib(包括其他语言的)还是比较简单的。我认为这不得不归功于Qt对于C++语言的增强。
  对于交互的流程,一个很关键的问题是,如何获知一个View 对象关联的Model对象,如何获知一个Model对象被哪些View对象使用。QAbstractItemView 提供了indexAt(QPoint) rootIndex() ,currentIndex() ,就能获取到Model。QAbstractItemModel 、QStandardItem 则共同构成Model。由于我们不能让model 依赖于View,否则便形成循环了。QStandardItemModel是QAbstractItemModel 子类,可以递归构造数据。QAbstractItemModel提供了数据改动的signal,View可以选择订阅这些signal。但是,一般的需求也就如此了。

  交互中有一项是很特殊的小功能:拖拽(Drag & Drop)。需要和操作系统本身通信。拖拽本身是由 OS提供的,Qt只是对不同的OS做了一层封装。在Linux上更复杂。因为Linux系统本身UI的实现机制更复杂一些,是server/client模式。而client有很多种实现。如果自己封装多平台实现,估计累的半死。所以,Qt直接把drag/drop作为QWidget基础的接口功能,大大方便了用户。

Qt UI对象的内存控制策略 
  对于业务使用的对象,我们都会非常注意其内存。但是,如何处理Qt 的UI 对象内存呢?当我们new 出来一个 Button,把它放到一个widget上,删除widget 会导致button 被删除吗?会的。我们只需要把每个 widget 设置好parent,形成一个良好的树状结构。当某个widget 被删除时,会级联的删除整个 tree。所以,自己写的控件类型,一定要在析构函数中处理好内存释放。 否则,会造成内存泄漏的问题。反而,如果使用Qt 控件,大概率是不会出现内存泄漏的问题的。

  说起MVC设计模式,我们基本上是没有什么机会完全参与其设计的。就如使用Qt或者MFC编程过程中,我们顶多只能控制一些Model、Controller端。如果有机会,可以自行实现一种UI lib。估计能够对于MVC的理解大有脾益。在CAD软件中,基本上会使用OpenGL 渲染出二维 窗口层。需要支持常见的Widget/Window/Button/List等控件,还需要使用freetype支持文字显示,还要解决性能问题。如果有对此感兴趣的同学,可以尝试实现下。

  1. http://doc.qt.io/qt-5/model-view-programming.html 官方第一手资料
  2. https://en.wikipedia.org/wiki/Model–view–controller
  3. https://www.codeguru.com/cpp/cpp/implementing-an-mvc-model-with-the-qt-c-framework.html
  4. http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html
  5. https://juejin.im/post/5b3a3a44f265da630e27a7e6
如果有任何意见,欢迎留言讨论。


[ 主页 ]
COMMENTS
POST A COMMENT

(optional)



(optional)