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設置固定大小
-
使用絕對位置的缺點:
- 用戶不能重置其大小
- 一些文本可能由於字體的變化或者語言的變化而被截去部分內容
- 在某些style中, widget可能會有不適當的大小
- 必須手工計算大小和位置, 乏味且容易出錯, 維護困難
-
使用絕對位置的缺點:
- 手工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 管理器的優點:
- 添加或者移除一個widget, layout會自動修正以適應新的情況, 對hide()和show()也有同樣的效果.
- 當子widget的size hint發生改變時, layout 管理器會自動修改以適應新的size hint
- 根據子widget的size hint和最小值來設置layout的最小值
- 有時我們需要修改size policy和widget的size hint來實現我們需要的layout
-
size policy的值:
- Fixed --- 固定的layout, 不能拉伸和收縮. 使用size hint的大小
- Minimum --- 表示該widget的最小值就是size hint. 不能夠收縮至比該值更小的值. 可以填充可用的空間給widget
- Maximum --- 表示該widget的最大值就是size hint, 該widget可以收縮至其最小值 minimum size hint
- Preferred --- 該widget的preferred值就是size hint, 在需要的時候可以收縮和拉伸
- 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配合使用.
123456789101112131415
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. 下面是創建的代碼
12345678910111213141516
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的例子
1234567891011121314151617
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
-
保存設置
123456789101112131415
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()
12345
QScrollArea scrollArea;
scrollArea.setWidget(iconEditor);
scrollArea.viewport()->setBackgroundRole(QPalette::Dark);
scrollArea.viewport()->setAutoFillBackground(
true
);
scrollArea.setWindowTitle(QObject::tr(
"Icon Editor"
));
-
使用QScrollArea的方法是調用setWidget使得該widget成爲QScrolllArea視口的子類. 訪問視口, QScrollArea::viewport()
- 通過調用setWidgetResizable(true)告知該QScrollArea可以自動的重置widget的大小. 這樣可以使用其size hint之外的空間
-
缺省情況是當視口比widget更小的時候才顯示滾動條, 如果想滾動條永遠顯示, 則使用以下代碼:
12
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區域
123456
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() 保存和恢復狀態和位置大小
-
工具條
12345678910
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);
-
保存和回覆設置
123456789101112
// 保存
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()函數
-
如果不這樣做, 當有大量的文件之時, 直到文件加載完畢之後構造函數還未必完成時, 用戶也許會在屏幕上看不到任何東西. 而認爲程序失敗且重啓程序
12345678910111213
void
MainWindow::loadFiles()
{
QStringList args = QApplication::arguments();
args.removeFirst();
if
(!args.isEmpty()) {
foreach (QString arg, args)
openFile(arg);
mdiArea->cascadeSubWindows();
}
else
{
newFile();
}
mdiArea->activateNextSubWindow();
}
-
在構造函數的結尾部分有一行代碼: QTimer::singleShot(0, this, SLOT(loadFiles()));
-
確保編輯器選擇了文本才允許這兩個菜單項可以使用
1234
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
123456789101112
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()函數
1234567891011
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);
}
-
如果想要修改該功能, 則重寫QWidget::event()函數
-
實現快捷鍵與Action, 處理函數的綁定
1234567891011
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完全顯示之後啓動計時器
1234567891011
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()函數中處理目標對象的事件
-
一般最好在構造函數中註冊監視器對象
1234
firstNameEdit->installEventFilter(
this
);
lastNameEdit->installEventFilter(
this
);
cityEdit->installEventFilter(
this
);
phoneNumberEdit->installEventFilter(
this
);
-
這四個widget首先將發送調用本widget的eventFilter()函數, 而後再到其自身的處理函數
1234567891011121314
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具有焦點
-
這四個widget首先將發送調用本widget的eventFilter()函數, 而後再到其自身的處理函數
-
五個處理與過濾事件的層次
- 我們可以重新實現特定的事件處理函數
-
我們可以重新實現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)
-
線性: 有兩個控制點用於定義, 通過一系列線上的"顏色點"來連接兩個點. 如:
1234
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度角繪製文本
1234
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頻道. 代碼:
12345678910111213
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() 來設置合成模式. 如:
1234
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() 則會產生一些副作用.
12
foreach (Link *link, myLinks)
delete
link;
-
當一個項目的圍繞矩形會發生變化之時(由於新的文本也許會大於或小於當前文本),
- 我們必須在之前立即調用 prepareGeometryChange(), 這樣便於影響項目的圍繞矩形.
- 修改顏色的時候不需要調用 prepareGeometryChange(), 因爲這不會影響到項目的圍繞矩形大小
-
求一個節點的矩形框
123456
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的富文本引擎
- 手動執行繪製和頁面中斷
-
富文本方式:
123
QTextDocument textDocument;
textDocument.setHtml(html);
textDocument.print(&printer);
-
本節還演示瞭如何給一個QStringList進行分頁打印. 寫了一個函數, 根據高度進行分頁
12
void
PrintWindow::paginate(QPainter *painter, QList<QStringList> *pages,
const
QStringList &entries)
- 在例子中函數 int PrintWindow::entryHeight(QPainter *painter, const QString &entry) 計算每個條目的高度 其使用 QPainter::boundingRect() 計算垂直高度.
- 通過QPrintDialog, 用戶可以設定拷貝次數, 打印範圍, 請求頁面順序(順序還是反序)
- 可通過調用QPrintDialog::setEnabledOptions() 來確定哪些選項不能由用戶設定