2017年12月13日 星期三

Qt--一个MDI图形应用框架

Qt:一个MDI图形应用框架

原创 2012年07月16日 09:55:46
(本文接上篇“概述")
(Qt Library目前的版本为4.8,以下本文的程序将以此版本为主。)
三、Gt的GUI
Qt的优势在于GUI(本人的理解,至少以前如此,现在则未必),所以了解Qt,也是从图形界面的UI开始。
Qt有几百个类,与图形有关的估计有一半以上。本文主要介绍基于window的图形编程接口,另外稍微介绍一下OpenGL,可以为程序添加三维显示。
1.QWidget
这个是所有window的基类,类似于MFC中的CWnd,提供了许多基类方法。在做GUI时,应仔细阅读其说明文档。
2.滚动条
Qt窗口的滚动条是两个"控件“,需要手工添加(当然,Qt中也有一些自带滚动条的窗体类),不如Windows窗口中,滚动条是窗口的一个属性,凭设置就可以添加滚动条,但Windows中的滚动条是”假“的,需要手工编程控制其移动,而Qt在这点上要简单得多。
3.父子窗口
Qt中一个独特的现象是创建一个Widget时,可以指定一个父Widget,当父Widget销毁时,父Widget会遍历其子Widget,自动销毁子Widget,这样我们就不需要delete子窗口了。这是Qt内存管理的一个方法,对窗口来讲是合适的,由于我们不知道窗口何时销毁,所以必须将非窗口的东西(与窗口的生命期不同)拿出来,放到诸如document中存储,我们手工管理其生存周期。
4. 四种图形界面绘制方式
在Qt窗体下,可以有四种方式构成我们的图形界面,两种是基于文本(块)的格式化布局方式:通常的Window控件;QML控件,两种基于图形的任意布局方式:一种是无对象的,自己绘制;另一种是继承于GraphicsItem类的,一个图形是一个对象。以下分别介绍一下。
基于window控件的格式化GUI:这是最通常的一种UI方式,目前我们大量的对话窗口和Web页就是基于这种方式,这种方式是一种文本方式显示和录入方式。在Qt下,也提供了若干显示控件,可以满足基本的用户文本交换功能,当然,与Windows下的大量丰富的控件相比,Qt的控件就太”寒酸“了。
基于QML控件的格式化GUI:Qt中有专门显示html控件的窗口,但不知道为什么自己提出了一个叫QML(Declarative markup language)的东西,该技术被称为Qt Quick。从字面来看,该技术是提供一种快速创建UI的方法,但既然html已经广为应用,为什么还另起炉灶?该技术基本上与flash、Silverlight等属于同类东西。QML中的可视化控件也并不特别丰富,现在,QML可能与Silverlight一样,受到来自HTML5的强大威胁。
自由绘制GUI:类似于Windows中的图形绘制,在窗口刷新时,需要自己重绘;作为图形GUI,这种方式是最原始的,当然也是最”轻量级“的。为了满足跨平台的要求,所有绘制的代码只能在窗口的paintEvent中完成(类似于windows窗口的onpaint),在其他方法中,设置需要重新绘制的窗口的位置、大小,则Qt会将若干重绘请求,合并执行,图形就更新了,这与Windows不同,在windows中,例如在onmousemove事件中绘制图形,依然有效,由此想到,是否Qt的绘制效率要低于Windows,因为每次刷新时,Qt实际上是要绘制整个画面的(在后台进行),裁剪出需要更新的部分,更新到前台(类似于双缓冲),而Windows的重绘命令可以由用户按需要发出(系统也可以发出,如其他窗口遮挡住本窗口再移开时)。当然,Qt的这种处理方式具有普遍性,以普遍性(跨平台)来牺牲效率,是值得的。
基于图形元素(继承于GraphicsItem)的GUI:与自由绘制相比,该方式是”重量级“的。该方式实际上是对点、线、圆等的封装,使其具有更强的操作性,例如,支持局部坐标及其变换、无需用户重绘、覆盖、碰撞检测、成组等许多图形操作实用的功能。如果我们的应用需要将图形当做元素来处理,而这些处理又是通常的操作,并不复杂,那么采用这种方式最好。其实,将图形元素化,并不困难,凡是具有计算机图形学背景的技术人员,都可以编写出来。
四、一个MDI框架
大多数的复杂应用,以MDI最为常见,本文就介绍一个基于MDI的图形应用框架。
由Wizard生成的程序框架包括如下文件:
main.cpp:程序的入口,这个基本不用修改。如果需要支持中文,可在此选择设置字符集。
mainwindow.cpp:MDI的主窗口,我们最顶层的设计(如菜单),和跨文档的操作,都需在这里完成。
mainwindow.ui:界面文件,这里我们需要加一个mdiarea,用来容纳各子文档窗口。其他诸如status bar,menu bar,tool bar等已经添加了。在界面设计时,可以添加Action,slot,signal,其实他们也可以手工在h,cpp文件中添加,这个依赖于个人的习惯。
对于Action,它的trigger方法没有参数,当菜单项较多时,需要为每个菜单项写一个slot函数,实在麻烦,这点上不如Windows的菜单,可以“映射”到一个处理函数,根据传入的参数判断哪个菜单项被按下。
我们建文件时,应为每种View建一个View文件,如果几个View有共同的数据,则这几个View可以共享一种Document。本文将建4种View,共享1种document。整个程序的大致类结构如下图所示。图中,有4个View(包含的View不算),它们都拥有一个CDoc和MainWindowBase的引用。当View中内容发生更新时,如需要,则通知CDoc和主框架窗口,随着View的变化做相应调整。如果MainWindow类与各View类处于不同的文件,采用双向关联时,会发生循环引用,所以在MainWindow上加一个基类来解决这个问题。
1. 自由绘制图形View
无疑,这个View的代码最多,因为需要自己创建诸如Line,Rectangle等图形元素,这些图形元素的显示变换也需要自己处理。CElementView继承于QWidget,为给它加上滚动条,这个类又包含在一个基类为QScrollArea的继承类中,由该类处理滚动条。本文设计中,这个View最复杂,它实际上包含了3个子View,如下图所示。
Qt中分割条也是一个窗体,比较复杂地分割窗体的的示例代码如下。(上图的效果)
  1. m_pvsplitter= new QSplitter(Qt::Vertical,this);  
  2.  m_pvsplitter->resize(400,400);  
  3.   
  4. m_phsplitter= new QSplitter(Qt::Horizontal,m_pvsplitter);  
  5.   
  6. m_ptreeview = new CTreeView(m_phsplitter,m_pMainWin, m_pdoc);  
  7. m_ptreeview->resize(100,300);  
  8. m_pelementview = new CElementView(0,m_pMainWin, m_pdoc);  
  9.   
  10. ScrollWindow *elementwin = new ScrollWindow(m_phsplitter,m_pelementview, 300,300);  
  11.   
  12.  m_phsplitter->addWidget(m_ptreeview);  
  13.  m_phsplitter->addWidget(elementwin);  
  14.  m_phsplitter->setStretchFactor(1,1);  
  15.  m_phsplitter->resize(400,300);  
  16.   
  17.  m_pinfoview = new CInfoView(m_pvsplitter,m_pMainWin, m_pdoc);  
  18.  m_pinfoview->resize(400,100);  
  19.   
  20. m_pvsplitter->addWidget(m_phsplitter);  
  21. m_pvsplitter->addWidget(m_pinfoview);  
  22. m_pvsplitter->setStretchFactor(0,1);  
关于图形元素的编写,难度倒不大,就是麻烦,这里不再讨论。
2. 图形元素View
与第一View相比,采用Qt内置的图形元素,代码量会少不少,图形库大部分现成,所有图形元素都继承于QGraphicsItem,目前支持的标准图形元素有(摘自Qt文档):
QGraphicsEllipseItem  provides an ellipse item 
QGraphicsLineItem       provides a line item 
QGraphicsPathItem       provides an arbitrary path item 
QGraphicsPixmapItem provides a pixmap item 
QGraphicsPolygonItem provides a polygon item 
QGraphicsRectItem       provides a rectangular item 
QGraphicsSimpleTextItem provides a simple text label item 
QGraphicsTextItem      provides an advanced text browser item 
可能用到的还有QGraphicsPixmapItem、QGraphicsPathItem、QGraphicsItemAnimation和QGraphicsItemGroup,提供图片、绘制路径、动画和成组功能。通常,Qt提供的方法足够了,但要实现“精确”控制,仍需要自己实现。例如,Qt中线的“拾取”比较费劲,因为它要求鼠标点必须严格在线上,而合理杂做法是鼠标点距离线的小于,例如2个像素即可,这时,需要覆盖QGraphicsLineItem的contains方法。代码如下。
  1. typedef struct {float x,y; } f2Point;  
  2. typedef struct { float A,B,C; } fEqLine;   //Ax+By+C=0  
  3.     
  4. bool MatchSegment(const f2Point &mp,const f2Point &one, const f2Point &two)       //匹配线段  
  5. {  
  6.   fEqLine temp;  
  7.   double dd;  
  8.   
  9.   temp.A=one.y-two.y;  
  10.   temp.B=two.x-one.x;  
  11.   temp.C=-(one.x*lin.A+one.y*lin.B);  
  12.   
  13.   dd=fabs(temp.A*mp.x+temp.B*mp.y+(double)temp.C)/sqrt((double)temp.A*temp.A+temp.B*temp.B);  
  14.   if(dd<MatchDiff_Win)  
  15.   {  
  16.      if((mp.x-one.x)*(mp.x-two.x)+(mp.y-one.y)*(mp.y-two.y)<0) return true;  
  17.   }  
  18.   return false;  
  19. }  
  20.   
  21.   
  22. bool  contains ( const QPointF & point ) const  
  23. {  
  24.   //  if(this->group()) return false;           //这一行可作为加入组时使用  
  25.   
  26.     f2Point mp = {point.x(), point.y()};  
  27.     f2Point one ={this->line().x1(),this->line().y1()};  
  28.     f2Point two ={this->line().x2(),this->line().y2()};  
  29.   
  30.    return MatchSegment(mp,one, two);  
  31. }  
另一个需要改变的是当图形元素成组时,鼠标的选取与我们通常的模式不同,因此有了上面contains方法中的第一行。
在GraphicsScene模式下,各种鼠标事件、图形变换等都单独成为一个类,不知何故?这样会使类个数急剧上升。
3. OpenGL View
Qt可以显示SVG图形,也支持OpenGL,也显示出Qt想图形显示”通吃“的理想。增加对OpenGL的支持,也就意味着我们可以使图形更炫:显示三维图形。本文不涉及如何构建三维图形,如果已经写好OpenGL代码,那么事情就非常简单了,大部分情况下需要覆盖以下方法
    void initializeGL();
    void resizeGL(int w, int h);
    void paintGL();
如果需要用户交互,则重写若干Mouse函数即可。
4. QML View
与Sliverlight、Flash相比,QML似乎也没有什么突出优势,各有长短而已。QDeclarativeView可以显示一个QML文件,所有用户的行为可以在QML文件中定义,当然,我们可以通过代码控制QML文件中各种显示元素的行为。
关于QML的内容,本文也不涉及。

沒有留言:

張貼留言