来源:http://blog.sina.com.cn/s/blog_638ea1960101eya5.html
Chapter 1: Getting Started
- 几个命令1234567
// 创建了proj文件
qmake -project
// 创建makefile
qmake hello.pro
// 创建vs版本
qmake -tp vc hello.pro
// 然后在vs中编译
Chapter 2: Creating Dialogs
- 对话框程序一般用于设置程序的选项和选择.
- 类中 Q_OBJECT 宏表示要定义singnal和slot, signal和slot都是宏
- Qt::CaseSensitivity 为枚举类型, 可取值Qt::CaseSensitive 和 Qt::CaseInsensitive, 表示匹配的灵敏度
- Qt最重要的模块为 QtCore, QtGui, QtNetwork, QtOpenGL, QtScript, QtSql, QtSvg, QtXml
- 在比较大的程序中, 头文件中包含其他的大头文件, 不是合适的做法, 所以在头文件中不要include QtGui
- tr函数可以用于转换该字符串至其他语言.
- label文本中的快捷键如&w,则按下alt+w则该label的buddy widget获得焦点.
- addStretch 就像在该处增加了弹簧一样显示空白
- slot和普通的C++成员函数相同, 可以是虚拟的, 可以被重载, 可以为公有, 保护或者私有函数. 可以直接调用该函数. 参数可以为任何类型. 唯一不同的就是当信号发出的时候就会自动调用该slot函数
- signal和slot
- 一个signal可以连接多个slot
- 多个signal可以连接一个slot
- 一个signal可以连接另一个signal12
// 当第一个信号发出的时候, 就会发出第二个信号
connect(lineEdit, SIGNAL(textChanged(
const
QString &)),
this
, SIGNAL(updateRecord(
const
QString &)));
- 可以移除连接
1 disconnect(lcd, SIGNAL(overflow()),
this
, SLOT(handleMathError()));
- signal和slot必须含有相同的参数和次序. 如果signal的参数多于slot的参数数, 多余的参数则会抛弃.
- 在connect中, 参数不要写出名称, 只需写出类型即可.
- signal和slot在QObject中实现, 不仅仅局限于GUI程序. 可以为任意QObject派生类所使用.
- Qt的Meta-Object系统
- 该系统提供了两个关键服务: signals-slots 和 introspection(反省), introspection的功能是实现signals-slots的必要部分.
- 同时允许应用程序员在运行时期得到关于QObject派生类的"meta-information", 其包含该对象支持的signals和slots列表以及类名称.
- 该机制支持属性(Designer扩展的)和文本翻译(国际化), 并且为QtScript模块的基础, 可动态添加属性.
- Qt通过提供分离的工具moc来实现该meta-object系统.
- 该机制的作用如下:
- Q_OBJECT宏声明一些introspection函数, 这些函数必须在每个QObject派生类中实现: metaObject(), tr(), qt_metacall(),以及其他
- moc工具生成Q_OBJECT所声明函数和所有信号的实现
- QObject的成员函数如connect和disconncet使用这些introspection函数来完成工作.
- 注意避免signal和slot的无限循环问题
- 无论是手写代码还是用Qt designer, 创建对话框都拥有以下相同的基本步骤:
- 创建和初始化子widget
- 将子widget放入layout中
- 设置tab次序
- 建立signal-slot连接
- 实现对话框自定义slot
- ui文件生成的c++文件里所生成的UI类, 声明了该form所有子widget和layout的成员变量. setupUi()函数初始化该form.
- 为了增加一些功能, 创建一个新类, 从QDialog和Ui::GoToCellDialog派生. 然后实现缺省的功能
- Qt提供了三个内建的validator类: QIntValidator, QDoubleValidator, 和 QRegExpValidator
- QRegExpValidator 可以设置其父对象, 这样就无需担心删除问题, 因为父对象的删除就必然要删除其子对象, 并在屏幕删除该对象
- slot accept()函数将dialog的返回值设置为QDialog::Accepted()(等于1). reject则设置返回值为QDialog::Rejected()(等于0)
- QLineEdit::hasAcceptableInput() 用验证器验证输入内容是否符合要求
- QDialogButtonBox --- 一个widget, 含有指定的按钮并使用正确的方式显示, 拖动Button Box widget至form
1 buttonBox->button(QDialogButtonBox::Ok)->setEnabled(
false
);
- 最常用的变形对话框为扩展对话框和多页面对话框.
- 相同的widget由于其内容不同, 则大小有可能不同, 如需设置相同大小则需要增加代码:12
primaryColumnCombo->setMinimumSize(secondaryColumnCombo->sizeHint());
// secondaryColumnCombo的内容为None, 比PrimaryColumnCombo的内容要多, 所以重新设置
- 多页面对话框
- QTabWidget --- 提供tab bar控制其内置的QStackedWidget
- QListWidget 和 QStackedWidget 配合使用, QListWidget的当前条目显示哪个QStackedWidget显示
- QListWidget::currentRowChanged() 信号连至 QStackedWidget::setCurrentIndex() 槽
- QTreeWidget 和 QStackedWidget 类似QListWidget
- 动态对话框
- 是指用Qt Designer创建的.ui文件在运行期间创建的对话框. 而不是用.ui文件生成的C++代码创建的对话框.
- 我们可以在运行期间使用QUiLoader类加载ui文件123456
QUiLoader uiLoader;
QFile file(
"sortdialog.ui"
);
QWidget *sortDialog = uiLoader.load(&file);
if
(sortDialog) {
...
}
- 我们可以使用QObject::findChild()访问该form的子widget12345
QComboBox *primaryColumnCombo =
sortDialog->findChild(
"primaryColumnCombo"
);
if
(primaryColumnCombo) {
...
}
- QUiLoader 位于特定的库里, 需要增加配置: CONFIG += uitools
- 内置的widget和对话框类
- 按钮: QPushButton, QToolButton, QCheckBox, QRadioButton
- 单页容器: QGroupBox, QFrame
- 多页容器: QTabWidget, QToolBox
- 显示条目: QListView, QTreeView, QTabView
- 显示: QLabel, QLCDNumber, QProgress, QTextBrowser
- 输入: QSpinBox, QDoubleSpinBox, QComboBox, QDateEdit, QTimeEdit, QDateTimeEdit, QScrollBar, QSlider, QTextEdit, QLineEdit, QDial
- 反馈对话框: QInputDialog, QProgressDialog, QMessageBox, QErrorMessage
- 颜色和字体对话框: QColorDialog, QFontDialog
- 文件和打印对话框: QPageSetupDialog, QFileDialog, QPrintDialog
- 滚动条QScrollBar的基类为 QAbstractScrollArea
- Qt提供富文本(rich text), 支持多格式文本
- QLabel支持纯文本, HTML, 图像
- QTextBrowser为只读QTextEdit, 可支持带格式文本, 相对于QLabel, 可以用于显示大量的文本内容, 提供滚动条, 键盘和鼠标可以控制浏览.
- QLineEditor支持validator, QTextEditor为QAbstractScrollArea的派生类, 可以输入大量的文本. 可以设置输入纯文本还是富文本(rich text)
- QLineEditor和QTextEditor都和剪贴板相关联
Chapter 3 Creating Main Windows
3.1 Subclassing QMainWindow(从QMainWindow 派生类)
- closeEvent是QWidget的虚函数, 当关闭窗口时自动调用, 在派生类中可以重新实现该函数.
- Qt 应用程序使用图像的方法:
- 保存图像至文件, 运行期间加载
- 在源代码中包含XPM文件(XPM文件也是有效的C++文件)
- 使用Qt资源机制
- 如果需要使用资源系统, 我们必须创建资源文件, 并在.pro文件中添加一行标识资源文件: RESOURCES = spreadsheet.qrc
- 资源文件仅仅是简单的XML格式
<</code>
RCC
>
<</code>
qresource
>
<</code>
file
>images/icon.png</</code>
file
>
...
<</code>
file
>images/gotocell.png</</code>
file
>
</</code>
qresource
>
</</code>
RCC
>
- 需要使用前缀 :/, 如 :/images/icon.png
3.2 Creating Menus and Toolbars
- 在Qt中创建菜单和工具条有以下三个步骤:
- 创建和设置Action
- 创建菜单和并给他们放置action
- 创建工具条并给他们放置action
- QAbstractItemView:: selectAll()
- QTableView::setShowGrid(bool)
- QMainWindow::menuBar () --- 第一次调用则创建一个菜单条
- widget增加右键菜单的方法:
- 首先addAction, 而后调用setContextMenuPolicy(Qt::ActionsContextMenu); 设置关联菜单
- 是重载QWidget::contextMenuEvent函数, 然后调用exec()实现
- QMainWindow::addToolBar() --- 增加工具条
3.3 Setting Up the Status Bar
- QMainWindow::statusBar ()函数得到其指针, 而后可用 addWidget() 添加该状态栏
- QStatusBar::addWidget() 第二个参数设置为1表示拉伸
3.4 Implementing the File Menu
- QMessageBox::warning() --- 警告对话框, 还有 information(), question(), critical()
- QFileDialog::getOpenFileName() 打开文件对话框. QFileDialog::getSaveFileName 保存文件对话框, QFileDialog::DontConfirmOverwrite()
- 文件对话框的窗口会出现其父窗口的左上角, 并共享其父窗口的任务条(taskbar entry)
- QWidget的close() slot会调用closeEvent()
- event->ignore(); // 忽略该事件
- event->accept(); // 表示接受该事件
- 我们可以通过设置QApplication's quitOnLastWindowClosed 属性为假来禁止程序关闭, 直至调用QApplication::quit()
- QFileInfo(fullFileName).fileName(); 得到文件名
- Qt列表容器的prepend()函数用于列表, 列表类的方法之一, 作用是插入列表的开头
- QVariant类型可以保存许多C++和Qt类型的数据, 可将该数据保存至 Action 的Data中
- QObject::sender() 该函数可以在slot中得到sender object的指针, 对于多个signal连接至一个slot时很有用.
3.5 Using Dialogs
- 通过signal和slot 对查找对话框和主程序进行互动
- QWidget可以通过方法 raise() 和 activateWindow() 来使得该窗口激活在屏幕的最前方
- 非模式对话框使用show()来显示, 模式对话框则使用 exec()来显示
- QTableWidgetSelectionRan
ge --- 存储表格选择区域的左上和右下所在行列 - About对话框 --- QMessageBox::about()
- 比较少的做法: QMessageBox 或 QFileDialog可以像正常的widget那样创建, 而后调用exec执行.
3.6 Storing Settings
- QSettings在不同的平台中, 存储在不同的地方. Windows程序则存储在系统注册表里.
- 其构造函数参数含组织名称和应用程序名称, 方便其查找和写入
- QSettings 存储类 key-value对的设定, key类似文件系统路径, subkey则类似路径语法(如findDialog/matchCase)
- 可使用beginGroup()和endGroup()
<</code>
div
class
=
"cnblogs_Highlighter"
>
class
=
"brush:cpp;gutter:true;"
>settings.beginGroup(
"findDialog"
);
settings.setValue(
"matchCase"
, caseCheckBox->isChecked());
settings.setValue(
"searchBackward"
, backwardCheckBox->isChecked());
settings.endGroup();
</</code>
div
>
- QSetting的value则可以为int, bool, double, QString, QStringList, 或者任意QVariant支持的类型.
3.9 Multiple Documents
- 修改程序为多文档程序
- File|New: 创建一个新的空文档窗口, 而不是重新使用已存的主窗口
- File|Close: 关闭当前主窗口
- File|Exit: 关闭所有窗口
- 给widget设置属性Qt::WA_DeleteOnClose, 当关闭的时候删除该widget在内存中的资源, 节省内存. ---> setAttribute(Qt::WA_DeleteOnClose);
- foreach (QWidget *win, QApplication::topLevelWidgets()); // 可以用来遍历应用程序的所有窗口
3.10 Splash Screens
- QSplashScreen实现Splash Screen效果
- QSplashScreen在主窗口显示之前显示一张图像, 并在图像上写信息用来告知用户应用程序的初始化过程.
- splash代码一般位于main()函数中, 在调用QApplication::exec()之前12345678910111213141516
QSplashScreen *splash =
new
QSplashScreen;
splash->setPixmap(QPixmap(
":/images/splash.png"
));
splash->show();
Qt::Alignment topRight = Qt::AlignRight | Qt::AlignTop;
splash->showMessage(QObject::tr(
"Setting up the main window..."
),
topRight, Qt::white);
... ...
splash->showMessage(QObject::tr(
"Loading modules..."
),
topRight, Qt::white);
... ... splash->showMessage(QObject::tr( "Establishing connections..."
),
topRight, Qt::white);
... ... splash->finish(&mainWin); delete splash;
return app.exec();
Chapter 4 Implementing Application Functionality
4.1 The Central Widget
- QMainWindow的中心区域可以是任意类型的widget.
- 使用标准的Qt Widget
- 使用自定义widget
- 使用带layout管理器的简单QWidget
- 使用splitter(分割器) --- QSplitter
- 使用MDI区域 --- QMdiArea widget
- 本例使用QTabelView的派生类用于中间widget. 其支持了许多spreadsheet操作, 但不支持剪贴板操作, 不理解spreadsheet公式
4.2 Subclassing QTableWidget
- QTabelWidget为一有效的网格来显示两维稀疏数组. 随着用户的滚动显示需要的网格. 当用户输入内容时, 会自动创建一个QTabelWidgetItem保存文本.
- 另外一个更多功能的table是 QicsTable, 见 http://www.ics.com/
- QTabelWidgetItem 不是widget, 为纯数据类, Cell是其派生类.
- 一般而言, QTabelWidget在一空白单元格输入文本时, 会自动创建一QTabelWidgetItem来保存该文本.
- 在本spreadsheet中, 我们希望创建Cell对象来保存文本, 则通过在构造函数中调用setItemPrototype来实现,
- 在内部, 每当需创建一个新的条目时, QTabelWidget就会克隆被当作prototype的条目
- setSelectionMode(ContiguousSelection); // 允许矩形选择内容
- setHorizontalHeaderItem(i, item); // 设置第一行的头内容
- 将数据存储为条目的方式在QListWidget和QTreeWidget中也运用到, 操作为QListWidgetItems 和 QTreeWidgetItem
- item类可以作为数据控制器, 如QTableWidgetItem则可以保存一些属性, 如字符串, 字体, 颜色和图标, 以及返回QTableWidget的指针. 也保存QVariant数据
- Qt中保存自定义数据的做法是使用 setData()和QVariant实现, 如果需要void指针, 则通过派生item类, 并增加void指针成员变量
- QTableWidget::setItem 设置条目
4.3 Loading and Saving
- 使用QFile和QDataStream实现文件操作, 跨平台的二进制I/O操作1234567891011
QFile file(fileName);
if
(!file.open(QIODevice::WriteOnly)) {
... ...
}
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_3);
out << quint32(MagicNumber);
QApplication::setOverrideCursor(Qt::WaitCursor);
out << .......
QApplication::restoreOverrideCursor();
- 整数类型: qint8, quint8, qint16, quint16, qint32, quint32, qint64, quint64
- QDataStream 使用二进制近期的大多数版本, 可以明确指定QDataStream的版本, 解决读取的兼容问题
- QDataStream不仅可以用于QFile, 也可用于QBuffer, QProcess, QTopSocket, QUdpSocket, QSalSocket
- QTextStream可以用来读取写入文本文件
4.3 Implementing the Edit Menu
- QApplication::clipboard()->setText(str); // 设置剪贴板内容
4.5 Implementing the Other Menus
- qStableSort() 函数算法进行排序
4.6 Subclassing QTableWidgetItema
- QTableWidgetItem可以保存数据, 每个数据都有两个值, 一个是编辑的值, 一个是显示的值, 大部分时候相同, 但是当其为公式的时候可能不同
- Cell没有提供text()函数,由QTableWidgetItem解决, 等同于调用 data(Qt::DisplayRole).toString();
- value() 函数
- 如果有 单引号"'", 则表示为字符串
- 如有"=", 则计算
- 否则直接转换为double
- 如果都不是就直接输出字符串
Chapter 5: Creating Custom Widgets
- 创建自定义widget的方法: 现有Qt Widget派生或者直接从QWidget派生生成
5.1 Customizing Qt Widgets
- 本章节例子: 让SpinBox实现十六进制数的显示和使用
- 类HexSpinBox继承自类QSpinBox, 构造函数, 重写基类函数validate, valueFromText, textFromValue, 增加私有成员QRegExpValidator *validator;
- 输入文本的验证
- 可能的三个返回值: Invalid, Intermediate, Acceptable1234
QValidator::State HexSpinBox::validate(QString &text,
int
&pos)
const
{
return
validator->validate(text, pos);
}
- QSpinBox在用户按下spin box的向上和向下箭头时, 调用该函数更新编辑区部分1234
QString HexSpinBox::textFromValue(
int
value)
const
{
return
QString::number(value, 16).toUpper();
}
- 当用户在编辑区输入一个值并回车的时候, 调用该函数. 执行字符串到值的转换12345
int
HexSpinBox::valueFromText(
const
QString &text)
const
{
bool
ok;
return
text.toInt(&ok, 16);
}
- 可能的三个返回值: Invalid, Intermediate, Acceptable
- 实现自定义widget的步骤
- 选择合适的Qt Widget
- 派生类
- 重写部分虚函数改变其行为.
- 如果我们仅仅想改变一个widget的外观, 我们可以应用一个style 表单或者实现一个自定义style, 而不是派生该widget类.
5.2 Subclassing QWidget
- 在Qt Designer中设计自定义widget
- 使用"Widget"创建一个新的form
- 增加需要的widget至该form, 并布局这些widget
- 设置signals和slots的连接
- 如果signals和slots还不能实现所有的行为, 则从QWidget和该uic生成类派生一个新类, 完成必要的代码实现这些行为
- 当然, 也可使用纯代码实现自定义widget, 不管是什么方法, 都必须从QWidget派生该类.
- 从QWidget派生类, 并重实现一些事件处理函数来绘制该widget, 对鼠标点击做出反应.
- 这个方法可以让我们完全自由的定义和控制该widget的外观和行为.
- 代码中使用 Q_PROPERTY() 宏声明自定义属性. 每个属性有一数据类型, 读取函数, 可选的写入函数
- 在Qt Designer中, 可以在 property editor 中自定义属性.
- QRgb 为32位无符号整数, 可使用qRgba和qRgb来返回该值.
- Qt 提供了两个类型保存颜色: QRgb和QColor, QRgb只用于QImage, 用来保存32位像素数据. QColor用于许多有用的函数保存颜色.除了QImage.
- size policy用来告知layout 系统是否可以拉伸和收缩.
- QSizePolicy::Minimum 用来告诉layout管理器该widget的size hint是其最小值. 即可以拉伸, 但不能收缩至比该值更小的大小.
- QWidget::updateGeometry() 用于告知包含该widget的layout, 该widget的size hint发生了变化. 而后该layout则会自动适应新的size hint
- 产生paint 事件的几种情况
- 第一次显示该widget
- 该widget的大小发生变化
- 被其他窗口覆盖, 且重新显示被覆盖的地方.
- 我们也可以通过调用 QWidget::update()和QWidget::repaint()函数使得paint事件发生.
- repaint() 需要立即重绘
- update() 则将绘制事件放入处理进程列表.
- 每个widget都有一个调色板用于指定使用哪个颜色. 缺省设置, 一个widget的调色板都是采用操作系统的颜色方案.
- 一个widget的调色板由三个颜色组组成: 活跃的, 非活跃的, 不能够使用的.
- 活跃组用于当前活跃窗口
- 非活跃组用于其他窗口
- disable使用组用于任意窗口的disable widget
- QWidget::palette() 返回该widget的调色板 QPalette对象, 颜色组则为特定的枚举类型 QPalette::ColorGroup.
- 调色板则可以根据不同的role得到其笔刷brush, 如QPalette::foreground()
- 一个widget的调色板由三个颜色组组成: 活跃的, 非活跃的, 不能够使用的.
- mouseMoveEvent 事件在按下鼠标的时候移动鼠标才产生, 除非你调用了QWidget::setMouseTracking() 函数
- Qt::WA_StaticContents 属性, 该属性用于告知Qt该widget的内容在大小变化时不需要改变, 内容始终对应在widget的左上角.
- 每当大小变化是, paint 事件只对原来未显示的内容起作用.
5.3 Integrating Custom Widgets with Qt Designer
- 在Qt Designer中使用自定义widget, 有两种方法:
- "提升"方法
- 插件方式
- 在Widget上直接提升自定义widget的方法
- 创建基Widget在Qt Designer上
- 右键该widget, 选择 Promote to Custom Widget
- 填充弹出对话框的类名和头文件名称
- 而后该uic生成的代码会包含上面填充的头文件, 而非基Widget的头文件. 而在Qt Designer, 显示的还是基Widget, 修改基Widget的属性.
- 缺点:
- 不能够在Qt Designer修改该自定义widget的自定义属性
- 不能够显示该Widget, 而是显示其基Widget
- 插件方式, 则生成一插件库让Qt Designer在运行期间动态加载123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
#include
class
IconEditorPlugin :
public
QObject,
public
QDesignerCustomWidgetInt
erface {
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetInt
erface) public
:
IconEditorPlugin(QObject *parent = 0);
QString name()
const
;
QString includeFile()
const
;
QString group()
const
;
QIcon icon()
const
;
QString toolTip()
const
;
QString whatsThis()
const
;
bool
isContainer()
const
;
QWidget *createWidget(QWidget *parent);
};
IconEditorPlugin::IconEditorPlugin(QObject *parent)
: QObject(parent)
{
}
// 返回插件的名称
QString IconEditorPlugin::name()
const
{
return
"IconEditor"
;
}
// 该函数用于返回自定义widget的头文件名称. 会在uic工具生成的代码中包含该头文件
QString IconEditorPlugin::includeFile()
const
{
return
"iconeditor.h"
;
}
// 用于Qt Designer的widget分组
QString IconEditorPlugin::group()
const
{
return
tr(
"Image Manipulation Widgets"
);
}
// 返回在Qt Designer用于表示该自定义widget的图标
QIcon IconEditorPlugin::icon()
const
{
return
QIcon(
":/images/iconeditor.png"
);
}
QString IconEditorPlugin::toolTip()
const
{
return
tr(
"An icon editor widget"
);
}
// 用于"What's This?"
QString IconEditorPlugin::whatsThis()
const
{
return
tr(
"This widget is presented in Chapter 5 of C++ GUI "
"Programming with Qt 4
as an example of a custom Qt ""widget."
);
}
// 该自定义widget是否为容器
bool
IconEditorPlugin::isContainer()
const
{
return
false
;
}
// Qt Designer 通过该函数创建该widget的实例
QWidget *IconEditorPlugin::createWidget(QWidget *parent)
{
return
new
IconEditor(parent);
}
// 使用该宏来使得其该插件可用于Qt Designer
// 第一个参数为插件的名称, 第二个则为实现的类
Q_EXPORT_PLUGIN2(iconeditorplugin, IconEditorPlugin)
- 工程文件:123456789
TEMPLATE
= lib CONFIG
+= designer plugin release HEADERS
= ../iconeditor/iconeditor.h \ iconeditorplugin.h
SOURCES
= ../iconeditor/iconeditor.cpp \ iconeditorplugin.cpp
RESOURCES
= iconeditorplugin.qrc DESTDIR
= $$[QT_INSTALL_PLUGINS]/designer // QT_INSTALL_PLUGINS --- Qt的插件安装目录
- 工程文件:
- 可以使用QDesignerCustomWidgetCol
lectionInterface将多个自定义widget集成至一个plugin中
5.4 Double Buffering
- 双缓存是GUI程序的一个技术, 有两个部分组成: 在屏幕背后渲染一个widget的像素映射内容, 而后将该像素映射拷贝至屏幕. 这个技术可以避免屏幕出现闪烁.
- 当一个widget的渲染很复杂以及需要重复绘制时, 可以使用双缓存技术.
- 长时间保存像素映射(pixmap), 随时准备下一个paint事件, 当接收到paint事件时, 拷贝该pixmap至widget
- QPointF 是一个浮点版本的QPoint
- QPixmap类型变量, 可表示绘制到widget的pixmap. 先在屏幕后将像素写到pixmap, 然后拷贝该pixmap至屏幕的widget
- QWidget::setBackgroundRole() 设置填充背景的方式, 本例使用调色板的"dark"成分来替换"window"成分. 用于填充任何新的增加窗口的像素, 在paint事件之前.
- 注意还需要调用 setAutoFillBackground(true); 来启动该机制.
- 缺省情况, 子widget都是继承父widget的背景
- QSizePolicy::Expanding 表示widget可以拉伸收缩. QSizePolicy::Preferred 表示widget尽量选择size hint的大小, 可以收缩至 size hint的最小值, 拉伸至无限大小
- QtWidget::setFocusPolicy(Qt::StrongFocus) 表示该widget通过鼠标点击或者键击tab接受焦点.
- 本例接受焦点后, 该widget会接受键盘事件, 本例+表示放大, -表示缩小. 向上表示向上滚动等等
- QtWidget::adjustSize() 表示设置为该widget的size hint的大小
- 本例 setPlotSetting()表示设置一个PlotSetting, 每当放大一次该plot, 则调用PlotSetting构造函数构建一个新的缩放.
- 调用refreshPixmap() 用于更新显示内容. 而不是使用update(), 先更新QPixmap至最新, 而后调用update()拷贝pixmap至widget
- 当widget具有焦点时, 焦点矩形的绘制:
- painter调用drawPrimitive()方法, 第一参数为QStyle::PE_FrameFocusRect, 第二参数为QStyleOptionFocusRect对象: painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
- 第二个参数调用initForm初始化, 该函数的参数为widget12
QStyleOptionFocusRect option;
option.initFrom(
this
);
- 设置QStyleOptionFocusRect的背景颜色
1 option.backgroundColor = palette().dark().color();
- 当我们想要使用当前style(风格)绘制时, 我们可以直接调用一个QStyle函数: style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &painter, this);
- style()返回用于绘制该widget的style, 在Qt中, 一个widget的style都是QStyle的派生类.
- 内置的style包括: QWindowsStyle, QWindowsXPStyle, QWindowsVistaStyle, QMotifStyle, QCDEStyle, QMacStyle, QPlastiqueStyle, QCleanlooksStyle
- 每个style都重新实现了QStyle的虚函数, 通过枚举style在不同的平台上使用正确的style.
- QStylePainter的drawPrimitive()则会调用QStyle内的同名函数绘制"基本元素", 如panels, buttons, forcus rectangle.
- 一般而言, 一个应用程序中的widget style应当与所有的widget的style相同, 但是可以使用QWidget::setStyle()来改变每个widget.
- 通过实现QStyle的派生类, 你可以定义一个自定义style.
- 所有的Qt内置widget都是依赖于QStyle来绘制其本身. 这就是为何这些widget在不同的平台就像其平台自身的widget一样.
- 对于自定义widget, 通常实现QStyle来绘制其自身, 或者使用内置的Qt Widget作为widget来实现与Qt内置widget一样的style>
- 本例的focus rectangle则使用QStyle, 而两个放大缩小按钮则使用Qt 内置widget
- 大小变化则会引发resizeEvent函数的调用
- Qt 提供了两个机制用于控制光标的形状
- QWidget::setCursor() 设置当光标在widget上方时的形状, 如果没有对应的光标, 则使用其父widget的光标设置. 顶层widget缺省使用 箭头光标
- QApplication::setOverrideCursor() 设置在整个应用程序中使用的光标形状, 覆盖各个widget的光标. 直到调用restoreOverrideCursor() 恢复
- QScrollArea 会自动处理鼠标滚轮事件
- QPainter的initFrom()函数用于初始化painter的笔刷, 背景, 字体等参数, 这些参数与给定Widget的设置相同. 由 QWidget 自动调用
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.setHorizontalScrollBarPo
licy(Qt::ScrollBarAlwaysOn); scrollArea.setVerticalScrollBarPoli
cy(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 --- 用于强制转换.
- 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 + Home123456789101112
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);
}
- 如果想要修改该功能, 则重写QWidget::event()函数
- 实现快捷键与Action, 处理函数的绑定1234567891011
goToBeginningOfLineActio
n = new
QAction(tr(
"Go to Beginning of Line"
),
this
);
goToBeginningOfLineActio
n->setShortcut(tr( "Home"
));
// 连接
connect(goToBeginningOfLineActio
n, SIGNAL(activated()), editor, SLOT(goToBeginningOfLine()));
goToBeginningOfDocumentA
ction = new
QAction(tr(
"Go to Beginning of Document"
),
this
);
goToBeginningOfDocumentA
ction->setShortcut(tr( "Ctrl+Home"
));
connect(goToBeginningOfDocumentA
ction, 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
(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 表示为其缩放因子 - 使用 ItemIgnoresTransformatio
ns 标志可以忽略缩放, 不会跟随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() 来确定哪些选项不能由用户设定
- QMainWindow的中心区域可以是任意类型的widget.