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() 来确定哪些选项不能由用户设定
分类:
程序