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() 來確定哪些選項不能由用戶設定
分類: 程序
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章