ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【QT】 信号与槽初始

【QT】 信号与槽初始 前言这篇就来认识Qt中的信号与槽。1.从控件到信号与槽显示一段Hello world并不只有标签这一种方式。Qt提供了不少和文字有关的控件先简单区分一下控件主要用途QLabel显示文本、图片等内容QLineEdit单行文本输入QTextEdit多行文本编辑QPushButton普通按钮文字通常作为按钮的说明比如在窗口构造函数中可以通过下面两行代码创建一个单行输入框QLineEdit*editnewQLineEdit(this);edit-setText(Hello world!);使用时包含QLineEdit。new QLineEdit(this)创建输入框并把当前窗口设置为父对象setText()设置其中的文字。这和前面创建标签的思路基本一致。这些控件都直接或间接继承自QWidget而QWidget又继承自QObject。可以先看下面这张简化关系图其实还有一些中间基类比如QPushButton和QRadioButton还经过QAbstractButton这里主要是认识它们都在这个继承体系中能够使用Qt提供的对象通信机制。控件有了接下来就需要考虑交互。比如用户点了一下按钮程序应该修改文字还是关闭窗口按钮本身并不知道我们的业务要求这时就需要把用户操作与处理代码联系起来。信号可以理解成对象发出的一条通知槽则是用来响应这条通知的函数。对于按钮clicked表示发生了点击我们再准备一个处理函数让Qt在收到这个信号时调用它。建立这层关系的函数就是connect()。这里先关注四件事哪个对象发出信号、发出什么信号、由哪个对象接收、接收后执行什么函数。发送者和接收者不一定都是界面控件本篇先用按钮与窗口来理解。Qt中的信号与槽属于对象之间的通信机制和Linux中的进程信号不是同一套东西。要让某次点击触发我们的处理函数需要在点击发生前建立连接。2.connect的基本使用2.1点击按钮关闭窗口先看一个直接的例子窗口上放一个按钮点击它就关闭当前窗口。使用QPushButton需要包含QPushButton。下面是构造函数中负责这一功能的部分放在ui-setupUi(this);之后// 设置位置QPushButton*buttonnewQPushButton(this);button-setText(我是按钮);button-move(200,300);// close 是 QWidget 内置的槽函数Widget 继承 QWidget 所以可以使用connect(button,QPushButton::clicked,this,Widget::close);前三行完成按钮的创建、文字设置和位置设置。move(200, 300)把按钮放到相对于父控件的这个位置真正把点击和关闭联系起来的是最后一行。把四个参数拆开看参数在这里表示什么button信号发送者也就是这个按钮对象QPushButton::clicked要连接的点击信号this信号接收者也就是当前Widget窗口对象Widget::close接收后调用的成员函数这里用于关闭窗口连起来就是当button发出clicked信号时调用当前窗口的close()。这里使用的是QObject提供的静态connect()重载。我们的Widget继承了QWidget而QWidget属于QObject的继承体系所以可以在成员函数中直接这样调用。connect()执行时是在建立联系。后面按钮真正发出信号才会触发相应处理。再看第二、第四个参数它们是成员函数指针用来告诉Qt要连接哪一个信号、哪一个处理函数。Widget是我们自己定义的窗口类QWidget才是Qt提供的类。close()虽然不是我们在Widget里重新实现的但它从基类继承而来所以可以写Widget::close。还有一点关闭窗口与立即删除窗口对象不是一回事。对于这里没有额外修改关闭行为的窗口close()会关闭并隐藏它对象什么时候析构还要看它的生命周期和相关设置。可以查考QWidget的close说明。2.2clicked刚开始写这行代码时还有一个很容易混淆的地方补全列表里既有click又有clicked它们有什么区别click()是一个槽函数可以通过代码调用它模拟一次按钮点击clicked则是按钮被点击时发出的信号。我们希望响应点击所以connect()的第二个参数使用的是clicked。如果在QPushButton自己的接口列表中没有直接找到clicked可以继续看它的父类QAbstractButton。Qt把不同按钮共有的功能放到了这个基类中clicked就是继承来的信号。QAbstractButton的clicked说明查文档时除了看名字还要看这个信号什么时候发出、参数表达什么含义以及相关功能是不是从父类继承的。把这些弄清楚比只记住一行connect()更有用但现在有了AI确实也更方便但想要更准确权威的答案还是得要参考官方提供的可能再过几年就不一定了。3.通过槽函数切换按钮文字关闭窗口使用的是Qt已经提供好的函数。如果想实现自己的处理逻辑实现一些定制化的需求例如让按钮文字在Hello world与Hello Qt之间切换就可以自己写一个函数。下面分成纯代码和Designer两种方式。它们是同一个效果的不同写法分别使用即可不要把关闭窗口和切换文字的几组示例直接叠在一起。3.1通过代码创建按钮先看窗口类的声明也就是widget.h#ifndefWIDGET_H#defineWIDGET_H#includeQWidget#includeQPushButtonQT_BEGIN_NAMESPACEnamespaceUi{classWidget;}QT_END_NAMESPACEclassWidget:publicQWidget{Q_OBJECTpublic:Widget(QWidget*parentnullptr);~Widget();voidHandleClick();private:Ui::Widget*ui;QPushButton*MyButton;};#endif// WIDGET_H这里主要关注新增的两项HandleClick()是我们自己的点击处理函数MyButton用来保存按钮的地址让另一个成员函数也能够访问这个按钮。前面关闭窗口的示例中按钮指针只是构造函数里的局部变量因为建立连接后不需要再通过另一个成员函数读取它。现在HandleClick()要修改按钮文字所以这里把指针保存为窗口类的成员。保留构造函数已有的ui(new Ui::Widget)初始化和ui-setupUi(this);把前面创建关闭按钮的那几行替换为下面这组代码MyButtonnewQPushButton(this);MyButton-setText(Hello world);connect(MyButton,QPushButton::clicked,this,Widget::HandleClick);这次信号发送者变成了MyButton接收者仍然是当前窗口只是最后一个参数换成了Widget::HandleClick。接着在widget.cpp中实现处理函数voidWidget::HandleClick(){if(MyButton-text()QString(Hello world)){MyButton-setText(Hello Qt);}else{MyButton-setText(Hello world);}}text()用来取得按钮当前显示的文字setText()则用来修改文字。第一次点击时原来的文字是Hello world于是进入if分支把它改成Hello Qt再点击一次就进入else分支改回Hello world。整个过程就是按钮被点击发出clicked信号Qt调用当前窗口的HandleClick()这个函数再修改按钮文字。3.2通过Designer创建按钮也可以在Designer中直接拖入一个QPushButton。这样按钮的创建代码由界面生成流程负责我们只需要找到这个按钮再建立连接。先在widget.ui中放入一个按钮把它的objectName设为pushButton初始text设为Hello world。这两个属性很容易混在一起实际上用途不同属性用途objectName对象名称界面生成代码会用相应名称提供访问成员text按钮在界面上显示的文字按钮上显示Hello world不代表代码中要写ui-Hello world。如果它的objectName是pushButton对应的访问方式就是ui-pushButton。这里再把界面生成过程接起来看构建时uic读取widget.ui生成ui_widget.h其中的界面辅助类保存控件指针并提供setupUi()来创建和设置控件。qmake负责组织这一步构建工作真正把.ui转换成C代码的是uic。UI文件的使用方式new Ui::Widget创建的是辅助对象随后调用ui-setupUi(this)才会完成界面控件的创建。因此下面这行连接要放在ui-setupUi(this);之后connect(ui-pushButton,QPushButton::clicked,this,Widget::HandleClick);这一版不需要再执行MyButton new QPushButton(this);那组三行也不用再次手动创建同一个按钮。widget.cpp仍然需要包含ui_widget.h才能访问界面辅助类中的成员。voidWidget::HandleClick(){if(ui-pushButton-text()QString(Hello world)){ui-pushButton-setText(Hello Qt);}else{ui-pushButton-setText(Hello world);}}这里仍然是在同一个HandleClick()中做文字切换变化的只是访问按钮的方式。前一版通过我们自己保存的MyButton访问这一版通过界面辅助对象中的ui-pushButton访问。对于比较固定的界面用Designer安排控件通常比较方便需要动态创建控件时直接写代码也很自然。这两种方式可以结合使用最后创建出来的仍然是Qt控件信号与槽的连接思路没有变化, 哪个方便就用哪个。4.connect的两种写法4.1SIGNAL与SLOT写法查看Qt文档或者一些已有代码时还会遇到另一种connect()形式。它的第二、第四个参数是const char*看起来和前面传入的成员函数指针完全不同这张图展示的是字符串形式的连接重载。发送者、信号、接收者、处理函数这四个要素仍然存在但信号与槽的表达方式不同。仍然以点击按钮关闭窗口为例对照写法如下connect(button,SIGNAL(clicked(bool)),this,SLOT(close()));这里的SIGNAL()和SLOT()是宏。宏里面写的是函数名和参数类型比如clicked(bool)、close()不写类名、返回类型也不写QPushButton::clicked这样的成员函数指针表达式。它们会把相应的函数签名文字转换成Qt需要的字符串形式再由Qt查找对应的信号与槽。这不是把一个函数指针强行转换成char*。字符指针与成员函数指针是不同类型两种写法能够工作是因为调用了不同的connect()重载。4.2成员函数指针写法从Qt 5开始可以使用前面一直在写的这种形式connect(button,QPushButton::clicked,this,Widget::close);旧的宏形式仍然可以使用只是这里更适合通过成员函数指针把对象与接口直接对应起来。如果继续看QObject::connect的相关实现就会发现这种重载使用了模板Func1、Func2分别对应传入的信号和处理函数类型QtPrivate::FunctionPointer用于提取相关类型信息。这里先看懂它为什么能够接收不同类的成员函数即可因为无我也没去研究。后面继续补充信号和槽完
返回列表