Qt学习小记(二)

来源:互联网 发布:乐视网络高清机顶盒 编辑:程序博客网 时间:2024/06/05 11:14

      • MainWindow简介
      • Qt中的对象模型

MainWindow简介

  • MainWindow的成分
    MainWindow的成分
  • 菜单栏和工具栏的动作QAction
    Qt 使用QAction类作为动作。顾名思义,这个类就是代表了窗口的一个“动作”,这个动作可能显示在菜单,作为一个菜单项,当用户点击该菜单项,对用户的点击做出响应;也可能在工具栏,作为一个工具栏按钮,用户点击这个按钮就可以执行相应的操作。有一点值得注意:无论是出现在菜单栏还是工具栏,用户选择之后,所执行的动作应该都是一样的。因此,Qt 并没有专门的菜单项类,只是使用一个QAction类,抽象出公共的动作。当我们把QAction对象添加到菜单,就显示成一个菜单项,添加到工具栏,就显示成一个工具按钮。用户可以通过点击菜单项、点击工具栏按钮、点击快捷键来激活这个动作。(目前所知QAction只用于这三个地方)
  • QAction构造与使用
    QAction包含了图标、菜单文字、快捷键、状态栏文字、浮动帮助等信息。当把一个QAction对象添加到程序中时,Qt 自己选择使用哪个属性来显示,无需我们关心。同时,Qt 能够保证把QAction对象添加到不同的菜单、工具栏时,显示内容是同步的。也就是说,如果我们在菜单中修改了QAction的图标,那么在工具栏上面这个QAction所对应的按钮的图标也会同步修改。

    /*  * 构造 * 我们使用的是 png 格式的图片,这是 Qt 内置支持的图片格式。 * 其他格式的图片,比如 jpg、gif 则需要插件支持。这些插件实际已经随着 Qt 一同发布。 * QAction第二个参数中,文本值前面有一个 &,意味着这将成为一个快捷键。 */QAction *action = new QAction(QIcon(":/1.png"), tr(&Open), this);/*  * setShortcuts(); * 用于说明这个QAction的快捷键。 * Qt 的QKeySequence为我们定义了很多内置的快捷键,比如我们使用的 Open。你可以通过查阅 API 文档获得所有的快捷键列表。  * 这个与我们自己定义的有什么区别呢?简单来说,我们完全可以自己定义一个tr("Ctrl+O")来实现快捷键。原因在于,这是 Qt 跨平台性的体现。比如 PC 键盘和 Mac 键盘是不一样的,一些键在 PC 键盘上有,而 Mac 键盘上可能并不存在,或者反之。 * 使用QKeySequence类来添加快捷键,会根据平台的不同来定义相应的快捷键。 */action->setShortcuts(QKeySequence::Open);/* * setStatusTip() * 实现了当用户鼠标滑过这个 action 时,会在主窗口下方的状态栏显示相应的提示。 */action->setStatusTip(tr("Open a existing file"));//在完成这些之后还需要把动作添加到菜单栏或者工具栏上QMenu *menu = menuBar()->addMenu(tr(&Open));//在菜单栏上添加一项menu->addAction(action);QToolBar *toolBar = addToolBar(tr(&Open));//在工具栏上添加一项,图标在动作中添加toolBar->addAction(action);//若要使动作实现完整的功能,还需要编写一个槽函数,连接到该QAction发出的trigger信号上connect(action, &QAction::trigger, receiver, &QObject::slots);

Qt中的对象模型

  • 标准 C++ 对象模型在运行时效率方面卓有成效,但是在某些特定问题域下的静态特性就显得捉襟见肘。GUI 界面需要同时具有运行时的效率以及更高级别的灵活性。为了解决这一问题,Qt “扩展”了标准 C++。所谓“扩展”,实际是在使用标准 C++ 编译器编译 Qt 源程序之前,Qt 先使用一个叫做 moc(Meta Object Compiler,元对象编译器)的工具,先对 Qt 源代码进行一次预处理(注意,这个预处理与标准 C++ 的预处理有所不同。Qt 的 moc 预处理发生在标准 C++ 预处理器工作之前,并且 Qt 的 moc 预处理不是递归的。),生成标准 C++ 源代码,然后再使用标准 C++ 编译器进行编译。如果你曾经为信号函数这样的语法感到奇怪(现在我们已经编译过一些 Qt 程序,你应当注意到了,信号函数是不需要编写实现代码的,那怎么可以通过标准 C++ 的编译呢?),这其实就是 moc 进行了处理之后的效果。
  • Qt 使用 moc,为标准 C++ 增加了一些特性:
    • 信号与槽机制
    • 查询、设计对象的属性
    • 事件机制
    • 基于上下文的字符串翻译机制(国际化),也就是 tr() 函数
    • 复杂的定时器实现,用于在事件驱动的 GUI 中嵌入能够精确控制的任务集成;
    • 层次化的可查询的对象树,提供一种自然的方式管理对象关系。
    • 智能指针(QPointer),在对象析构之后自动设为 0,防止野指针;
    • 能够跨越库边界的动态转换机制。
  • 通过继承QObject类,我们可以很方便地获得这些特性。
    这些特性都是由 moc 帮助我们实现的。moc 其实实现的是一个叫做元对象系统(meta-object system)的机制。正如上面所说,这是一个标准 C++ 的扩展,使得标准 C++ 更适合于进行 GUI 编程。虽然利用模板可以达到类似的效果,但是 Qt 没有选择使用模板。按照 Qt 官方的说法,模板虽然是内置语言特性,但是其语法实在是复杂,并且由于 GUI 是动态的,利用静态的模板机制有时候很难处理。而自己使用 moc 生成代码更为灵活,虽然效率有些降低(一个信号槽的调用大约相当于四个模板函数调用),不过在现代计算机上,这点性能损耗实在是可以忽略。
  • QObject是以对象树的形式组织起来的。
    当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
  • QWidget是能够在屏幕上显示的一切组件的父类。
    QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
    当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
    我们可以使用QObject::dumpObjectTree()和QObject::dumpObjectInfo()这两个函数进行这方面的调试。
  • Qt 引入对象树的概念,在一定程度上解决了内存问题。
    当一个QObject对象在上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。Qt 保证的是,任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
  • 那么在栈上呢?

    int main(){    QWidget window;    QPushButton quit(tr("QUIT"), &window);  }


    基于C++标准(ISO/IEC 14882:2003),局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。故正确。

    int main(){    QPushButton quit(tr("QUIT"));    QWidget window;    quit.setParent(&window);}


    在这种情况下,销毁的顺序会引起一个问题。父窗体的析构函数首先被调用,因为它最后被创建。然后调用子部件(quit)的析构函数,这是不正确的,因为quit是一个局部变量,当出了作用域后,其析构函数再次被调用,因此会出错。

  • 结论
    Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。
  • 另外
    在 main() 函数中,不应该在堆上面创建对象。这是由于如果在 main() 中在堆上面创建对象,app.exec() 函数是一个死循环,创建出的这个对象没有办法被 delete(不开启事件循环,组件就不能显示,不显示组件就不能 delete,否则你还创建它干什么呢?)。另外的原因是,由于我们的 QApplication 是在栈上面创建的,在堆上面创建的 QLabel 对象生命周期要长于 QApplication,这在 Qt 中是应该避免的。而对于我们自己定义的组件就没有这个问题,因为不在 main() 函数中,我们始终可以保证最晚在关闭时销毁(当然是不发生内存泄露的情况下),也就没有这个问题。
原创粉丝点击