Qt学习笔记

来源:http://blog.sina.com.cn/s/blog_638ea1960101eya5.html


Chapter 1: Getting Started

  • 几个命令
    ?
    1
    2
    3
    4
    5
    6
    7
    // 创建了proj文件
    qmake -project
    // 创建makefile
    qmake hello.pro
    // 创建vs版本
    qmake -tp vc hello.pro
    // 然后在vs中编译

Chapter 2: Creating Dialogs

  1. 对话框程序一般用于设置程序的选项和选择.
  2. 类中 Q_OBJECT 宏表示要定义singnal和slot, signal和slot都是宏
  3. Qt::CaseSensitivity 为枚举类型, 可取值Qt::CaseSensitive 和 Qt::CaseInsensitive, 表示匹配的灵敏度
  4. Qt最重要的模块为 QtCore, QtGui, QtNetwork, QtOpenGL, QtScript, QtSql, QtSvg, QtXml
  5. 在比较大的程序中, 头文件中包含其他的大头文件, 不是合适的做法, 所以在头文件中不要include QtGui
  6. tr函数可以用于转换该字符串至其他语言.
  7. label文本中的快捷键如&w,则按下alt+w则该label的buddy widget获得焦点.
  8. addStretch 就像在该处增加了弹簧一样显示空白
  9. slot和普通的C++成员函数相同, 可以是虚拟的, 可以被重载, 可以为公有, 保护或者私有函数. 可以直接调用该函数. 参数可以为任何类型. 唯一不同的就是当信号发出的时候就会自动调用该slot函数
  10. signal和slot
    • 一个signal可以连接多个slot
    • 多个signal可以连接一个slot
    • 一个signal可以连接另一个signal
      ?
      1
      2
      // 当第一个信号发出的时候, 就会发出第二个信号
      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派生类所使用.
  11. 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函数来完成工作.
  12. 注意避免signal和slot的无限循环问题
  13. 无论是手写代码还是用Qt designer, 创建对话框都拥有以下相同的基本步骤:
    • 创建和初始化子widget
    • 将子widget放入layout中
    • 设置tab次序
    • 建立signal-slot连接
    • 实现对话框自定义slot
  14. ui文件生成的c++文件里所生成的UI类, 声明了该form所有子widget和layout的成员变量. setupUi()函数初始化该form.
  15. 为了增加一些功能, 创建一个新类, 从QDialog和Ui::GoToCellDialog派生. 然后实现缺省的功能
  16. Qt提供了三个内建的validator类: QIntValidatorQDoubleValidator, 和 QRegExpValidator
    • QRegExpValidator 可以设置其父对象, 这样就无需担心删除问题, 因为父对象的删除就必然要删除其子对象, 并在屏幕删除该对象
  17. slot accept()函数将dialog的返回值设置为QDialog::Accepted()(等于1). reject则设置返回值为QDialog::Rejected()(等于0)
  18. QLineEdit::hasAcceptableInput() 用验证器验证输入内容是否符合要求
  19. QDialogButtonBox --- 一个widget, 含有指定的按钮并使用正确的方式显示, 拖动Button Box widget至form
    ?
    1 buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
  20. 最常用的变形对话框为扩展对话框和多页面对话框.
  21. 相同的widget由于其内容不同, 则大小有可能不同, 如需设置相同大小则需要增加代码:
    ?
    1
    2
    primaryColumnCombo->setMinimumSize(secondaryColumnCombo->sizeHint());
    // secondaryColumnCombo的内容为None, 比PrimaryColumnCombo的内容要多, 所以重新设置
  22. 多页面对话框
    • QTabWidget --- 提供tab bar控制其内置的QStackedWidget
    • QListWidget 和 QStackedWidget 配合使用, QListWidget的当前条目显示哪个QStackedWidget显示
      • QListWidget::currentRowChanged() 信号连至 QStackedWidget::setCurrentIndex() 槽
    • QTreeWidget 和 QStackedWidget 类似QListWidget
  23. 动态对话框
    • 是指用Qt Designer创建的.ui文件在运行期间创建的对话框. 而不是用.ui文件生成的C++代码创建的对话框.
    • 我们可以在运行期间使用QUiLoader类加载ui文件
      ?
      1
      2
      3
      4
      5
      6
      QUiLoader uiLoader;
      QFile file("sortdialog.ui");
      QWidget *sortDialog = uiLoader.load(&file);
      if (sortDialog) {
          ...
      }
    • 我们可以使用QObject::findChild()访问该form的子widget
      ?
      1
      2
      3
      4
      5
      QComboBox *primaryColumnCombo =
          sortDialog->findChild("primaryColumnCombo");
      if (primaryColumnCombo) {
          ...
      }
    • QUiLoader 位于特定的库里, 需要增加配置: CONFIG += uitools
  24. 内置的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
  25. 滚动条QScrollBar的基类为 QAbstractScrollArea
  26. Qt提供富文本(rich text), 支持多格式文本
  27. QLabel支持纯文本, HTML, 图像
  28. QTextBrowser为只读QTextEdit, 可支持带格式文本, 相对于QLabel, 可以用于显示大量的文本内容, 提供滚动条, 键盘和鼠标可以控制浏览.
  29. QLineEditor支持validator, QTextEditor为QAbstractScrollArea的派生类, 可以输入大量的文本. 可以设置输入纯文本还是富文本(rich text)
  30. QLineEditor和QTextEditor都和剪贴板相关联

Chapter 3 Creating Main Windows

3.1 Subclassing QMainWindow(从QMainWindow 派生类)

  • closeEvent是QWidget的虚函数, 当关闭窗口时自动调用, 在派生类中可以重新实现该函数.
  • Qt 应用程序使用图像的方法:
    • 保存图像至文件, 运行期间加载
    • 在源代码中包含XPM文件(XPM文件也是有效的C++文件)
    • 使用Qt资源机制
  • 如果需要使用资源系统, 我们必须创建资源文件, 并在.pro文件中添加一行标识资源文件: RESOURCES = spreadsheet.qrc
  • 资源文件仅仅是简单的XML格式
    ?
    <RCC>
    <qresource>
        <file>images/icon.pngfile>
        ...
        <file>images/gotocell.pngfile>
    qresource>
    RCC>
     
  • 需要使用前缀 :/, 如 :/images/icon.png

3.2 Creating Menus and Toolbars

  • 在Qt中创建菜单和工具条有以下三个步骤:
    1. 创建和设置Action
    2. 创建菜单和并给他们放置action
    3. 创建工具条并给他们放置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()来显示
  • QTableWidgetSelectionRange --- 存储表格选择区域的左上和右下所在行列
  • About对话框 --- QMessageBox::about()
  • 比较少的做法: QMessageBox 或 QFileDialog可以像正常的widget那样创建, 而后调用exec执行.

3.6 Storing Settings

  • QSettings在不同的平台中, 存储在不同的地方. Windows程序则存储在系统注册表里.
    • 其构造函数参数含组织名称和应用程序名称, 方便其查找和写入
    • QSettings 存储类 key-value对的设定, key类似文件系统路径, subkey则类似路径语法(如findDialog/matchCase)
    • 可使用beginGroup()和endGroup()
      ?
      <div class="cnblogs_Highlighter">
      class="brush:cpp;gutter:true;">settings.beginGroup("findDialog");
      settings.setValue("matchCase", caseCheckBox->isChecked());
      settings.setValue("searchBackward", backwardCheckBox->isChecked());
      settings.endGroup();    
      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()之前
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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操作
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      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, Acceptable
        ?
        1
        2
        3
        4
        QValidator::State HexSpinBox::validate(QString &text, int &pos) const
        {
            return validator->validate(text, pos);
        }
      • QSpinBox在用户按下spin box的向上和向下箭头时, 调用该函数更新编辑区部分
        ?
        1
        2
        3
        4
        QString HexSpinBox::textFromValue(int value) const
        {
            return QString::number(value, 16).toUpper();
        }
      • 当用户在编辑区输入一个值并回车的时候, 调用该函数. 执行字符串到值的转换
        ?
        1
        2
        3
        4
        5
        int HexSpinBox::valueFromText(const QString &text) const
        {
            bool ok;
            return text.toInt(&ok, 16);
        }
    • 实现自定义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()
    • 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在运行期间动态加载
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      #include
       
      class IconEditorPlugin : public QObject,
                               public QDesignerCustomWidgetInterface
      {
          Q_OBJECT
          Q_INTERFACES(QDesignerCustomWidgetInterface)
       
      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)
      • 工程文件:
        ?
        1
        2
        3
        4
        5
        6
        7
        8
        9
        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的插件安装目录
    • 可以使用QDesignerCustomWidgetCollectionInterface将多个自定义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初始化, 该函数的参数为widget
        ?
        1
        2
        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设置固定大小
      • 使用绝对位置的缺点:
        1. 用户不能重置其大小
        2. 一些文本可能由于字体的变化或者语言的变化而被截去部分内容
        3. 在某些style中, widget可能会有不适当的大小
        4. 必须手工计算大小和位置, 乏味且容易出错, 维护困难
    • 手工layout, 位置仍然绝对, 但是大小可以适应窗口. 通过在 resizeEvent() 方法中实现该功能
    • layout 管理器方法, 考虑每个widget的size hint(该size hint依赖于widget的字体, 内容), 同时也考虑最小和最大大小.
      • 根据字体的变化, 内容的变化, 窗口大小的变化自动修正大小.
      • 三个最重要的layout类是QHBoxLayout, QVBoxLayout, QGridLayout, 这三者都为QLayout的派生类
      • 一个layout内的边缘和子widget之间的空格由当前widget的style决定. 也可以使用QLayout::setContentsMargins()和QLayout::setSpacing()来改变
      • QGridLayout的语法: layout->addWidget(widget, row, column, rowSpan, columnSpan);
      • addStretch() 用来告知layout 管理器填充该处的空白.
      • layout 管理器的优点:
        1. 添加或者移除一个widget, layout会自动修正以适应新的情况, 对hide()和show()也有同样的效果.
        2. 当子widget的size hint发生改变时, layout 管理器会自动修改以适应新的size hint
        3. 根据子widget的size hint和最小值来设置layout的最小值
      • 有时我们需要修改size policy和widget的size hint来实现我们需要的layout
      • size policy的值:
        1. Fixed --- 固定的layout, 不能拉伸和收缩. 使用size hint的大小
        2. Minimum --- 表示该widget的最小值就是size hint. 不能够收缩至比该值更小的值. 可以填充可用的空间给widget
        3. Maximum --- 表示该widget的最大值就是size hint, 该widget可以收缩至其最小值 minimum size hint
        4. Preferred --- 该widget的preferred值就是size hint, 在需要的时候可以收缩和拉伸
        5. Expanding --- 表示该widget可以收缩和拉伸, 但其最好选择拉伸
      • Expanding 和 Preferred的区别: 当一个form包含两者的widget, 该form大小变化时, 额外的空白处则给予Expanding widget, 而Preferred widget保持为其size hint的大小
      • Minimum, Expanding 和 Ignored这两个Size Policy不再经常使用, 后者忽略widget的size hint和最小值hint
      • 为了补充水平和垂直部分的size policy, 我们还设定了拉伸因子(strectch factor), 可以设置widget在水平或垂直方向的拉伸
      • 如果对一个widget不满意, 我们还可以派生该widget类, 重写其sizeHint()函数

    6.2 Stacked Layouts

    • QStackedLayout 类对一系列子widget布局, 或者"分页". 且每次只显示一个页面, 隐藏其他页面的内容.
      • Qt提供QStackedWidget类表示带内置QStackedLayout的QWidget.
    • 页数是从0开始, 设置当前页 setCurrentIndex, 得到一个子widget的页号则使用indexOf().
    • QListWidget可以和QStackedLayout配合使用.
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      listWidget = new QListWidget;
      listWidget->addItem(tr("Appearance"));
      listWidget->addItem(tr("Web Browser"));
      listWidget->addItem(tr("Mail & News"));
      listWidget->addItem(tr("Advanced"));
       
      stackedLayout = new QStackedLayout;
      stackedLayout->addWidget(appearancePage);
      stackedLayout->addWidget(webBrowserPage);
      stackedLayout->addWidget(mailAndNewsPage);
      stackedLayout->addWidget(advancedPage);
      connect(listWidget, SIGNAL(currentRowChanged(int)),
              stackedLayout, SLOT(setCurrentIndex(int)));
      ...
      listWidget->setCurrentRow(0);
      • currentRowChanged(int)信号发送给setCurrentIndex(int) slot 实现页面切换
      • setCurrentRow() 设置当前页面
    • Qt Designer实现分页
      • 用"dialog"模板或者"widget"模板创建新的form
      • 添加QListWidget和QStackedWidget
      • 填充每个页面的widget和layout
      • 水平方向布局这两个widget
      • signal和slot连接 currentRowChanged(int) --> setCurrentIndex(int)
      • 设置list widget的currentRow属性

    6.3 Splitters

    • QSplitter的子widget根据创建的顺序自动排列在一起. 相邻的widget之间有splitter bar. 下面是创建的代码
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      int main(int argc, char *argv[])
      {
          QApplication app(argc, argv);
       
          QTextEdit *editor1 = new QTextEdit;
          QTextEdit *editor2 = new QTextEdit;
          QTextEdit *editor3 = new QTextEdit;
       
          QSplitter splitter(Qt::Horizontal);
          splitter.addWidget(editor1);
          splitter.addWidget(editor2);
          splitter.addWidget(editor3);
          ...
          splitter.show();
          return app.exec();
      }
    • QSplitter 派生自QWidget, 像其他的widget一样使用.
    • MailClient的例子
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      MailClient::MailClient()
      {
          ...
          rightSplitter = new QSplitter(Qt::Vertical);
          rightSplitter->addWidget(messagesTreeWidget);
          rightSplitter->addWidget(textEdit);
          rightSplitter->setStretchFactor(1, 1);
       
          mainSplitter = new QSplitter(Qt::Horizontal);
          mainSplitter->addWidget(foldersTreeWidget);
          mainSplitter->addWidget(rightSplitter);
          mainSplitter->setStretchFactor(1, 1);
          setCentralWidget(mainSplitter);
       
          setWindowTitle(tr("Mail Client"));
          readSettings();
      }
      • setStretchFactor 设置拉伸因子, 缺省是随着大小变化, 各部分的比例不变, 第一个参数是以第一个widget为0的索引值, 第二个参数设置拉伸因子, 缺省为0
    • 保存设置
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      QSettings settings("Software Inc.", "Mail Client");
      settings.beginGroup("mainWindow");
      settings.setValue("geometry", saveGeometry());
      settings.setValue("mainSplitter", mainSplitter->saveState());
      settings.setValue("rightSplitter", rightSplitter->saveState());
      settings.endGroup();
      // 读取设置
      QSettings settings("Software Inc.", "Mail Client");
      settings.beginGroup("mainWindow");
      restoreGeometry(settings.value("geometry").toByteArray());
      mainSplitter->restoreState(
              settings.value("mainSplitter").toByteArray());
      rightSplitter->restoreState(
              settings.value("rightSplitter").toByteArray());
      settings.endGroup();

    6.4 Scrolling Areas

    • 如果需要使用滚动条, 最好使用QScrollArea而不是自己实现QScrollBar和滚动功能, 因为这样太复杂
      • 使用QScrollArea的方法是调用setWidget使得该widget成为QScrolllArea视口的子类. 访问视口, QScrollArea::viewport()
        ?
        1
        2
        3
        4
        5
        QScrollArea scrollArea;
        scrollArea.setWidget(iconEditor);
        scrollArea.viewport()->setBackgroundRole(QPalette::Dark);
        scrollArea.viewport()->setAutoFillBackground(true);
        scrollArea.setWindowTitle(QObject::tr("Icon Editor"));
    • 通过调用setWidgetResizable(true)告知该QScrollArea可以自动的重置widget的大小. 这样可以使用其size hint之外的空间
    • 缺省情况是当视口比widget更小的时候才显示滚动条, 如果想滚动条永远显示, 则使用以下代码:
      ?
      1
      2
      scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
      scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    • QScrollArea派生自QAbstractScrollArea, QTextEdit和QAbstractItemView的基类为QAbstractScrollBar.

    6.5 Dock Windows and Toolbars

    • Dock Window表示那些可以在QMainWindow Dock的窗口以及可以独立出来的窗口.
      • QMainWindow 提供了四个浮动区域, 上,下, 左, 右.
    • 每个dock window都有其标题条, 可通过QDockWidget::setFeatures() 设置其属性
    • QMainWindow::setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); // 上面的函数设置左上角区域属于左边的dock widget区域
    • 如何在一个QDockWidget包装一已存widget, 且插入右边的dock区域
      ?
      1
      2
      3
      4
      5
      6
      QDockWidget *shapesDockWidget = new QDockWidget(tr("Shapes"));
      shapesDockWidget->setObjectName("shapesDockWidget");
      shapesDockWidget->setWidget(treeWidget);
      shapesDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea
                                           | Qt::RightDockWidgetArea);
      addDockWidget(Qt::RightDockWidgetArea, shapesDockWidget);
      • 每个对象都有一个对象名, 在调试的时候有用
      • Dock widget和Toolbar都需设置对象名, 这样可使用函数QMainWindow::saveState()和QMainWindow::restoreState() 保存和恢复状态和位置大小
    • 工具条
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      QToolBar *fontToolBar = new QToolBar(tr("Font"));
      fontToolBar->setObjectName("fontToolBar");
      fontToolBar->addWidget(familyComboBox);
      fontToolBar->addWidget(sizeSpinBox);
      fontToolBar->addAction(boldAction);
      fontToolBar->addAction(italicAction);
      fontToolBar->addAction(underlineAction);
      fontToolBar->setAllowedAreas(Qt::TopToolBarArea
                                   | Qt::BottomToolBarArea);
      addToolBar(fontToolBar);
    • 保存和回复设置
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 保存
      QSettings settings("Software Inc.", "Icon Editor");
      settings.beginGroup("mainWindow");
      settings.setValue("geometry", saveGeometry());
      settings.setValue("state", saveState());
      settings.endGroup();
      // 恢复
      QSettings settings("Software Inc.", "Icon Editor");
      settings.beginGroup("mainWindow");
      restoreGeometry(settings.value("geometry").toByteArray());
      restoreState(settings.value("state").toByteArray());
      settings.endGroup();
    • QMainWindow还给dock window和Toolbar提供了右键菜单

    6.6 多文档接口

    • 一个MDI应用程序通过使用QMdiArea类作为中心widget以及让每个文档窗口为一个QMdiArea的子窗口
      • 子窗口菜单-MDI应用程序提供了一系列菜单选项表示所有的文档窗口, 当前的文档窗口会有选中标志
    • 在构造函数中, 创建一个QMdiArea对象并设置为中心widget, 并将subWindowActivated()信号发送给一个slot, 实现菜单的更新
      • 在构造函数的结尾部分有一行代码: QTimer::singleShot(0, this, SLOT(loadFiles()));
        • 表示0秒的间隔之后调用loadFiles(). 在事件循环为空闲时, 计时器运行完时间, 事实上表示当构造函数完成之后, 主窗口显示之时, 调用loadFiles()函数
        • 如果不这样做, 当有大量的文件之时, 直到文件加载完毕之后构造函数还未必完成时, 用户也许会在屏幕上看不到任何东西. 而认为程序失败且重启程序
          ?
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          void MainWindow::loadFiles()
          {
              QStringList args = QApplication::arguments();
              args.removeFirst();
              if (!args.isEmpty()) {
                  foreach (QString arg, args)
                      openFile(arg);
                  mdiArea->cascadeSubWindows();
              } else {
                  newFile();
              }
              mdiArea->activateNextSubWindow();
          }
    • 确保编辑器选择了文本才允许这两个菜单项可以使用
      ?
      1
      2
      3
      4
      connect(editor, SIGNAL(copyAvailable(bool)),
              cutAction, SLOT(setEnabled(bool)));
      connect(editor, SIGNAL(copyAvailable(bool)),
              copyAction, SLOT(setEnabled(bool)));
      • QMdiArea的addSubWindow() 函数可以创建一个新的QMdiSubWindow: QMdiSubWindow *subWindow = mdiArea->addSubWindow(editor);
      • QActionGroup 确保只有一个窗口菜单选项被选中.
    • QMdiArea::activeSubWindow() --- 返回其活跃窗口
    • qobject_cast --- 用于强制转换.
    • QTextCursor::hasSelection () --- 返回当前文本光标是否选择了文本
    • QAction::setChecked() --- 设置选中该Action
    • QScintilla --- 代码编辑的widget
    • 每个子窗口设置 Qt::WA_DeleteOnClose 属性, 当关闭的时候删除该窗口, 以免内存泄漏

    Chapter 7 Event Processing

    7.1 Reimplementing Event Handlers

    • 在Qt中, 任何事件都是QEvent派生类的实例. Qt 处理上百种事件类型, 通过枚举值来标识出事件类型.
      • 举个例子: QEvent::type() 返回 QEvent::MouseButtonPress 则表示一个鼠标按下事件.
      • 许多的事件类型都需要存储更多的信息, 例如鼠标按下事件需要知道是哪个按键被按下以及指针所在位置. 这些都保存在QEvent的派生类QMouseEvent中.
    • 通过event()函数将事件通知给对象. 该函数从QObject继承而来.
      • 在QWidget中实现了大多数通用事件处理函数: mousePressEvent, keyPressEvent, paintEvent.
      • 可以创建自定义事件类型并分配给我们自己的事件.
    • 键盘事件通过重写keyPressEvent()和keyReleaseEvent()实现.
      • Modifier键: Ctrl, Shift, Alt, 可以使用KeyPressEvent() 和 QKeyEvent::modifiers().
      • 例如判断 Ctrl + Home
        ?
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        switch (event->key()) {
        case Qt::Key_Home:
            if (event->modifiers() & Qt::ControlModifier) {
                goToBeginningOfDocument();
            }
            else
            {
                goToBeginningOfLine();
            }
            break;
        ... ...
        }
    • 一般而言, Tab和Shift+Tab用于切换widget. 在QWidget::event()中被处理, 该函数在keyPressEvent()之前被调用,
      • 如果想要修改该功能, 则重写QWidget::event()函数
        ?
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        bool CodeEditor::event(QEvent *event)
        {
            if (event->type() == QEvent::KeyPress) {
                QKeyEvent *keyEvent = static_cast(event);
                if (keyEvent->key() == Qt::Key_Tab) {
                    insertAtCurrentPosition('\t');
                    return true;
                }
            }
            return QWidget::event(event);
        }
    • 实现快捷键与Action, 处理函数的绑定
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      goToBeginningOfLineAction =
              new QAction(tr("Go to Beginning of Line"), this);
      goToBeginningOfLineAction->setShortcut(tr("Home"));  // 连接
      connect(goToBeginningOfLineAction, SIGNAL(activated()),
              editor, SLOT(goToBeginningOfLine()));
       
      goToBeginningOfDocumentAction =
              new QAction(tr("Go to Beginning of Document"), this);
      goToBeginningOfDocumentAction->setShortcut(tr("Ctrl+Home"));
      connect(goToBeginningOfDocumentAction, SIGNAL(activated()),
              editor, SLOT(goToBeginningOfDocument()));
      • 如果在程序用户界面菜单和工具栏都没有这个Action, 则会使用QShortcut来实现该快捷键功能, 以实现键的绑定
      • 可以用QAction::setShortcutContext() 或者 QShortcut::setContext() 修改快捷键的绑定
    • 三个事件 timerEvent(), showEvent(), hideEvent()
      • updateGeometry() 用于通知widget的layout manager其子widget的size hint可能发生变化, 让其进行修正.
    • startTimer() 函数启动一个计时器, 在showEvent()中设置计时器, 可以使得在widget完全显示之后启动计时器
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      void Ticker::timerEvent(QTimerEvent *event)
      {
          if (event->timerId() == myTimerId) {
              ++offset;
              if (offset >= fontMetrics().width(text()))
                  offset = 0;
              scroll(-1, 0);
          } else {
              QWidget::timerEvent(event);
          }
      }
    • 本例使用 QWidget::scroll() 替换update(), 更有效率, 每次只需要绘制多出的1像素位置的内容.

    7.2 Installing Event Filter

    • Qt的事件模型一个非常强大的功能就是一个QObject的实例可以监视另一个QObject实例的事件, 在后者QObject的实例看到这个事件之前.
    • 通过建立监视器来监控子widget的事件, 来实现特定功能, 使用事件过滤器. 具体有两个步骤:
      • 通过在目标上调用installEventFilter()函数来注册目标对象的监视器对象
      • 在监视器的eventFilter()函数中处理目标对象的事件
    • 一般最好在构造函数中注册监视器对象
      ?
      1
      2
      3
      4
      firstNameEdit->installEventFilter(this);
      lastNameEdit->installEventFilter(this);
      cityEdit->installEventFilter(this);
      phoneNumberEdit->installEventFilter(this);
      • 这四个widget首先将发送调用本widget的eventFilter()函数, 而后再到其自身的处理函数
        ?
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)
        {
            if (target == firstNameEdit || target == lastNameEdit
                    || target == cityEdit || target == phoneNumberEdit) {
                if (event->type() == QEvent::KeyPress) {
                    QKeyEvent *keyEvent = static_cast(event);
                    if (keyEvent->key() == Qt::Key_Space) {
                        focusNextChild();
                        return true;
                    }
                }
            }
            return QDialog::eventFilter(target, event);
        }
      • 上面代码实现空格键切换widget, 注意如果实现了需要的功能返回true, 这样就不会事件传递到目标对象.
      • QWidget::focusNextChild() --- 使下一个widget具有焦点
    • 五个处理与过滤事件的层次
      • 我们可以重新实现特定的事件处理函数
      • 我们可以重新实现QObject::event()
        • 用于在到达特定的事件处理函数之前处理该事件, 例如Tab键. 另外在重实现该函数的过程中需要调用基类的event()用于处理其他事件
      • 我们可以在单个对象上安装event filter
      • 我们可以在QApplication 对象上安装一个event filter, 所有对象的所有事件都会发送给eventFilter()函数. 常用语调试,
      • 我们可以实现QApplication的派生类, 以及重新实现notify().
        • Qt调用QApplication::notify()发送事件, 重新实现该函数是获得所有事件的唯一方法, 在任何event filter有机会处理事件之前.
    • 许多类型的事件, 包括鼠标和按键事件, 都会进行传递. 当目标对象没有处理该事件, 则其父widget会进行处理该事件. 直到顶层对象.

    7.3 Staying Responsive during Intensive Processing

    • 调用QApplication::exec()之后, 开始时间循环, 首先是显示和绘制widget, 而后循环运行检查是否有新的事件, 而后分发这些事件至对象.
    • 使用多线程处理一些耗时的任务, 以避免界面不响应
    • QApplication::processEvents()告诉Qt处理任何待处理的事件, 而后返回控制给调用者.
      • 事实上 QApplication::exec只是在while循环内部调用processEvents().
    • 在耗时的处理函数中使用qApp->processEvents(); 或者: qApp->processEvents(QEventLoop::ExcludeUserInputEvents); 以避免其他重要的操作如关闭程序. 用于忽略鼠标和按钮事件.
    • QProgressDialog是一个进程条, 用于告知当前处理的程度
      • QProgressDialog 类: setLabelText, setRange, setModel, setValue, wasCanceled
    • 用QApplication::hasPendingEvents和计时器来判断当前是否处于空闲时期

    Chapter 8 2D Graphics

    • Qt 4.2 "图形视图"结构的核心部分: QGraphicsView, QGraphicsScene, QGraphicsitem 类

    8.1 Painting with QPainter

    • 在图形设备上绘制, 仅仅需要将设备的指针传递给QPainter构造函数的参数, 如: QPainter painter(this);
      • QPainter的三个重要属性: pen, brush, font
      • 重要draw函数: drawPoint, drawLine, drawPolyLine, drawPoints, drawLines, drawPolygon, drawRect, drawRoundRect, drawEllipse, drawArc, drawChord, drawPie, drawText, drawPixmap, drawPath
      • pen 用于绘制线条和图形轮廓, 由颜色, 宽度, 线条形状, 关联方式组成.
        • Cap 和 joint styles: FlatCap, SquareCap, RoundCap, MiterJoint, BevelJoint, RoundJoint
        • Line Style: SolidLine, DashLine, DotLint, DashDotLine, DashDotDotLine, NoPen
      • brush 表示用于填充几何形状的模式, 由颜色和风格组成. 也可以是一个纹理
        • style: SolidPattern, Dense1Pattern, Dense2Pattern, Dense3Pattern, Dense4Pattern, Dense5Pattern, Dense6Pattern, Dense7Pattern, HorPattern, VerPattern, CrossPattern, BDiagPattern, FDiagPattern, DiagCrossPattern, NoBrush
      • font 用于绘制文本, 含有许多属性, 其中包含 family和 点大小
      • 可以用setPen, setBrush, setFont 修改这些内容
    • painter.setRenderHint(QPainter::Antialiasing, true); // 可以实现反锯齿
    • QPainterPath 可以指定用于连接基本图形的元素容器: 如直线, 椭圆形, 多边形, 弧, 贝塞尔曲线 和其他绘制路径.
      • 一个路径可以表示一条轮廓, 以及该轮廓所标示的面积, 该面积可以用笔刷填充.
    • gradient fill是一个可选的单色填充方式, Gradient依赖于颜色插值以得到两个颜色之间的平滑转换. 常用于生成3D效果.
      • Qt 提供三个gradient类型: 线性, 圆锥体的(conical), 径向的(radial)
      • 线性: 有两个控制点用于定义, 通过一系列线上的"颜色点"来连接两个点. 如:
        ?
        1
        2
        3
        4
        QLinearGradient gradient(50, 100, 300, 350);
        gradient.setColorAt(0.0, Qt::white);
        gradient.setColorAt(0.2, Qt::green);
        gradient.setColorAt(1.0, Qt::black);
      • Radial: 通过中心点(Xc,Yc), 半径r, 焦点(Xf, Yf)来定义,
      • Conical: 通过中心点(Xc, Yc)和角度a来定义.
    • 其他的属性:
      • background brush, brush origin, clip region(可以绘制的区域),
      • viewport, window, world transform --- 可以确定逻辑QPainter坐标映射到物理绘制设备坐标. 缺省情况两者一致
      • composition mode --- 定义新绘制的像素如何与原有像素相互影响. 默认是alpha 混合.
    • 我们可以通过 save()保存当前painter的状态, 通过restore()还原.

    8.2 Coordinate System Transformations

    • 缺省的坐标系统, 左上角(0, 0), X轴正向方向向右, Y轴正向方向向下, 每个像素的大小为 1x1
    • 一个像素的中心是0.5, 只有当禁用反锯齿的时候+0.5.
      • 如果开启了反锯齿, 在点(100, 100)绘制黑色. 则四个点(99.5, 99.5), (99.5, 100.5), (100.5, 99.5), (100.5, 100.5)为灰色.
      • 如果不想使用这个效果, 则移动QPainter (+0.5, +0.5), 或者指定半个像素的坐标
      • 窗口(逻辑) --- 视图(物理)
      • 窗口-视图机制 可以让绘制代码独立于绘制设备的分辨率和大小
        ?
        1 painter.setWindow(-50, -50, 100, 100);    // 从(-50, -50)到(50, 50), 中心点为(0, 0)
      • 如果我们想要在45度角绘制文本
        ?
        1
        2
        3
        4
        QTransform transform;
        transform.rotate(+45.0);
        painter.setWorldTransform(transform);
        painter.drawText(pos, tr("Sales"));
      • 指定转换的简单方法是使用QPainter的translate(), scale(), rotate()和shear()方法
      • 如果经常使用同一个转换, 可以保存一个QTransform, 在需要的时候使用
    • QTimer调用setSingleShot(true), 表示在时间结束之后只发送一次time out信息. 否则缺省计时器会重复启发直至他们停止或者销毁.
    • qBound() 函数, 确保第二个参数的值在第一个和第三个参数之间.
    • 本节的例子使用QConicalGradient(QRadialGradient, QLinearGradient)实现了非常漂亮的效果, 这几个可以当作笔刷使用

    8.3 High-Quality Rendering with QImage

    • 有时我们需要权衡速度和精确度之间的关系. 当精确度比效率更重要时, 我们将绘制到QImage, 而后将结果拷贝至屏幕. 这将使用Qt的内置绘制引擎.
      • 唯一的限制是QImage的创建参数为QImage::Format_RGB32 或 QImage::Format_ARGB32_Premultiplied
    • "premultiplied ARPG32" 格式表示红,绿,蓝频道带有多个alpha频道. 代码:
      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      void MyWidget::paintEvent(QPaintEvent *event)
      {
          QImage image(size(), QImage::Format_ARGB32_Premultiplied);
          QPainter imagePainter(&image);
          imagePainter.initFrom(this);
          imagePainter.setRenderHint(QPainter::Antialiasing, true);
          imagePainter.eraseRect(rect());
          draw(&imagePainter);
          imagePainter.end();
       
          QPainter widgetPainter(this);
          widgetPainter.drawImage(0, 0, image);
      }
      • 上面代码先用QPainter绘制QImage, 而后绘制到屏幕
      • 一个非常好的功能就是Qt图形引擎支持合成模式, 源和目标像素可以混合. 在所有的绘制操作都可以实现这点, 如笔, 笔刷, 渐进和图像绘制
      • 缺省的合成模式为: QImage::CompositionMode_SourceOver, 表示源像素会覆盖在目标像素之上, 根据alpha值设置透明度.
      • 可通过 QPainter::setCompositionMode() 来设置合成模式. 如:
        ?
        1
        2
        3
        4
        QImage resultImage = checkerPatternImage;
        QPainter painter(&resultImage);
        painter.setCompositionMode(QPainter::CompositionMode_Xor);
        painter.drawImage(0, 0, butterflyImage);
      • XOR 源和目标. 注意XOR模式对Alpha也是有效的.

    8.4 Item-Based Rendering with Graphics View

    • QPainter对于绘制自定义widget和比较少的条目时是比较理想的方法
    • 绘制大量的条目和内容时的解决方案
      • 图形视图结构包含场景, 由QGraphicsScene类表示, 场景中的项目, 由QGraphicsItem的子类表示.
      • 通过在视图中显示场景. 由QGraphicsView类表示视图. 相同的场景可以在多个视图中显示. 如显示一个巨大场景的不同部分, 不同的转换.
    • 预定义好的QGraphicsItem派生类, 如QGraphicsLineItem, QGraphicsPixmapItem, QGraphicsSimpleTextItem, QGraphicsTextItem
      • 也可以自己创建自定义派生类
    • QGraphicsScene控制图形元素的结合, 有三个layer, 背景层, 元素层, 前景层. 背景和前景层通常由QBrushes指定
      • 我们可以基于pixmap创建一个纹理QBrush作为背景. 前景则可以设置半透明等
      • 视图则管理场景. 视图可用内置的2D绘制引擎, 也可以用Opengl. 使用setViewport()来调用opengl
      • 视图可以用打印一个场景或者场景的一部分.
      • 这个结构使用三个不同的坐标系统 - 视图坐标, 场景坐标和项目(item)坐标 -- 带有一个坐标系统向另一个坐标系统映射的功能.
        • 视图坐标系统位于QGraphicsView的视图内部. 场景坐标系统是逻辑坐标系统, 用于防治场景上的顶层项目(item).
        • 项目坐标系统用于指定每个项目, 且其中心为(0, 0)本地坐标.
        • 事实上我们只关心系统坐标放置顶层项目, 项目坐标放置子项目.
      • 为了介绍图形视图, 本例使用了两个例子, 第一个例子为简单的图表编辑器,
      • 第二个例子为注解映射程序显示如何处理大量的图形对象, 如何有效的渲染以及缩放
    • QGraphicsItem不是QObject的派生类, 如果想要使用signal和slot, 可以实现多个继承, 其中一个基类为QObject. 其可以调用函数 setLine 绘制直线.
    • Q_DECLARE_TR_FUNCTIONS()宏用于添加一个tr()函数至该类. 即便它不是QOjbect的派生类, 这样可以让我们简单的使用tr(), 而不是QObject::tr() 或者 QApplication::translate()
    • 当我们实现了QGraphicsItem的派生类时, 如果想要手工绘制内容, 则需要重新实现boundingRect()和paint()
      • 如果我们不重新实现 shape(), 基类则会回去调用boundingRect, 所以我们重新实现shape()用于返回更精确的形状, 该形状可以考虑到节点的圆角角
      • graphic view结构使用围绕矩形(bounding rectangle)确定一个项目绘制的区域. 这能够快速的显示任意大的场景, 虽然在每个时候只能显示该场景的一部分
      • 形状(shape)则确定某点是否在一个项目之内, 或者两个项目是否相互碰撞.
    • 本例图表应用程序, 我们将提供属性对话框用于编辑节点的位置, 颜色和文本. 通过双击节点则可修改文本.
    • 下面的代码删除该节点的所有Link, 而无论该Link是否被销毁了, 如果使用qDeleteAll() 则会产生一些副作用.
      ?
      1
      2
      foreach (Link *link, myLinks)
          delete link;
    • 当一个项目的围绕矩形会发生变化之时(由于新的文本也许会大于或小于当前文本),
      • 我们必须在之前立即调用 prepareGeometryChange(), 这样便于影响项目的围绕矩形.
      • 修改颜色的时候不需要调用 prepareGeometryChange(), 因为这不会影响到项目的围绕矩形大小
    • 求一个节点的矩形框
      ?
      1
      2
      3
      4
      5
      6
      const int Padding = 8;
      QFontMetricsF metrics = qApp->font();
      QRectF rect = metrics.boundingRect(myText);
      rect.adjust(-Padding, -Padding, +Padding, +Padding);
      rect.translate(-rect.center());
      return rect;   
      • QFontMetrics计算的围绕矩形左上角坐标总为(0, 0)
    • 可使用QPainterPath精确的描述圆角矩形, 可以使得当鼠标位于角落且不在圆角矩形之内时, 不能够选择该矩形
    • QStyleOptionGraphicsItem是一个不经常使用的类, 提供了几个公有成员变量,
      • 如当前layout方向, 字体metrics, palette, 矩形, 状态, 转换矩阵, 以及细节层次Lod,
      • 重新实现 QGraphicsItem::itemChange, 当项目变化时作出一些反应
    • 创建一个QGraphicsScene, 而后创建一个QGraphicsView来显示它.
      • 选择的项目可以通过按Ctrl键多选. 设置模式QGraphicsView::RubberBandDrag, 表示可以通过鼠标划拉一个矩形多选项目: view->setDragMode(QGraphicsView::RubberBandDrag);
      • QGraphicsScene 可以发射信号 selectionChanged, 调用addItem 增加项目, clearSelection取消选择, selectedItems返回选中项目的列表
      • QGraphicsItem可调用setPos设置位置, setSelected表示选中与否
      • QGraphicsView 可调用removeAction 移除菜单, 调用addAction添加菜单
    • QMutableListIterator 用于遍历一个列表; qDeleteAll 用于删除一列表所有元素
    • QColorDialog::getColor() 调用颜色对话框
    • QApplication::clipboard()->setText(str); --- 使用剪贴板
    • QApplication::clipboard()->text(); --- 得到剪贴板文本
    • QString::split --- 分割字符串为QStringList
    • QStringList 用mid 得到部分的列表, 用join合起来成一个字符串
    • 本节第二个例子
      • QGraphicsScene: setBackgroundBrush设置背景笔刷
      • Lod可以表示为缩放因子, QStyleOptionGraphicsItem::levelOfDetail 表示为其缩放因子
      • 使用 ItemIgnoresTransformations 标志可以忽略缩放, 不会跟随View的缩放而更改该Item的大小
    • QGraphicsView派生一个类, 实现特定的特色.
      • 调用setDragMode, 可设置拖曳模式, 如 setDragMode(ScrollHandDrag);
      • 实现wheelEvent函数, 可实现鼠标滚轮事件. 而后调用QGraphicsView::scale函数实现缩放
    • 我们的graphic view有许多的功能, 如可以拖曳, 图形项目有tooltip和自定义光标.
      • 可通过给项目设置QGraphicsItemAnimations和QTimeLine 来实现动画.

    8.5 Printing

    • 对于Qt来说, 打印和QWidget, QPixmap, QImage绘制一样, 由以下步骤组成
      • 创建一个QPrinter用于绘制设备
      • 弹出QPrintDialog, 让用户选择一个打印机并设置一些属性.
      • 创建一个QPainter, 让其对QPrinter进行操作
      • 使用QPainter绘制一个页面
      • 调用QPrinter::newPage() 绘制下一个页面
      • 重复第四步和第五步直至所有页面打印完毕
    • 在Windows和Mac OS中, QPrinter使用系统的打印驱动. 在Unix中, 它生成PostScript并将其发送给ip或ipr(或者使用QPrinter::setPrintProgram()发送给程序集)
      • QPrinter也可以通过调用setOutputFromat(QPrinter::PdfFormat)生成PDF文件
      • 通过QPrintDialog的对象调用exec()来执行打印对话框.
      • 可将QPrinter对象作为参数传送给QPainter, 而后QPainter绘制图像实现打印一个图像. drawImage
      • 如果要打印一个graphics view scenes也很简单, 将QPrinter作为第一个参数传送给QGraphicsScene::render()或者QGraphicsView::render().
        • 如果只想绘制场景的一部分, 则将目标矩形(打印页面的位置)和源矩形作为参数传送给render()的可选参数.
    • 两个处理打印多页面的方法
      • 我们可以将我们的数据转换成HTML, 而后使用QTextDocument渲染它. 使用Qt的富文本引擎
      • 手动执行绘制和页面中断
      • 富文本方式:
        ?
        1
        2
        3
        QTextDocument textDocument;
        textDocument.setHtml(html);
        textDocument.print(&printer);
    • 本节还演示了如何给一个QStringList进行分页打印. 写了一个函数, 根据高度进行分页
      ?
      1
      2
      void PrintWindow::paginate(QPainter *painter, QList *pages,
                              const QStringList &entries)
      • 在例子中函数 int PrintWindow::entryHeight(QPainter *painter, const QString &entry) 计算每个条目的高度 其使用 QPainter::boundingRect() 计算垂直高度.
      • 通过QPrintDialog, 用户可以设定拷贝次数, 打印范围, 请求页面顺序(顺序还是反序)
      • 可通过调用QPrintDialog::setEnabledOptions() 来确定哪些选项不能由用户设定



你可能感兴趣的:(Qt学习笔记)