Chapter6-Chapter8 Chapter 6

Qt 笔记 Chapter6-Chapter8 Chapter 6 Layout ManagementQt提供的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 LayoutsQStackedLayout 类对一系列子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 SplittersQSplitter的子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")); 通过调用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 ToolbarsDock 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(); } 确保编辑器选择了文本才允许这两个菜单项可以使用 ?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 --- 用于强制转换. QTextCursor::hasSelection () --- 返回当前文本光标是否选择了文本 QAction::setChecked() --- 设置选中该Action QScintilla --- 代码编辑的widget 每个子窗口设置 Qt::WA_DeleteOnClose 属性, 当关闭的时候删除该窗口, 以免内存泄漏 Chapter 7 Event Processing7.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(event); if (keyEvent->key() == Qt::Key_Tab) { insertAtCurrentPosition('\t'); return true; } } return QWidget::event(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 FilterQt的事件模型一个非常强大的功能就是一个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(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 GraphicsQt 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 ViewQPainter对于绘制自定义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 *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、付费专栏及课程。

余额充值