Qt 笔记 Chapter6-Chapter8

Chapter 6 Layout Management

  • Qt提供的layout: QHBoxLayout, QVBoxLayout, QGridLayout, QStackedLayout.
  • 使用layout的一个理由是使得widget适应字体的变化和程序界面语言的变化.
  • 其他可以执行layout管理的类: QSplitter, QScrollArea, QMainWindow, QMdiArea
    • QSplitter 会提供以一个splitter条让用户可以拖动或者重置widget的大小.
    • QMdiArea则支持MDI(多文档接口)

6.1 Laying Out Widgets on a Form

  • 这里管理子widget的layout有三种方式: 绝对位置, 手工layout, layout 管理器
  • 绝对位置方式, 通过各个子widget调用setGeometry来设定位置和大小, 主widget则调用setFixedSize设置固定大小
    • 使用绝对位置的缺点:
      1. 用户不能重置其大小
      2. 一些文本可能由于字体的变化或者语言的变化而被截去部分内容
      3. 在某些style中, widget可能会有不适当的大小
      4. 必须手工计算大小和位置, 乏味且容易出错, 维护困难
  • 手工layout, 位置仍然绝对, 但是大小可以适应窗口. 通过在 resizeEvent() 方法中实现该功能
  • layout 管理器方法, 考虑每个widget的size hint(该size hint依赖于widget的字体, 内容), 同时也考虑最小和最大大小.
    • 根据字体的变化, 内容的变化, 窗口大小的变化自动修正大小.
    • 三个最重要的layout类是QHBoxLayout, QVBoxLayout, QGridLayout, 这三者都为QLayout的派生类
    • 一个layout内的边缘和子widget之间的空格由当前widget的style决定. 也可以使用QLayout::setContentsMargins()和QLayout::setSpacing()来改变
    • QGridLayout的语法: layout->addWidget(widget, row, column, rowSpan, columnSpan);
    • addStretch() 用来告知layout 管理器填充该处的空白.
    • layout 管理器的优点:
      1. 添加或者移除一个widget, layout会自动修正以适应新的情况, 对hide()和show()也有同样的效果.
      2. 当子widget的size hint发生改变时, layout 管理器会自动修改以适应新的size hint
      3. 根据子widget的size hint和最小值来设置layout的最小值
    • 有时我们需要修改size policy和widget的size hint来实现我们需要的layout
    • size policy的值:
      1. Fixed --- 固定的layout, 不能拉伸和收缩. 使用size hint的大小
      2. Minimum --- 表示该widget的最小值就是size hint. 不能够收缩至比该值更小的值. 可以填充可用的空间给widget
      3. Maximum --- 表示该widget的最大值就是size hint, 该widget可以收缩至其最小值 minimum size hint
      4. Preferred --- 该widget的preferred值就是size hint, 在需要的时候可以收缩和拉伸
      5. Expanding --- 表示该widget可以收缩和拉伸, 但其最好选择拉伸
    • Expanding 和 Preferred的区别: 当一个form包含两者的widget, 该form大小变化时, 额外的空白处则给予Expanding widget, 而Preferred widget保持为其size hint的大小
    • Minimum, Expanding 和 Ignored这两个Size Policy不再经常使用, 后者忽略widget的size hint和最小值hint
    • 为了补充水平和垂直部分的size policy, 我们还设定了拉伸因子(strectch factor), 可以设置widget在水平或垂直方向的拉伸
    • 如果对一个widget不满意, 我们还可以派生该widget类, 重写其sizeHint()函数

6.2 Stacked Layouts

  • QStackedLayout 类对一系列子widget布局, 或者"分页". 且每次只显示一个页面, 隐藏其他页面的内容.
    • Qt提供QStackedWidget类表示带内置QStackedLayout的QWidget.
  • 页数是从0开始, 设置当前页 setCurrentIndex, 得到一个子widget的页号则使用indexOf().
  • QListWidget可以和QStackedLayout配合使用.
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    listWidget = new QListWidget;
    listWidget->addItem(tr( "Appearance" ));
    listWidget->addItem(tr( "Web Browser" ));
    listWidget->addItem(tr( "Mail & News" ));
    listWidget->addItem(tr( "Advanced" ));
     
    stackedLayout = new QStackedLayout;
    stackedLayout->addWidget(appearancePage);
    stackedLayout->addWidget(webBrowserPage);
    stackedLayout->addWidget(mailAndNewsPage);
    stackedLayout->addWidget(advancedPage);
    connect(listWidget, SIGNAL(currentRowChanged( int )),
             stackedLayout, SLOT(setCurrentIndex( int )));
    ...
    listWidget->setCurrentRow(0);
    • currentRowChanged(int)信号发送给setCurrentIndex(int) slot 实现页面切换
    • setCurrentRow() 设置当前页面
  • Qt Designer实现分页
    • 用"dialog"模板或者"widget"模板创建新的form
    • 添加QListWidget和QStackedWidget
    • 填充每个页面的widget和layout
    • 水平方向布局这两个widget
    • signal和slot连接 currentRowChanged(int) --> setCurrentIndex(int)
    • 设置list widget的currentRow属性

6.3 Splitters

  • QSplitter的子widget根据创建的顺序自动排列在一起. 相邻的widget之间有splitter bar. 下面是创建的代码
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int main( int argc, char *argv[])
    {
         QApplication app(argc, argv);
     
         QTextEdit *editor1 = new QTextEdit;
         QTextEdit *editor2 = new QTextEdit;
         QTextEdit *editor3 = new QTextEdit;
     
         QSplitter splitter(Qt::Horizontal);
         splitter.addWidget(editor1);
         splitter.addWidget(editor2);
         splitter.addWidget(editor3);
         ...
         splitter.show();
         return app.exec();
    }
  • QSplitter 派生自QWidget, 像其他的widget一样使用.
  • MailClient的例子
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    MailClient::MailClient()
    {
         ...
         rightSplitter = new QSplitter(Qt::Vertical);
         rightSplitter->addWidget(messagesTreeWidget);
         rightSplitter->addWidget(textEdit);
         rightSplitter->setStretchFactor(1, 1);
     
         mainSplitter = new QSplitter(Qt::Horizontal);
         mainSplitter->addWidget(foldersTreeWidget);
         mainSplitter->addWidget(rightSplitter);
         mainSplitter->setStretchFactor(1, 1);
         setCentralWidget(mainSplitter);
     
         setWindowTitle(tr( "Mail Client" ));
         readSettings();
    }
    • setStretchFactor 设置拉伸因子, 缺省是随着大小变化, 各部分的比例不变, 第一个参数是以第一个widget为0的索引值, 第二个参数设置拉伸因子, 缺省为0
  • 保存设置
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    QSettings settings( "Software Inc." , "Mail Client" );
    settings.beginGroup( "mainWindow" );
    settings.setValue( "geometry" , saveGeometry());
    settings.setValue( "mainSplitter" , mainSplitter->saveState());
    settings.setValue( "rightSplitter" , rightSplitter->saveState());
    settings.endGroup();
    // 读取设置
    QSettings settings( "Software Inc." , "Mail Client" );
    settings.beginGroup( "mainWindow" );
    restoreGeometry(settings.value( "geometry" ).toByteArray());
    mainSplitter->restoreState(
             settings.value( "mainSplitter" ).toByteArray());
    rightSplitter->restoreState(
             settings.value( "rightSplitter" ).toByteArray());
    settings.endGroup();

6.4 Scrolling Areas

  • 如果需要使用滚动条, 最好使用QScrollArea而不是自己实现QScrollBar和滚动功能, 因为这样太复杂
    • 使用QScrollArea的方法是调用setWidget使得该widget成为QScrolllArea视口的子类. 访问视口, QScrollArea::viewport()
      ?
      1
      2
      3
      4
      5
      QScrollArea scrollArea;
      scrollArea.setWidget(iconEditor);
      scrollArea.viewport()->setBackgroundRole(QPalette::Dark);
      scrollArea.viewport()->setAutoFillBackground( true );
      scrollArea.setWindowTitle(QObject::tr( "Icon Editor" ));
  • 通过调用setWidgetResizable(true)告知该QScrollArea可以自动的重置widget的大小. 这样可以使用其size hint之外的空间
  • 缺省情况是当视口比widget更小的时候才显示滚动条, 如果想滚动条永远显示, 则使用以下代码:
    ?
    1
    2
    scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
  • QScrollArea派生自QAbstractScrollArea, QTextEdit和QAbstractItemView的基类为QAbstractScrollBar.

6.5 Dock Windows and Toolbars

  • Dock Window表示那些可以在QMainWindow Dock的窗口以及可以独立出来的窗口.
    • QMainWindow 提供了四个浮动区域, 上,下, 左, 右.
  • 每个dock window都有其标题条, 可通过QDockWidget::setFeatures() 设置其属性
  • QMainWindow::setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); // 上面的函数设置左上角区域属于左边的dock widget区域
  • 如何在一个QDockWidget包装一已存widget, 且插入右边的dock区域
    ?
    1
    2
    3
    4
    5
    6
    QDockWidget *shapesDockWidget = new QDockWidget(tr( "Shapes" ));
    shapesDockWidget->setObjectName( "shapesDockWidget" );
    shapesDockWidget->setWidget(treeWidget);
    shapesDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea
                                          | Qt::RightDockWidgetArea);
    addDockWidget(Qt::RightDockWidgetArea, shapesDockWidget);
    • 每个对象都有一个对象名, 在调试的时候有用
    • Dock widget和Toolbar都需设置对象名, 这样可使用函数QMainWindow::saveState()和QMainWindow::restoreState() 保存和恢复状态和位置大小
  • 工具条
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    QToolBar *fontToolBar = new QToolBar(tr( "Font" ));
    fontToolBar->setObjectName( "fontToolBar" );
    fontToolBar->addWidget(familyComboBox);
    fontToolBar->addWidget(sizeSpinBox);
    fontToolBar->addAction(boldAction);
    fontToolBar->addAction(italicAction);
    fontToolBar->addAction(underlineAction);
    fontToolBar->setAllowedAreas(Qt::TopToolBarArea
                                  | Qt::BottomToolBarArea);
    addToolBar(fontToolBar);
  • 保存和回复设置
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 保存
    QSettings settings( "Software Inc." , "Icon Editor" );
    settings.beginGroup( "mainWindow" );
    settings.setValue( "geometry" , saveGeometry());
    settings.setValue( "state" , saveState());
    settings.endGroup();
    // 恢复
    QSettings settings( "Software Inc." , "Icon Editor" );
    settings.beginGroup( "mainWindow" );
    restoreGeometry(settings.value( "geometry" ).toByteArray());
    restoreState(settings.value( "state" ).toByteArray());
    settings.endGroup();
  • QMainWindow还给dock window和Toolbar提供了右键菜单

6.6 多文档接口

  • 一个MDI应用程序通过使用QMdiArea类作为中心widget以及让每个文档窗口为一个QMdiArea的子窗口
    • 子窗口菜单-MDI应用程序提供了一系列菜单选项表示所有的文档窗口, 当前的文档窗口会有选中标志
  • 在构造函数中, 创建一个QMdiArea对象并设置为中心widget, 并将subWindowActivated()信号发送给一个slot, 实现菜单的更新
    • 在构造函数的结尾部分有一行代码: QTimer::singleShot(0, this, SLOT(loadFiles()));
      • 表示0秒的间隔之后调用loadFiles(). 在事件循环为空闲时, 计时器运行完时间, 事实上表示当构造函数完成之后, 主窗口显示之时, 调用loadFiles()函数
      • 如果不这样做, 当有大量的文件之时, 直到文件加载完毕之后构造函数还未必完成时, 用户也许会在屏幕上看不到任何东西. 而认为程序失败且重启程序
        ?
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        void MainWindow::loadFiles()
        {
             QStringList args = QApplication::arguments();
             args.removeFirst();
             if (!args.isEmpty()) {
                 foreach (QString arg, args)
                     openFile(arg);
                 mdiArea->cascadeSubWindows();
             } else {
                 newFile();
             }
             mdiArea->activateNextSubWindow();
        }
  • 确保编辑器选择了文本才允许这两个菜单项可以使用
    ?
    1
    2
    3
    4
    connect(editor, SIGNAL(copyAvailable( bool )),
             cutAction, SLOT(setEnabled( bool )));
    connect(editor, SIGNAL(copyAvailable( bool )),
             copyAction, SLOT(setEnabled( bool )));
    • QMdiArea的addSubWindow() 函数可以创建一个新的QMdiSubWindow: QMdiSubWindow *subWindow = mdiArea->addSubWindow(editor);
    • QActionGroup 确保只有一个窗口菜单选项被选中.
  • QMdiArea::activeSubWindow() --- 返回其活跃窗口
  • qobject_cast<Editor *> --- 用于强制转换.
  • QTextCursor::hasSelection () --- 返回当前文本光标是否选择了文本
  • QAction::setChecked() --- 设置选中该Action
  • QScintilla --- 代码编辑的widget
  • 每个子窗口设置 Qt::WA_DeleteOnClose 属性, 当关闭的时候删除该窗口, 以免内存泄漏

Chapter 7 Event Processing

7.1 Reimplementing Event Handlers

  • 在Qt中, 任何事件都是QEvent派生类的实例. Qt 处理上百种事件类型, 通过枚举值来标识出事件类型.
    • 举个例子: QEvent::type() 返回 QEvent::MouseButtonPress 则表示一个鼠标按下事件.
    • 许多的事件类型都需要存储更多的信息, 例如鼠标按下事件需要知道是哪个按键被按下以及指针所在位置. 这些都保存在QEvent的派生类QMouseEvent中.
  • 通过event()函数将事件通知给对象. 该函数从QObject继承而来.
    • 在QWidget中实现了大多数通用事件处理函数: mousePressEvent, keyPressEvent, paintEvent.
    • 可以创建自定义事件类型并分配给我们自己的事件.
  • 键盘事件通过重写keyPressEvent()和keyReleaseEvent()实现.
    • Modifier键: Ctrl, Shift, Alt, 可以使用KeyPressEvent() 和 QKeyEvent::modifiers().
    • 例如判断 Ctrl + Home
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      switch (event->key()) {
      case Qt::Key_Home:
           if (event->modifiers() & Qt::ControlModifier) {
               goToBeginningOfDocument();
           }
           else
           {
               goToBeginningOfLine();
           }
           break ;
      ... ...
      }
  • 一般而言, Tab和Shift+Tab用于切换widget. 在QWidget::event()中被处理, 该函数在keyPressEvent()之前被调用,
    • 如果想要修改该功能, 则重写QWidget::event()函数
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      bool CodeEditor::event(QEvent *event)
      {
           if (event->type() == QEvent::KeyPress) {
               QKeyEvent *keyEvent = static_cast <QKeyEvent *>(event);
               if (keyEvent->key() == Qt::Key_Tab) {
                   insertAtCurrentPosition( '\t' );
                   return true ;
               }
           }
           return QWidget::event(event);
      }
  • 实现快捷键与Action, 处理函数的绑定
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    goToBeginningOfLineAction =
             new QAction(tr( "Go to Beginning of Line" ), this );
    goToBeginningOfLineAction->setShortcut(tr( "Home" ));  // 连接
    connect(goToBeginningOfLineAction, SIGNAL(activated()),
             editor, SLOT(goToBeginningOfLine()));
     
    goToBeginningOfDocumentAction =
             new QAction(tr( "Go to Beginning of Document" ), this );
    goToBeginningOfDocumentAction->setShortcut(tr( "Ctrl+Home" ));
    connect(goToBeginningOfDocumentAction, SIGNAL(activated()),
             editor, SLOT(goToBeginningOfDocument()));
    • 如果在程序用户界面菜单和工具栏都没有这个Action, 则会使用QShortcut来实现该快捷键功能, 以实现键的绑定
    • 可以用QAction::setShortcutContext() 或者 QShortcut::setContext() 修改快捷键的绑定
  • 三个事件 timerEvent(), showEvent(), hideEvent()
    • updateGeometry() 用于通知widget的layout manager其子widget的size hint可能发生变化, 让其进行修正.
  • startTimer() 函数启动一个计时器, 在showEvent()中设置计时器, 可以使得在widget完全显示之后启动计时器
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void Ticker::timerEvent(QTimerEvent *event)
    {
         if (event->timerId() == myTimerId) {
             ++offset;
             if (offset >= fontMetrics().width(text()))
                 offset = 0;
             scroll(-1, 0);
         } else {
             QWidget::timerEvent(event);
         }
    }
  • 本例使用 QWidget::scroll() 替换update(), 更有效率, 每次只需要绘制多出的1像素位置的内容.

7.2 Installing Event Filter

  • Qt的事件模型一个非常强大的功能就是一个QObject的实例可以监视另一个QObject实例的事件, 在后者QObject的实例看到这个事件之前.
  • 通过建立监视器来监控子widget的事件, 来实现特定功能, 使用事件过滤器. 具体有两个步骤:
    • 通过在目标上调用installEventFilter()函数来注册目标对象的监视器对象
    • 在监视器的eventFilter()函数中处理目标对象的事件
  • 一般最好在构造函数中注册监视器对象
    ?
    1
    2
    3
    4
    firstNameEdit->installEventFilter( this );
    lastNameEdit->installEventFilter( this );
    cityEdit->installEventFilter( this );
    phoneNumberEdit->installEventFilter( this );
    • 这四个widget首先将发送调用本widget的eventFilter()函数, 而后再到其自身的处理函数
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)
      {
           if (target == firstNameEdit || target == lastNameEdit
                   || target == cityEdit || target == phoneNumberEdit) {
               if (event->type() == QEvent::KeyPress) {
                   QKeyEvent *keyEvent = static_cast <QKeyEvent *>(event);
                   if (keyEvent->key() == Qt::Key_Space) {
                       focusNextChild();
                       return true ;
                   }
               }
           }
           return QDialog::eventFilter(target, event);
      }
    • 上面代码实现空格键切换widget, 注意如果实现了需要的功能返回true, 这样就不会事件传递到目标对象.
    • QWidget::focusNextChild() --- 使下一个widget具有焦点
  • 五个处理与过滤事件的层次
    • 我们可以重新实现特定的事件处理函数
    • 我们可以重新实现QObject::event()
      • 用于在到达特定的事件处理函数之前处理该事件, 例如Tab键. 另外在重实现该函数的过程中需要调用基类的event()用于处理其他事件
    • 我们可以在单个对象上安装event filter
    • 我们可以在QApplication 对象上安装一个event filter, 所有对象的所有事件都会发送给eventFilter()函数. 常用语调试,
    • 我们可以实现QApplication的派生类, 以及重新实现notify().
      • Qt调用QApplication::notify()发送事件, 重新实现该函数是获得所有事件的唯一方法, 在任何event filter有机会处理事件之前.
  • 许多类型的事件, 包括鼠标和按键事件, 都会进行传递. 当目标对象没有处理该事件, 则其父widget会进行处理该事件. 直到顶层对象.

7.3 Staying Responsive during Intensive Processing

  • 调用QApplication::exec()之后, 开始时间循环, 首先是显示和绘制widget, 而后循环运行检查是否有新的事件, 而后分发这些事件至对象.
  • 使用多线程处理一些耗时的任务, 以避免界面不响应
  • QApplication::processEvents()告诉Qt处理任何待处理的事件, 而后返回控制给调用者.
    • 事实上 QApplication::exec只是在while循环内部调用processEvents().
  • 在耗时的处理函数中使用qApp->processEvents(); 或者: qApp->processEvents(QEventLoop::ExcludeUserInputEvents); 以避免其他重要的操作如关闭程序. 用于忽略鼠标和按钮事件.
  • QProgressDialog是一个进程条, 用于告知当前处理的程度
    • QProgressDialog 类: setLabelText, setRange, setModel, setValue, wasCanceled
  • 用QApplication::hasPendingEvents和计时器来判断当前是否处于空闲时期

Chapter 8 2D Graphics

  • Qt 4.2 "图形视图"结构的核心部分: QGraphicsView, QGraphicsScene, QGraphicsitem 类

8.1 Painting with QPainter

  • 在图形设备上绘制, 仅仅需要将设备的指针传递给QPainter构造函数的参数, 如: QPainter painter(this);
    • QPainter的三个重要属性: pen, brush, font
    • 重要draw函数: drawPoint, drawLine, drawPolyLine, drawPoints, drawLines, drawPolygon, drawRect, drawRoundRect, drawEllipse, drawArc, drawChord, drawPie, drawText, drawPixmap, drawPath
    • pen 用于绘制线条和图形轮廓, 由颜色, 宽度, 线条形状, 关联方式组成.
      • Cap 和 joint styles: FlatCap, SquareCap, RoundCap, MiterJoint, BevelJoint, RoundJoint
      • Line Style: SolidLine, DashLine, DotLint, DashDotLine, DashDotDotLine, NoPen
    • brush 表示用于填充几何形状的模式, 由颜色和风格组成. 也可以是一个纹理
      • style: SolidPattern, Dense1Pattern, Dense2Pattern, Dense3Pattern, Dense4Pattern, Dense5Pattern, Dense6Pattern, Dense7Pattern, HorPattern, VerPattern, CrossPattern, BDiagPattern, FDiagPattern, DiagCrossPattern, NoBrush
    • font 用于绘制文本, 含有许多属性, 其中包含 family和 点大小
    • 可以用setPen, setBrush, setFont 修改这些内容
  • painter.setRenderHint(QPainter::Antialiasing, true); // 可以实现反锯齿
  • QPainterPath 可以指定用于连接基本图形的元素容器: 如直线, 椭圆形, 多边形, 弧, 贝塞尔曲线 和其他绘制路径.
    • 一个路径可以表示一条轮廓, 以及该轮廓所标示的面积, 该面积可以用笔刷填充.
  • gradient fill是一个可选的单色填充方式, Gradient依赖于颜色插值以得到两个颜色之间的平滑转换. 常用于生成3D效果.
    • Qt 提供三个gradient类型: 线性, 圆锥体的(conical), 径向的(radial)
    • 线性: 有两个控制点用于定义, 通过一系列线上的"颜色点"来连接两个点. 如:
      ?
      1
      2
      3
      4
      QLinearGradient gradient(50, 100, 300, 350);
      gradient.setColorAt(0.0, Qt::white);
      gradient.setColorAt(0.2, Qt::green);
      gradient.setColorAt(1.0, Qt::black);
    • Radial: 通过中心点(Xc,Yc), 半径r, 焦点(Xf, Yf)来定义,
    • Conical: 通过中心点(Xc, Yc)和角度a来定义.
  • 其他的属性:
    • background brush, brush origin, clip region(可以绘制的区域),
    • viewport, window, world transform --- 可以确定逻辑QPainter坐标映射到物理绘制设备坐标. 缺省情况两者一致
    • composition mode --- 定义新绘制的像素如何与原有像素相互影响. 默认是alpha 混合.
  • 我们可以通过 save()保存当前painter的状态, 通过restore()还原.

8.2 Coordinate System Transformations

  • 缺省的坐标系统, 左上角(0, 0), X轴正向方向向右, Y轴正向方向向下, 每个像素的大小为 1x1
  • 一个像素的中心是0.5, 只有当禁用反锯齿的时候+0.5.
    • 如果开启了反锯齿, 在点(100, 100)绘制黑色. 则四个点(99.5, 99.5), (99.5, 100.5), (100.5, 99.5), (100.5, 100.5)为灰色.
    • 如果不想使用这个效果, 则移动QPainter (+0.5, +0.5), 或者指定半个像素的坐标
    • 窗口(逻辑) --- 视图(物理)
    • 窗口-视图机制 可以让绘制代码独立于绘制设备的分辨率和大小
      ?
      1
      painter.setWindow(-50, -50, 100, 100);    // 从(-50, -50)到(50, 50), 中心点为(0, 0)
    • 如果我们想要在45度角绘制文本
      ?
      1
      2
      3
      4
      QTransform transform;
      transform.rotate(+45.0);
      painter.setWorldTransform(transform);
      painter.drawText(pos, tr( "Sales" ));
    • 指定转换的简单方法是使用QPainter的translate(), scale(), rotate()和shear()方法
    • 如果经常使用同一个转换, 可以保存一个QTransform, 在需要的时候使用
  • QTimer调用setSingleShot(true), 表示在时间结束之后只发送一次time out信息. 否则缺省计时器会重复启发直至他们停止或者销毁.
  • qBound() 函数, 确保第二个参数的值在第一个和第三个参数之间.
  • 本节的例子使用QConicalGradient(QRadialGradient, QLinearGradient)实现了非常漂亮的效果, 这几个可以当作笔刷使用

8.3 High-Quality Rendering with QImage

  • 有时我们需要权衡速度和精确度之间的关系. 当精确度比效率更重要时, 我们将绘制到QImage, 而后将结果拷贝至屏幕. 这将使用Qt的内置绘制引擎.
    • 唯一的限制是QImage的创建参数为QImage::Format_RGB32 或 QImage::Format_ARGB32_Premultiplied
  • "premultiplied ARPG32" 格式表示红,绿,蓝频道带有多个alpha频道. 代码:
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void MyWidget::paintEvent(QPaintEvent *event)
    {
         QImage image(size(), QImage::Format_ARGB32_Premultiplied);
         QPainter imagePainter(&image);
         imagePainter.initFrom( this );
         imagePainter.setRenderHint(QPainter::Antialiasing, true );
         imagePainter.eraseRect(rect());
         draw(&imagePainter);
         imagePainter.end();
     
         QPainter widgetPainter( this );
         widgetPainter.drawImage(0, 0, image);
    }
    • 上面代码先用QPainter绘制QImage, 而后绘制到屏幕
    • 一个非常好的功能就是Qt图形引擎支持合成模式, 源和目标像素可以混合. 在所有的绘制操作都可以实现这点, 如笔, 笔刷, 渐进和图像绘制
    • 缺省的合成模式为: QImage::CompositionMode_SourceOver, 表示源像素会覆盖在目标像素之上, 根据alpha值设置透明度.
    • 可通过 QPainter::setCompositionMode() 来设置合成模式. 如:
      ?
      1
      2
      3
      4
      QImage resultImage = checkerPatternImage;
      QPainter painter(&resultImage);
      painter.setCompositionMode(QPainter::CompositionMode_Xor);
      painter.drawImage(0, 0, butterflyImage);
    • XOR 源和目标. 注意XOR模式对Alpha也是有效的.

8.4 Item-Based Rendering with Graphics View

  • QPainter对于绘制自定义widget和比较少的条目时是比较理想的方法
  • 绘制大量的条目和内容时的解决方案
    • 图形视图结构包含场景, 由QGraphicsScene类表示, 场景中的项目, 由QGraphicsItem的子类表示.
    • 通过在视图中显示场景. 由QGraphicsView类表示视图. 相同的场景可以在多个视图中显示. 如显示一个巨大场景的不同部分, 不同的转换.
  • 预定义好的QGraphicsItem派生类, 如QGraphicsLineItem, QGraphicsPixmapItem, QGraphicsSimpleTextItem, QGraphicsTextItem
    • 也可以自己创建自定义派生类
  • QGraphicsScene控制图形元素的结合, 有三个layer, 背景层, 元素层, 前景层. 背景和前景层通常由QBrushes指定
    • 我们可以基于pixmap创建一个纹理QBrush作为背景. 前景则可以设置半透明等
    • 视图则管理场景. 视图可用内置的2D绘制引擎, 也可以用Opengl. 使用setViewport()来调用opengl
    • 视图可以用打印一个场景或者场景的一部分.
    • 这个结构使用三个不同的坐标系统 - 视图坐标, 场景坐标和项目(item)坐标 -- 带有一个坐标系统向另一个坐标系统映射的功能.
      • 视图坐标系统位于QGraphicsView的视图内部. 场景坐标系统是逻辑坐标系统, 用于防治场景上的顶层项目(item).
      • 项目坐标系统用于指定每个项目, 且其中心为(0, 0)本地坐标.
      • 事实上我们只关心系统坐标放置顶层项目, 项目坐标放置子项目.
    • 为了介绍图形视图, 本例使用了两个例子, 第一个例子为简单的图表编辑器,
    • 第二个例子为注解映射程序显示如何处理大量的图形对象, 如何有效的渲染以及缩放
  • QGraphicsItem不是QObject的派生类, 如果想要使用signal和slot, 可以实现多个继承, 其中一个基类为QObject. 其可以调用函数 setLine 绘制直线.
  • Q_DECLARE_TR_FUNCTIONS()宏用于添加一个tr()函数至该类. 即便它不是QOjbect的派生类, 这样可以让我们简单的使用tr(), 而不是QObject::tr() 或者 QApplication::translate()
  • 当我们实现了QGraphicsItem的派生类时, 如果想要手工绘制内容, 则需要重新实现boundingRect()和paint()
    • 如果我们不重新实现 shape(), 基类则会回去调用boundingRect, 所以我们重新实现shape()用于返回更精确的形状, 该形状可以考虑到节点的圆角角
    • graphic view结构使用围绕矩形(bounding rectangle)确定一个项目绘制的区域. 这能够快速的显示任意大的场景, 虽然在每个时候只能显示该场景的一部分
    • 形状(shape)则确定某点是否在一个项目之内, 或者两个项目是否相互碰撞.
  • 本例图表应用程序, 我们将提供属性对话框用于编辑节点的位置, 颜色和文本. 通过双击节点则可修改文本.
  • 下面的代码删除该节点的所有Link, 而无论该Link是否被销毁了, 如果使用qDeleteAll() 则会产生一些副作用.
    ?
    1
    2
    foreach (Link *link, myLinks)
         delete link;
  • 当一个项目的围绕矩形会发生变化之时(由于新的文本也许会大于或小于当前文本),
    • 我们必须在之前立即调用 prepareGeometryChange(), 这样便于影响项目的围绕矩形.
    • 修改颜色的时候不需要调用 prepareGeometryChange(), 因为这不会影响到项目的围绕矩形大小
  • 求一个节点的矩形框
    ?
    1
    2
    3
    4
    5
    6
    const int Padding = 8;
    QFontMetricsF metrics = qApp->font();
    QRectF rect = metrics.boundingRect(myText);
    rect.adjust(-Padding, -Padding, +Padding, +Padding);
    rect.translate(-rect.center());
    return rect;   
    • QFontMetrics计算的围绕矩形左上角坐标总为(0, 0)
  • 可使用QPainterPath精确的描述圆角矩形, 可以使得当鼠标位于角落且不在圆角矩形之内时, 不能够选择该矩形
  • QStyleOptionGraphicsItem是一个不经常使用的类, 提供了几个公有成员变量,
    • 如当前layout方向, 字体metrics, palette, 矩形, 状态, 转换矩阵, 以及细节层次Lod,
    • 重新实现 QGraphicsItem::itemChange, 当项目变化时作出一些反应
  • 创建一个QGraphicsScene, 而后创建一个QGraphicsView来显示它.
    • 选择的项目可以通过按Ctrl键多选. 设置模式QGraphicsView::RubberBandDrag, 表示可以通过鼠标划拉一个矩形多选项目: view->setDragMode(QGraphicsView::RubberBandDrag);
    • QGraphicsScene 可以发射信号 selectionChanged, 调用addItem 增加项目, clearSelection取消选择, selectedItems返回选中项目的列表
    • QGraphicsItem可调用setPos设置位置, setSelected表示选中与否
    • QGraphicsView 可调用removeAction 移除菜单, 调用addAction添加菜单
  • QMutableListIterator 用于遍历一个列表; qDeleteAll 用于删除一列表所有元素
  • QColorDialog::getColor() 调用颜色对话框
  • QApplication::clipboard()->setText(str); --- 使用剪贴板
  • QApplication::clipboard()->text(); --- 得到剪贴板文本
  • QString::split --- 分割字符串为QStringList
  • QStringList 用mid 得到部分的列表, 用join合起来成一个字符串
  • 本节第二个例子
    • QGraphicsScene: setBackgroundBrush设置背景笔刷
    • Lod可以表示为缩放因子, QStyleOptionGraphicsItem::levelOfDetail 表示为其缩放因子
    • 使用 ItemIgnoresTransformations 标志可以忽略缩放, 不会跟随View的缩放而更改该Item的大小
  • QGraphicsView派生一个类, 实现特定的特色.
    • 调用setDragMode, 可设置拖曳模式, 如 setDragMode(ScrollHandDrag);
    • 实现wheelEvent函数, 可实现鼠标滚轮事件. 而后调用QGraphicsView::scale函数实现缩放
  • 我们的graphic view有许多的功能, 如可以拖曳, 图形项目有tooltip和自定义光标.
    • 可通过给项目设置QGraphicsItemAnimations和QTimeLine 来实现动画.

8.5 Printing

  • 对于Qt来说, 打印和QWidget, QPixmap, QImage绘制一样, 由以下步骤组成
    • 创建一个QPrinter用于绘制设备
    • 弹出QPrintDialog, 让用户选择一个打印机并设置一些属性.
    • 创建一个QPainter, 让其对QPrinter进行操作
    • 使用QPainter绘制一个页面
    • 调用QPrinter::newPage() 绘制下一个页面
    • 重复第四步和第五步直至所有页面打印完毕
  • 在Windows和Mac OS中, QPrinter使用系统的打印驱动. 在Unix中, 它生成PostScript并将其发送给ip或ipr(或者使用QPrinter::setPrintProgram()发送给程序集)
    • QPrinter也可以通过调用setOutputFromat(QPrinter::PdfFormat)生成PDF文件
    • 通过QPrintDialog的对象调用exec()来执行打印对话框.
    • 可将QPrinter对象作为参数传送给QPainter, 而后QPainter绘制图像实现打印一个图像. drawImage
    • 如果要打印一个graphics view scenes也很简单, 将QPrinter作为第一个参数传送给QGraphicsScene::render()或者QGraphicsView::render().
      • 如果只想绘制场景的一部分, 则将目标矩形(打印页面的位置)和源矩形作为参数传送给render()的可选参数.
  • 两个处理打印多页面的方法
    • 我们可以将我们的数据转换成HTML, 而后使用QTextDocument渲染它. 使用Qt的富文本引擎
    • 手动执行绘制和页面中断
    • 富文本方式:
      ?
      1
      2
      3
      QTextDocument textDocument;
      textDocument.setHtml(html);
      textDocument.print(&printer);
  • 本节还演示了如何给一个QStringList进行分页打印. 写了一个函数, 根据高度进行分页
    ?
    1
    2
    void PrintWindow::paginate(QPainter *painter, QList<QStringList> *pages,
                             const QStringList &entries)
    • 在例子中函数 int PrintWindow::entryHeight(QPainter *painter, const QString &entry) 计算每个条目的高度 其使用 QPainter::boundingRect() 计算垂直高度.
    • 通过QPrintDialog, 用户可以设定拷贝次数, 打印范围, 请求页面顺序(顺序还是反序)
    • 可通过调用QPrintDialog::setEnabledOptions() 来确定哪些选项不能由用户设定
分类:  程序
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值