跨平台编程的利器—Qt:一个MDI图形应用框架

(本文接上篇“概述")

(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中分割条也是一个窗体,比较复杂地分割窗体的的示例代码如下。(上图的效果)

    m_pvsplitter= new QSplitter(Qt::Vertical,this);
     m_pvsplitter->resize(400,400);

    m_phsplitter= new QSplitter(Qt::Horizontal,m_pvsplitter);

    m_ptreeview = new CTreeView(m_phsplitter,m_pMainWin, m_pdoc);
    m_ptreeview->resize(100,300);
    m_pelementview = new CElementView(0,m_pMainWin, m_pdoc);

    ScrollWindow *elementwin = new ScrollWindow(m_phsplitter,m_pelementview, 300,300);

     m_phsplitter->addWidget(m_ptreeview);
     m_phsplitter->addWidget(elementwin);
     m_phsplitter->setStretchFactor(1,1);
     m_phsplitter->resize(400,300);

     m_pinfoview = new CInfoView(m_pvsplitter,m_pMainWin, m_pdoc);
     m_pinfoview->resize(400,100);

    m_pvsplitter->addWidget(m_phsplitter);
    m_pvsplitter->addWidget(m_pinfoview);
    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方法。代码如下。

typedef struct {float x,y; } f2Point;
typedef struct { float A,B,C; } fEqLine;   //Ax+By+C=0
  
bool MatchSegment(const f2Point &mp,const f2Point &one, const f2Point &two)       //匹配线段
{
  fEqLine temp;
  double dd;

  temp.A=one.y-two.y;
  temp.B=two.x-one.x;
  temp.C=-(one.x*lin.A+one.y*lin.B);

  dd=fabs(temp.A*mp.x+temp.B*mp.y+(double)temp.C)/sqrt((double)temp.A*temp.A+temp.B*temp.B);
  if(ddgroup()) return false;           //这一行可作为加入组时使用

    f2Point mp = {point.x(), point.y()};
    f2Point one ={this->line().x1(),this->line().y1()};
    f2Point two ={this->line().x2(),this->line().y2()};

   return MatchSegment(mp,one, two);
}
另一个需要改变的是当图形元素成组时,鼠标的选取与我们通常的模式不同,因此有了上面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的内容,本文也不涉及。



你可能感兴趣的:(Qt)