完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1、Hello, Qt! 我们以一个非常简单的 Qt 程序开始 Qt 的学习。我们首先一行行的分析代码,然后我 们将会看到怎样编译和运行这个程序。 1 #include 2 #include 3 int main (int argc, char *argv []) 4 { 5 QApplication app (argc, argv); 6 QLabel *label = new QLabel ("Hello Qt!"); 7 label->show (); 8 return app. exec (); 9 } 第 1 行和第 2 行包含了两个类的定义:QApplication 和 QLabel。对于每一个 Qt 的类, 都会有一个同名的头文件,头文件里包含了这个类的定义。因此,你如果在程序中使用了一 个类的对象,那么在程序中就必须包括这个头文件。 第 3 行是程序的入口。几乎在使用 Qt 的所有情况下,main()函数只需要在把控制权转 交给 Qt 库之前执行一些初始化,然后 Qt 库通过事件来向程序告知用户的行为。argc 是命 令行变量的数量,argv 是命令行变量的数组。这是一个 C/C++特征。它不是 Qt 专有的,无 论如何 Qt 需要处理这些变量 第 5 行定义了一个 QApplication 对象 App。QApplication 管理了各种各样的应用程序的 广泛资源,比如默认的字体和光标。 App 的创建需要 argc 和 argv 是因为 Qt 支持一些自己的 命令行参数。在每一个使用 Qt 的应用程序中都必须使用一个 QApplication 对象,并且在任 何 Qt 的窗口系统部件被使用之前创建此对象是必须的。App 在这里被创建并且处理后面的 命令行变量(比如在 X 窗口下的-display)。请注意,所有被 Qt 识别的命令行参数都会从 argv 中被移除(并且 argc 也因此而减少)。 第 6 行创建了一个 QLabel 窗口部件(widget) ,用来显示“Hello,Qt!”。在 Qt 和 Unix 的术语中,一个窗口部件就是用户界面中一个可见的元素,它相当于 Windows 术语中的“容 器”加上“控制器”。按钮(Button)、菜单(menu)、滚动条(scroll bars)和框架(frame) 都是窗口部件的例子。窗口部件可以包含其它的窗口部件。例如,一个应用程序界面通常就 是一个包含了 QMenuBar,一些 QToolBar,一个 QStatusBar 和其它的一些部件的窗口。绝大 多数应用程序使用一个 QMainWindow 或者一个 QDialog 作为程序界面,但是 Qt 允许任何 窗口部件成为窗口。在这个例子中,QLabel 窗口部件就是作为应用程序主窗口的。 第 7 行使我们创建的 QLabel 可见。当窗口部件被创建的时候,它总是隐藏的,必须调 用 show()来使它可见。通过这个特点我们可以在显示这些窗口部件之前定制它们,这样就不 会出现闪烁的情况。 第 8 行就是 main()将控制权交给 Qt。在这里,程序进入了事件循环。事件循环是一种 stand-by 的模式,程序会等待用户的动作(比如按下鼠标或者是键盘)。用户的动作将会产 生程序可以做出反应的事件(也被称为“消息”) 。程序对这些事件的反应通常是执行一个或 几个函数。 为了简单起见,我们没有在 main()函数的结尾处调用 delete 来删除 QLabel 对象。这种 内存泄露是无害的,因为像这样的小程序,在结束时操作系统将会释放程序占用的内存堆。 下面我们来编译这个程序。建立一个名为 hello 的目录,在目录下建立一个名为 hello.cpp 的 c++源文件,将上面的代码写入文件中。 运行“开始à程序àQt by TrolltechàQt Command Prompt”。 在命令行模式下,切换目录到 hello 下,然后输入命令:qmake –project。这个命令将 产生一个依赖于工作平台的工程文件(hello.pro)。 再输入命令:qmake hello.pro。这个命令通过工程文件产生一个可以在特定工作平台 上使用的 makefile。 最后输入命令:make 来产生应用程序。运行这个程序,可以得到如下的程序界面。 Qt 也支持 XML。我们可以把程序的第 6 行替换成下面的语句: QLabel *label = new QLabel (" Hello " "Qt!");重新编译程序,我们发现界面拥有了简单的 HTML 风格。如下图: 2、调用退出 第二个例子展示了如何使应用程序对用户的动作进行响应。这个应用程序包括了一个按 钮,用户可以点击这个按钮来退出程序。程序代码与上一个程序非常相似,不同之处在于我 们使用了一个 QPushButton 来代替 QLabel 作为我们的主窗口,并且我们将一个用户动作(点 击一个按钮)和一些程序代码连接起来。 1 #include 2 #include 3 int main (int argc, char *argv []) 4 { 5 QApplication app (argc, argv); 6 QPushButton *button = new QPushButton ("Quit"); 7 QObject::connect (button, SIGNAL (clicked ()), 8 &app, SLOT (quit ())); 9 button->show (); 10 return app. exec (); 11 } Qt 程序的窗口部件发射信号(signals)来指出一个用户的动作或者是状态的变化。在这 个例子中,当用户点击这个按钮的时候,QPushButton 就会发射一个信号——clicked()。一 个信号可以和一个函数(在这种情况下我们把这个函数叫做“槽(slot)”)相连,当信号 被发射的时候,和信号相连的槽就会自动执行。在这个例子中,我们把按钮的信号“clicked()” 和一个 QApplication 对象的槽“quit()”相连。当按钮被按下的时候,这个程序就退出了。 3、窗口布局 在本小节,我们将用一个样例来展现如何在窗口中规划各个部件的布局,并学习使用信 号和槽来使两个窗口部件同步。这个应用程序要求输入用户的年龄,使用者可以通过一个旋 转窗口或者一个滑块窗口来输入。 这个应用程序包括三个窗口部件:一个 QSpinBox,一个 QSlider 和一个 QWidget。窗口 部件 QWidget 是程序的主窗口。 QSpinBox 和 QSlider 被放置在 QWidget 中;他们是 QWidget 的子窗口。当然,我们也可以说 QWidget 是 QSpinBox 和 QSlider 的父窗口。QWidget 本身 没有父窗口,因为它被当作一个顶级的窗口。 QWidget 以及所有它的子类的构造函数都拥有 一个参数:QWidget *,这说明了它的父窗口。 下面是程序的代码: 1 #include 2 #include 3 #include 4 #include 5 int main (int argc, char *argv []) 6 { 7 QApplication app (argc, argv); 8 QWidget *window = new QWidget; 9 window->setWindowTitle ("Enter Your Age"); 10 QSpinBox *spinBox = new QSpinBox; 11 QSlider *slider = new QSlider (Qt::Horizontal); 12 spinBox->setRange (0, 130); 13 slider->setRange (0, 130); 14 QObject::connect (spinBox, SIGNAL (valueChanged (int)), 15 slider, SLOT (setValue (int))); 16 QObject::connect (slider, SIGNAL (valueChanged (int)), 17 spinBox, SLOT (setValue (int))); 18 spinBox->setValue (50); 19 QHBoxLayout *layout = new QHBoxLayout; 20 layout->addWidget (spinBox); 21 layout->addWidget (slider); 22 window->setLayout (layout); 23 window->show (); 24 return app. exec (); 25 } 第 8 行和第 9 行设置了 QWidget ,它将被作为程序的主窗口。我们调 用函数 setWindowTitle()来设置窗口的标题栏。 第 10 行和第 11 行创建了一个 QSpinBox 和一个 QSlider,第 12 行和第 13 行设置了它们 的取值范围(我们假设用户最大也只有 130 岁)。我们可以将之前创建的 QWidget 对象 window 传递给 QSpinBox 和 QSlider 的构造函数,用来说明这两个对象的父窗口,但是这么做并不 是必须的。原因是窗口布局系统将会自己指出这一点,自动将 window 设置为父窗口。我们 一会儿就可以看到这个特性。 在第 14 行和第 17 行,两个对于 QObject::connect()函数的调用确保了旋转窗口和滑块窗 口的同步,这样这两个窗口总是显示同样的数值。不管一个窗口对象的数值何时发生变化, 它的信号 valueChanged(int)就将被发射,而另一个窗口对象的槽 setValue(int)会接受到这个信 号,使得自身的数值与其相等。 第 18 行将旋转窗口的数值设置为 50。当这个事件发生的时候,QSpinBox 发射信号 valueChanged(int) ,这个信号包括一个值为 50 的整型参数。这个参数被 QSlider 的槽 setValue(int)接受,就会将滑块的值也设置为 50。由于 QSlider 的值被改变,所以 QSlider 也 会发出一个 valueChanged(int)信号并触发 QSpinBox 的 setValue(int)槽。但是在这个时候, QSpinBox 不会再发出任何信号,因为旋转窗口的值已经被设置为 50 了。这将有效地防止信 号的无限循环。 从第 19 行到第 22 行,我们通过使用一个 layout 管理器对旋转窗口和滑块窗口进行了布 局设置。一个布局管理者就是一个根据窗口作用设置其大小和位置的对象。Qt 有三个主要 的布局管理类: QHBoxLayout:将窗口部件水平自左至右设置(有些情况下是自右向左)。 QVBoxLayout:将窗口部件垂直自上向下设置。 QGridLayout: 以网格形式设置窗口部件。 第 22 行我们调用 QWidget::setLayout()函数在对象 window 上安装布局管理器。通过这 个调用,QSpinBox 和 QSlider 自动成为布局管理器所在窗口的子窗口。现在我们明白为什 么在设置子窗口时不用显式地说明父窗口了。 可以看到,虽然没有明显地给出任何窗口的大小和位置,但 QSpinBox 和 QSlider 是很 完美地被水平依次放置的。这是因为 QHBox-Layout 根据各个窗口的作用自动的为其设置了 合理的大小和位置。这个功能使我们从烦琐的界面调整中解放出来,更加专注于功能的实现。 Qt 构建用户界面的方法很容易理解,并且有很高的灵活性。Qt 程序员最常用的设计模 式是:说明所需要的窗口部件,然后设置这些部件必须的特性。程序员把窗口部件添加到布 局管理器中,布局管理器就将自动地设置这些部件的大小和位置。而用户界面的行为是通过 连接各个部件(运用信号/槽机制)来实现的。 4、派生 QDialog 我们现在开始尝试着在 Qt 里只用 C++语言而不是借助界面设计器来完成一个对话框: FIND。我们将这个对话框作为一个类来完成,这么做的好处是我们使这个对话框成为了一 个独立的,拥有自己的信号和槽的,设备齐全的组件。 程序的源代码由两部分组成:finddialog.h 和 finddialog.cpp。我们从头文件开始。 1 #ifndef FINDDIALOG_H 2 #define FINDDIALOG_H 3 #include 4 class QCheckBox; 5 class QLabel; 6 class QLineEdit; 7 class QPushButton; 第 1 行,第 2 行(和第 27 行)的作用是防止头文件被重复包含。 第 3 行包含了 QDialog 的定义。QDialog 从 QWidget 继承而来,是 Qt 的对话框基类。 第 4 行到第 7 行是对我们将要用来填充对话框的对象的类的预定义。一个预先的声明 将会告诉 C++编译器这个类的存在,而不用给出所有关于实现的细节。 然后我们定义 FindDialog 作为 QDialog 的一个子类: 8 class FindDialog: public QDialog 9 { 10 Q_OBJECT 11 public: 12 FindDialog (QWidget *parent = 0); 在类定义顶端出现了宏:Q_OBJECT。这对于所有定义了信号或槽的类都是必须的。 FindDialog 的构造函数拥有 Qt 窗口类的典型特征。参数 parent 声明了父窗口。其默认 值是一个空指针,表示这个对话框没有父窗口。 13 signals: 14 void findNext (const QString &str, Qt::CaseSensitivity cs); 15 void findPrevious (const QString &str, Qt::CaseSensitivity cs); 标记为 signals 的这一段声明了两个信号。当用户点击对话框的“Find”按钮的时候, 信号将被发射。如果选项“Search backward”被选中,对话框将发射消息 findPrevious();相 反的,对话框将发射消息 findNext()。 关键字“signals”实际上也是一个宏。 C++预处理器将在编译器看到它之前就已经将它 转换为了标准的 C++。Qt::CaseSecsitivity 是一个枚举类型。它可以代表值 Qt::CaseSensitive 和 Qt::CaseInsensitive。 16 private slots: 17 void findClicked (); 18 void enableFindButton (const QString &text); 19 private: 20 QLabel *label; 21 QLineEdit *lineEdit; 22 QCheckBox *caseCheckBox; 23 QCheckBox *backwardCheckBox; 24 QPushButton *findButton; 25 QPushButton *closeButton; 26 }; 27 #endif 在类的 private 字段中,我们声明了两个槽。为了实现这些槽,我们需要访问大多数对 话框的子窗口,所以我们在私有字段中保留了这些子窗口的指针。和 signals 一样,关键字 slots 也是一个构造后可以被 C++编译器辩识的宏。 对于私有变量,我们使用了它们的类的预定义。这是被编译器所允许的,因为他们都 是指针,而我们在头文件中并不需要访问他们,所以编译器并不需要完整的类定义。我们可 以在头文件中包含使用这些类所需要的头文件( 预定义可以在某种程度上加快编译过程。 现在我们来看源文件 finddialog.cpp。源文件里包括了 FindDialog 类的实现。 1 #include 2 #include "finddialog.h" 首先,我们包含了 模块,每一个模块都依赖于自己的库文件。最重要的几个模块分别是 QtCore, QtGui, QtNetwork, QtOpenGL, QtSpl, QtSvg 和 QtXml。头文件 中所有类的实现。包含此头文件使我们不用单独地列出每个类所需要的头文件。 在 filedialog.h 中,我们也可以简单地包含 简单一些,但是在头文件中包含另外一个大的头文件是一个很坏的方式,尤其是在比较大的 应用当中。 3 FindDialog::FindDialog (QWidget *parent) 4 : QDialog (parent) 5 { 6 label = new QLabel (tr ("Find &what :")); 7 lineEdit = new QLineEdit; 8 label->setBuddy (lineEdit); 9 caseCheckBox = new QCheckBox (tr ("Match &case")); 10 backwardCheckBox = new QCheckBox (tr ("Search &backward")); 11 findButton = new QPushButton (tr ("&Find")); 12 findButton->setDefault (true); 13 findButton->setEnabled (false); 14 closeButton = new QPushButton (tr ("Close")); 在第 4 行,我们将参数 parent 传递给基类的构造函数。然后我们创建子窗口。对于所 有的字符串我们都调用函数 tr(),这些字符串被标记为可以翻译成别的语言。函数 tr()在 QObject 和每个含有 Q_OBJECT 宏的子类中被定义。将没一个用户可见的字符串都用 TR() 包括起来是一个很好的习惯,即使你现在并没有将程序翻译为别的语言的计划。对于 Qt 应 用程序的翻译将在后述章节中详细呈现。 在字符串中,我们用操作符’&’来指出快捷键。例如,第 11 行创建了一个”Find”按钮。 在支持快捷键的平台上用户可以按下 Alt+F 来切换到这个按钮上。操作符’&’也可以用来控 制程序焦点:在第 6 行我们创建了一个拥有快捷键(Alt+W)的标签,在第 8 行我们给这个 标签设置一个伙伴(buddy):lineEdit。一个 buddy 就是当标签的快捷键被按下的时候,接 收程序焦点的窗口。所以,当用户按下 Alt+W 的时候,程序焦点转移到字符编辑框上。 在第 12 行,通过调用函数 setDefault(true),我们将按钮 Find 设置为程序的默认按钮(所 谓的默认按钮就是当用户按下回车键时被触发的按钮)。在第 13 行,我们将按钮 Find 设置 为不可用。当一个窗口被设置为不可用的时候,它通常显示为灰色,并不会和用户产生任何 交互。 15 connect (lineEdit, SIGNAL (textChanged (const QString &)), 16 this, SLOT (enableFindButton (const QString &))); 17 connect (findButton, SIGNAL (clicked ()), 18 this, SLOT (findClicked ())); 19 connect (closeButton, SIGNAL (clicked ()), 20 this, SLOT (close ())); 当字符编辑框中的文字被改变的时候,私有的槽 enableFindButton(const QString &)被调 用。私有槽 findClicked()在用户点击 Find 按钮时被调用。当用户点击关闭的时候,对话框将 关闭自身。槽 close()是从 QWidget 继承而来的,它的默认行为是隐藏窗口对象(而不是删 除它)。我们马上就能看到槽 enableFindButton()和 findClicked()的代码。 由于 QObject 是 FindDialog 的一个父类,所以我们可以在调用 connect()函数时忽略前 面的前缀 QObject:: 。 21 QHBoxLayout *topLeftLayout = new QHBoxLayout; 22 topLeftLayout->addWidget (label); 23 topLeftLayout->addWidget (lineEdit); 24 QVBoxLayout *leftLayout = new QVBoxLayout; 25 leftLayout->addLayout (topLeftLayout); 26 leftLayout->addWidget (caseCheckBox); 27 leftLayout->addWidget (backwardCheckBox); 28 QVBoxLayout *rightLayout = new QVBoxLayout; 29 rightLayout->addWidget (findButton); 30 rightLayout->addWidget (closeButton); 31 rightLayout->addStretch (); 32 QHBoxLayout *mainLayout = new QHBoxLayout; 33 mainLayout->addLayout (leftLayout); 34 mainLayout->addLayout (rightLayout); 35 setLayout (mainLayout); 接下来,我们使用布局管理器来对子窗口部件进行布局。布局管理器可以包括窗口, 也可以包括其它的布局管理器。通过对 QHBoxLayout,QVBoxLayout 和 QGridLayout 这三个布局管理类的嵌套使用,就可以生成非常复杂的对话框了。 如上图所示,对于对话框 Find,我们使用了两个 QHBoxLayout 和两个 QVBoxLayout。 最外层的布局是主要的布局,它在第 35 行被安装并负责响应对话框的全部区域。另外的三 个布局是子布局。图的右下方有一个“弹簧”,这是个空白的区域。在按钮 Find 和 Close 的下方使用空白是为了保证这些按钮出现在它们所在的布局的上方。 一个比较微妙的地方是布局管理类并不是窗口对象。它们从 QLayout 继承而来,而 QLayout 从 QObject 继承而来。在上图中,窗口以实线标记,而布局以虚线标记。在一个正在运行的程序当中,布局是不可见的。 当子布局被添加到父布局中的时候(代码的第 25 行,33 行和 34 行),子布局自动子 类化。当主布局被安装的时候(第 35 行),它成为了对话框的一个子类,所以在布局当中的 所有窗口对象都成为了对话框的子类。本例中各个类的继承层次在下图中表明。 36 setWindowTitle (tr ("Find")); 37 setFixedHeight (sizeHint ().height ()); 38 } 在代码的最后,我们将对话框标题栏的内容设置为“Find”,然后给窗口设置一个合适 的高度。由于这个对话框中没有任何子窗口可能占据多余的垂直空间,函数 QWidget::sizeHint()将会返回一个“理想”的大小。 考虑一下 FindDialog 的构造过程。由于我们使用了 new 来生成对话框的窗口和布局, 看起来我们应该为每一个窗口和布局编写一个析构函数来调用 delete。事实上这不是必须的, 因为在父窗口被销毁的时候,Qt 将会自动删除所有的子对象。本例中,所有的窗口和布局 都是从 FindDialog 继承而来,在对话框被关闭的时候,这些子对象也会被自动销毁。 现在我们看一下对话框的槽: 39 void FindDialog::findClicked () 40 { 41 QString text = lineEdit->text (); 42 Qt::CaseSensitivity cs = 43 caseCheckBox->isChecked ()? Qt::CaseSensitive 44 : Qt::CaseInsensitive; 45 if (backwardCheckBox->isChecked ()) { 46 emit findPrevious (text, cs); 47 } else { 48 emit findNext (text, cs); 49 } 50 } 51 void FindDialog::enableFindButton (const QString &text) { 52 findButton->setEnabled (! text.isEmpty ()); 53 } 当用户按下 Find 按钮时, 按钮会发射 findPrevious()或者 findNext()信号, 槽 findClicked() 会被调用。关键字 emit 在 Qt 里很特殊,和其它的 Qt 扩展名一样,它在被传递给标准 C++ 编译器之前会被 C++预处理器转换。 当用户改变字符编辑框中的内容时,槽 enableFindButton()被调用。也就是说,当字符 编辑框中有内容时,Find 按钮是可见的;当编辑框中没有内容的时候,Find 按钮不可见。 这两个槽被定义之后,我们关于这个对话框的内容就完成了。现在可以创建一个名为 main.cpp 的文件来试验一下 FindDialog 窗口。 1 #include 2 #include "finddialog.h" 3 int main (int argc, char *argv []) 4 { 5 QApplication app (argc, argv); 6 FindDialog *dialog = new FindDialog; 7 dialog->show (); 8 return app. exec (); 9 } 现在可以编译并运行这个程序了。如果你的平台支持快捷键,尝试着使用快捷键 Alt+W, Alt+C, Alt+B 和 Alt+F 来触发正确的行为。按下 Tab 键来切换各个窗口。默认的 tab 顺序在程序生成时已经确定。如果想更改这个顺序,可以调用函数 QWidget::setTabOrder()。 以上我们派生了 QDialog 来生成对话框。同样的道理,我们也通过可以派生 QMainWindow 来生成程序主窗口,然后在主窗口中创建菜单和工具条。也就是说,我们可 以通过只编写代码来生成一个完整的程序。 5、关于“信号和槽”(signal and slot) 通过上几节我们已经看到了“信号与槽”的运用。下面我们详细解释这个机制以及一些 相关的内容。使用信号与槽的基本格式为: connect (sender, SIGNAL (signal), receiver, SLOT (slot)); 这里的 sender 和 receiver 是指向 QObject 的指针,而 signal 和 slot 是无参数名的函数信 号。 “信号和槽”机制用于 Qt 对象间的通讯。 “信号/槽”机制是一种关于无缝对象通讯的 机制,它是 Qt 的一个中心特征,也是 Qt 与其它工具包的最不相同的部分。 在图形用户界面编程中,我们经常希望一个窗口部件的一个变化被通知给另一个窗口部 件。更一般地,我们希望任何一类的对象可以和其它对象进行通讯。例如,如果我们正在解 析一个 XML 文件,当我们遇到一个新的标签时,我们也许希望通知列表视图我们正在用来 表达 XML 文件的结构。 较老的工具包使用一种被称作回调(callback)的通讯方式来实现同一目的。回调是指 一个函数的指针,如果你希望一个处理函数通知你一些事件,你可以把另一个函数(回调) 的指针传递给处理函数。处理函数在适当的时候调用回调。回调有两个主要缺点: 首先他们 不是类型安全的。我们从来都不能确定处理函数使用了正确的参数来调用回调。其次回调和 处理函数是非常强有力地联系在一起的,因为处理函数必须知道要调用哪个回调。 在 Qt 中我们有一种可以替代回调的技术。我们使用信号和槽。当一个特定事件发生的 时候,一个信号被发射。Qt 的窗口部件有很多预定义的信号,但是我们总是可以通过继承 来加入我们自己的信号。 ‘槽’就是一个可以被调用处理特定信号的函数。Qt 的窗口部件也 有很多预定义的槽,但是通常的习惯是你可以加入自己的槽,这样你就可以处理你所感兴趣 的信号。 “信号和槽” 的机制是类型安全的:一个信号的签名必须与它的接收槽的签名相匹配(实 际上一个槽的签名可以比它接收的信号的签名少,因为它可以忽略额外的签名) 。因为签名 是一致的,编译器就可以帮助我们检测类型不匹配。信号和槽是宽松地联系在一起的:一个 发射信号的类不用知道也不用注意哪个槽要接收这个信号。Qt 的“信号和槽”的机制可以 保证如果你把一个信号和一个槽连接起来,槽会在正确的时间使用信号的参数而被调用。信 号和槽可以使用任何数量、任何类型的参数。它们是完全类型安全的:不会再有回调核心转 储(core dump)。 从 QObject 类, 或者它的一个子类(比如 QWidget 类) 所继承出的所有类,都可以包含 信号和槽。当对象改变它们的状态的时候,信号被发送,这就是所有的对象通讯时所做的一 切。它不执导也不注意有没有对象接收它所发射的信号。槽用来接收信号,但它们同时也是 对象中正常的成员函数。一个槽不知道它是否被任意信号连接。此外,对象并不知道关于这 种通讯的机制。你可以把很多信号和你所希望的单一槽相连,并且一个信号也可以和你所期 望的许多槽相连。把一个信号和另外一个信号直接相连也是可行的(这种情况下,只要第一 个信号被发射,第二个信号就会被立即发射)。 ▲ 一个小例子 一个最小的 C++类声明如下: class Foo { public: Foo (); int value () const {return val ;} void setValue (int); private: int val; }; 一个小的 Qt 类声明如下: Class Foo: public QObject { Q_OBJECT Public: Foo (); int value () const {return val ;} public slots: void setValue(int); signals: void valueChanged (int); private: int val; } 这个类有同样的内部状态,和公有方法访问状态,但是另外它也支持使用信号和槽的 组件编程:这个类可以通过发射一个信号: valueChanged()来告诉外界它的状态发生了变 化,并且它有一个槽,其它对象可以发送信号给这个槽。所有包含信号和/或者槽的类必须 在它们的声明中提到 Q_OBJECT。 槽可以由应用程序的编写者来实现。这里是 Foo::setValue()一个可能的实现: V oid Foo::setValue (int v) { if (v != val) { val = v; emit valueChanged (v); } } emit valueChanged(v)这一行从对象中发射 valueChanged 信号。正如你所能看到的,你 通过使用 emit signal(arguments)来发射信号。 下面是把两个对象连接在一起的一种方法: Foo a, b; connect (&a, SIGNAL (valueChanged (int)), &b, SLOT (setValue (int))); b.setValue (11); // a == undefined b == 11 a.setValue (79); // a == 79 b == 79 b.value (); 调用 a.setValue(79)会使 a 发射一个 valueChanged() 信号, b 将会在它的 setValue() 槽中接收这个信号,也就是 b.setValue(79) 被调用。接下来 b 会发射同样的 valueChanged() 信号,但是因为没有槽被连接到 b 的 valueChanged()信号,所以没有发生任何事(信号消 失了)。 注意:只有当 v != val 的时候 setValue()函数才会设置这个值并且发射信号。这样就 避免了在循环连接的情况下(比如 b.valueChanged() 和 a.setValue()连接在一起)出现无 休止的循环的情况。 这个例子说明了对象之间可以在互相不知道的情况下一起工作,只要在最初的时在它 们中间建立连接。 预处理程序改变或者移除了 signals、slots 和 emit 这些关键字,这样就可以使用标 准的 C++编译器。 在一个定义有信号和槽的类上运行 moc。这样就会生成一个可以和其它对象文件编译 和连接成引用程序的 C++源文件。 ▲ 信号 当对象的内部状态发生改变,信号就被发射,只有定义了一个信号的类和它的子类才能 发射这个信号。 例如,一个列表框同时发射 highlighted()和 activated()这两个信号。绝大多数对象也 许只对 activated()这个信号感兴趣,但是有时我们也想知道列表框中的哪个条目在当前是 高亮的。如果两个不同的类对同一个信号感兴趣,你可以把这个信号和这两个对象连接起来。 当一个信号被发射,它所连接的槽会被立即执行,就像一个普通函数调用一样。信号/ 槽机制完全不依赖于任何一种图形用户界面的事件回路。当所有的槽都返回后 emit 也将返 回。 如果几个槽被连接到一个信号,当信号被发射时,这些槽就会被按任意顺序一个接一个 地执行。 信号会由 moc 自动生成并且一定不要在.cpp 文件中实现。它们也不能有任何返回类型 (比如使用 void)。 关于参数需要注意。我们的经验显示如果信号和槽不使用特殊的类型,它们都可以多次 使用。如果 QScrollBar::valueChanged() 使用了一个特殊的类型,比如 hypothetical QRangeControl::Range,它就只能被连接到被设计成可以处理 QRangeControl 的槽。 ▲ 槽 当一个和槽连接的信号被发射的时候,这个操被调用。槽也是普通的 C++函数并且可 以像它们一样被调用;它们唯一的特点就是它们可以被信号连接。槽的参数不能含有默认值, 并且和信号一样,为了槽的参数而使用自己特定的类型是很不明智的。 因为槽就是普通成员函数,但却有一点非常有意思的东西,它们也和普通成员函数一 样有访问权限。一个槽的访问权限决定了谁可以和它相连: 一个 public slots:区包含了任何信号都可以相连的槽。这对于组件编程来说非常有用: 你生成了许多对象,它们互相并不知道,把它们的信号和槽连接起来,这样信息就可以正确 地传递,并且就像一个铁路模型,把它打开然后让它跑起来。 一个 protected slots:区包含了之后这个类和它的子类的信号才能连接的槽。这就是说这 些槽只是类的实现的一部分,而不是它和外界的接口。 一个 private slots:区包含了之后这个类本身的信号可以连接的槽。这就是说它和这个类 是非常紧密的,甚至它的子类都没有获得连接权利这样的信任。 你也可以把槽定义为虚的,这在实践中被发现也是非常有用的。 6、关于元对象系统(Meta-Object System) Qt 的一个最主要的特点可能就是它扩展了 C++的机制,可以创建独立的软件组件,这 些组件可以被绑定在一起,而不需要互相的任何了解。 这个机制被成为元对象系统,它提供了两个关键服务:信号/槽、运行时的类型信息和 动态属性系统(内省机制)。内省机制对于实现信号和槽是必须的,并且允许应用程序员在 程序运行时获得“元信息”(包括被对象支持的信号和槽的列表,以及这些信号/槽所在的类 的名称)。内省机制同时支持“道具”(对于 Qt Designer)和文本翻译(国际化) ,它还是 Qt 应用程序脚本(Qt Script for Application)的基础。 标准的 C++并不提供对于 Qt 的元对象系统所需要的动态元信息的支持。Qt 提供了一 个单独的工具:元对象编译器(moc)来解决这个问题。Moc 用来解析 Q_OBJECT 类的定 义,使这些信息在 C++函数中可用。由于 moc 使用纯粹的 C++函数来实现,所以 Qt 的元对 象系统在任何 C++编译器下都可以工作。 元对象系统这样工作: ● Q_OBJECT 宏声明一些内省函数(metaObject(),TR(),qt_matacall()和少量其他的函 数)。这些函数必须在所有的 QObject 的子类中被实现。 ● Qt 的 moc 工具负责执行被 Q_OBJECT 宏声明的函数,同时负责执行所有的信号 函数。 ● QObject 的成员函数,例如 connect()和 disconnect(),使用内省函数来工作。 元对象系统基于以下三类: 1)、QOBJECT 类; 2)、类声明中的私有段的 Q_OBJECT 宏; 3)、元对象编译器。 Moc 读取 C++源文件。如果它发现其中包含一个或多个类的声明中含有 Q_OBJECT 宏, 它就会给含有 Q_OBJECT 宏的类生成另一个含有元对象代码的 C++源文件。这个生成的源 文件可以被类的源文件包含(#include)到或者和这个类的实现一起编译和连接。 除了提供对象间通讯的信号和槽机制之外(这也是介绍这个系统的主要原因), QObject 中的元对象代码也实现其它特征: 1)、className()函数在运行的时候以字符串返回类的名称,不需要 C++编译器中的运 行时刻类型识别(RTTI)的支持。 2)、 inherits()函数返回这个对象是否是一个继承于 QObject 继承树中一个特定类的类的 实例。 3)、tr()和 trUtf8()两个函数是用于国际化的字符串翻译。 4)、setPorperty()和 property()两个函数是用来通过名称动态设置和获得对象属性的。 5)、metaObject()函数返回这个类所关联的元对象。 虽然使用 QObject 作为一个基类而不使用 Q_OBJECT 宏和元对象代码是可以的,但是 如果 Q_OBJECT 宏没有被使用,那么这里的信号和槽以及其它特征描述都不会被提供。根 据元对象系统的观点,一个没有元代码的 QObject 的子类和它含有元对象代码的最近的祖先 相同。举例来说就是, className()将不会返回你的类的实际名称,返回的是它的这个祖先的 名称。我们强烈建议 QObject 的所有子类使用 Q_OBJECT 宏,而不管它们是否实际使用了信号、槽和属性。 |
|
相关推荐 |
|
只有小组成员才能发言,加入小组>>
3023 浏览 3 评论
1509 浏览 3 评论
4627 浏览 1 评论
2001 浏览 1 评论
3232 浏览 2 评论
499浏览 1评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-5 10:53 , Processed in 1.325439 second(s), Total 39, Slave 30 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号