一、C++的信号和QML的槽
前言:
Qt中的信号与槽,通常是一个信号SIGNAL和一个槽SLOT,通过connet连接,而QML中不需要再写槽函数,只需要在合适的地方告诉QML:如果x信号产生则执行x
如有信号:
Class A{
signals:
void rcvData(QString str);
};
则在QML里:
onRcvData:{
//执行x如console.log(str);
}
格式:在信号前面加on,然后信号首字母大写,注意参数可执行使用
1、如果注册的是C++类,那么可以在QML中实例化对象的时候直接捕获
A{
id:a
onRcvData:{
console.log(str)
}
}
2、如果注册的是C++对象,则需要Connections连接
Connections {
target: a
onRcvData: {
console.log(str)
}
}
注意:在高版本QT5.15中,已经对这种方式进行了改变:
Connections {
target: button
function onRcvData() {
//相关事件
}
}
否则报警告:QML Connections: Implicitly defined onFoo properties in Connections are deprecated
二、QML的信号和QML的槽
使用信号的connect函数连接槽【函数】
sendSignal.connect(target.recvSlot)
在合适的地方如Component.onCompleted里连接信号与槽,sendSignal当前对象是定义的信号,recvSlot实际上是一个function
三、汇总
1、连接qml中信号与函数
Component.onCompleted:
{
sendMessageSigal.connect(getMessage);
}
2、连接 qml中函数与c++中信号
Connections
{
//qml 连接 c++ 信号
target:interaction
onConnectcppsignal:
{
label2.text=strMsg;
}
}
3、qml中直接调用c++函数
onClicked:
{
//qml 调用 c++函数
interaction.test();
}
4、连接c++中函数与qml中信号
#include <QQuickItem>
//指定对象
QObject* quitButton = root->findChild<QObject*>("quitButton");
if (quitButton){
QObject::connect(quitButton, SIGNAL(clicked()), &app, SLOT(quit()));
}
#include <QQuickItem>
//根对象
QObject *root = ui->quickWidget_lianlutongxin->rootObject();
if (root){
QObject::connect(root, SIGNAL(beginTest(int, bool)), this, SLOT(beginTestSlot(int, bool)));
}
5、c++中直接调用qml中函数
QObject* textLabel = root->findChild<QObject*>("objNameL4");
if (textLabel)
{
QMetaObject::invokeMethod(textLabel, "setL4text");
}
6、c++连接信号与槽函数
QObject::connect(this,&CInteraction::valueChanged,this,&CInteraction::setValue); //qt5 新语法
7、c++ 连接信号与普通函数
QObject::connect(this,&CInteraction::valueChanged,outPutInfo);
8、c++ 连接信号与lambda函数
QObject::connect(this,&CInteraction::valueChanged,[=,this](){ qDebug()<<"lambda called, value:"<<this->value_<<"\n"; });
/**********************************************************************
信号和槽(signals & slots)是Qt框架的核心机制,Qt C++中的槽函数对应QML中的信号处理程序(signal handler)。
信号是事件(event),信号通过信号处理程序响应。发出信号时,将调用相应的信号处理程序。在处理程序中放置诸如脚本或其他操作之类的逻辑允许组件响应事件。
第一个例子:信号处理程序
//以下所有示例都在Qt5.9.7下测试
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
//按键点击的信号处理程序
onClicked: {
console.log("on clicked");
}
}
}
如例子所示,若要接收特定对象发出的特定信号,对象定义应声明一个名on<Signal>的信号处理程序,其中<Signal>是信号的名称,首字母大写。信号处理程序应包含在调用信号处理程序时要执行的JavaScript代码。
当MouserArea的clicked信号触发,onClicked处理程序就会被调用。
第二个例子:自定义信号及触发
除了处理预定义的信号,还可以自己定义和触发信号。
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id:root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle{
id:rect
anchors.fill: parent
color: "green"
//自定义属性
property int click_counts: 0
//自定义信号 --无参信号可以省略括号
signal signalA
signal signalB(string str,int value)
MouseArea {
anchors.fill: parent
onClicked: {
//按键点击,计数加一
rect.click_counts++;
//调用函数的形式来触发信号
rect.signalB("signal counts",rect.click_counts);
if(rect.click_counts%5===0){
rect.signalA(); //点击五次触发
}
}
}
//信号处理函数
onSignalA: {
console.log("click 5");
}
onSignalB: {
//可以直接使用形参变量名
console.log(str,value);
}
}
}
可以发现定义一个信号很简单,使用signal关键字就行了:signal <name>[([<type> <parameter name>[, ...]])]
在Qt C++通过emit来发射信号,而在QML中直接将声明的信号当作函数来调用就可以触发了。而在信号处理函数中,可以使用声明中的变量名来获取对应的参数,如clicked信号带一个mouse参数,可以访问他获取点击时坐标等信息。
第三个例子:属性改变信号
在QML中,当属性(property)值改变时,会自动触发信号。
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id:root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
property int counts: 0
//预定义属性改变
onHeightChanged: {}
//自定义属性改变
onCountsChanged: {
console.log("click counts",counts);
}
MouseArea {
anchors.fill: parent
//修改属性,触发信号
onClicked: root.counts++
}
}
属性改变信号处理程序以on<Property>Changed的形式关联,<Property>是属性的名称,首字母大写。
第四个例子:附加信号处理程序
一个附加信号处理程序是从接收信号的信号处理程序附加类型(attaching type)而不是在其内的处理程序被声明的对象。
例如,Component.onCompleted是附加的信号处理程序。此处理程序通常用于在其创建过程完成时执行某些JavaScript代码。
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id:root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id:rect
anchors.fill: parent
color: "yellow"
focus: true
//附加属性
Keys.enabled: true
//附加信号处理程序
Keys.onPressed: {
if(event.key===Qt.Key_0)
console.log("key 0 pressed");
}
//附加信号处理程序
Component.onCompleted: {
//创建完成时执行
console.log("color is", color)
}
}
}
附加属性的语法格式为:<AttachingType>.<propertyName>
附加信号处理程序的语法格式为:<AttachingType>.on<SignalName>
要了解更多的附加类型相关,可以查看文档。
第五个例子:连接任意对象的信号
在某些情况下,可能需要访问发射它的对象外部的信号(In some cases it may be desirable to access a signal outside of the object that emits it.)。出于这些目的,QtQuick模块提供Connections类型以连接任意对象的信号。一个Connections对象可以接收来自它的指定目标(target)的任何信号。
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id:root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id:rect
anchors.fill: parent
color: "yellow"
MouseArea {
id:mouse_area
anchors.fill: parent
}
Connections{
target: mouse_area
//在外部连接MouseArea的点击信号
onClicked:{
console.log("clicked");
}
}
}
}
第六个例子:信号到方法/信号的连接
信号对象具有connect()将信号连接到方法或另一信号的方法。当信号连接到方法时,无论何时发出信号,都会自动调用该方法。该机制使得能够通过方法而不是信号处理器来接收信号。可以一个信号connect()多个方法,并且可以对动态创建的对象使用connect()。亦可以使用disconnect()方法来取消关联。
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id:root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id:rect
anchors.fill: parent
color: "purple"
signal signalA(string str)
signal signalB(string str)
MouseArea {
anchors.fill: parent
onClicked: {
rect.signalA("gong jian bo 1992")
}
}
Component.onCompleted: {
//信号--方法
rect.signalA.connect(methodA)
//信号--信号
rect.signalA.connect(rect.signalB)
rect.signalB.connect(methodB)
rect.signalB.connect(methodC)
}
//函数可以放到js文件中
function methodA(str){
console.log("method a",str)
}
function methodB(str){
console.log("method b",str)
}
function methodC(){
console.log("method c")
}
}
}
通过示例可以发现,用connect()可以很方便的关联信号和方法/信号。
在Qt C++中,信号槽的关联需要信号和槽的参数匹配(槽的参数个数不能多于信号定义的个数),且从左至右一一对应。但是在测试过程中,我发现由于js函数参数机制不一样,导致函数参数个数可以任意,只是未匹配的参数值为undefined而已。
/******************************************Qt中C++与QML交互
###main.c部分
int main(int argc, char *argv[])
{
QString info1 = "xxxxxxxxxxx";
QString info2 = "yyyyyyyyy";
。。。。
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QObject *root = engine.rootObjects().first();
QObject *info1 = root->findChild<QObject *>("info1");
info1->setProperty("text", "info1: "+QString(info1));
QObject *info2 = root->findChild<QObject *>("info2");
info2->setProperty("text", "info2: "+QString(info2));
。。。
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
###main.qml部分
Item{
id:window
....
Text {
objectName:"info"
id: info1
x: 20
y: 20
z: 1
width: 700
height: 130
text: "info1:"
color: "green"
font.pixelSize: 50
font.family: "WenQuanYi Micro Hei"
font.bold: true
}
Text {
objectName:"info2"
id: info2
x: 20
y: 180
z: 1
width: 700
height: 130
text: "info2:"
color: "green"
font.pixelSize: 50
font.family: "WenQuanYi Micro Hei"
font.bold: true
}
....
}
======================================================
一、C++中调用QML对象
所有的QML对象类型,包括QML引擎内部实现或者实现第三方库,都是QObject子类,都允许QML引擎使用Qt元对象系统动态实例化任何QML对象类型。
在启动QML时,会初始化一个QQmlEngine作为QML引擎,然后使用QQmlComponent对象加载QML文档,QML引擎会提供一个默认的QQmlContext对象作为顶层执行的上下文,用来执行QML文档中定义的函数和表达式。
QQmlEngine::rootContext() 返回当前引擎QML的上下文,唯一的,QQmlContext* QQuickView::rootContext()
QQuickItem* QQuickView::rootObject() 返回当前QQuickView的根节点,也就是QML的根节点
1、使用C++加载QML对象
(1)使用QQmlComponent进行加载,读取QML文档,将转换成C++对象,进行赋值操作。
例子:
QQmlEngine engine; //QML引擎
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:///main.qml"))); //加载QML
//用QQmlComponent创建一个组件的实例,并且赋值给object*,这步操作非常关键,Object类型可以转换其他任意类型,比如QQuickItem
QObject* object = component.create();
object->setProperty("width", 500); //元对象系统赋值操作
QQmlProperty(object, "width").write(500); //元对象系统赋值操作
QQuickItem* item = qobject_cast<QQuickItem*>(object); //把 QObject* 转换成 QQuickItem* 类型
tiem->setWidth(500); //QQuickItem* 赋值操作
(2)使用QQuickView加载,QQuickView是继承QWindow,所有可以加载一个可视化QML对象,并且可以与应用程序的图形用户界面进行融合。
例子:
QQuickView view; //QQuickView对象
view.setSource( QUrl(QStringLiteral("qrc:///main.qml"))); //加载QML
view.show(); //QQuickView可以显示可视化QML对象
QQuickItem* item = view.rootObject(); //返回当前QQuickView的根节点
tiem->setWidth(500); //QQuickItem* 赋值操作
2、使用对象名字访问加载的QML对象
QML中的所有节点都会绑定到根节点树上,QObject::objectName这个属性保存特定对象。QML组件的子对象可以在C++中通过 QObject::findChild()查找到在QML中用objectName定义的对象。
bool QObject::setProperty(const char *name, const QVariant &value) 元对象系统的设置函数
T QObject::findChild(const QString &name = QString(), Qt::FindChildOptionsoptions = Qt::FindChildrenRecursively) const 是一个QObject类型的模板函数,意味着可以转成任意类型如:
QPushButton* button = root.findChild<QPushButton*>("qml_button")
QObject* object = root.findChild<QObject*>("qml_object")
QQuickItem* item = root.findChild<QQuickItem*>("qml_item")
如果有多个对象使用objectName:"qml_button"同名标记,QObject::findChild返回最后一个标记的QML对象,QObject::findChildren返回所有标记的QML对象存放在QList类型的列表中。
例子:
QQuickView view; //QQuickView对象
view.setSource( QUrl(QStringLiteral("qrc:///main.qml"))); //加载QML
view.show(); //QQuickView可以显示可视化QML对象
QQuickItem* root = view.rootObject(); //返回当前QQuickView的根节点,底下可以绑定很多节点
//在根节点root中查找有objectName:"qml_button"这个标志位保存的QML节点qml_Button
QObject* button = root->findChild<QObject*>("qml_button");
button->setProperty("width", 500);
//在根节点root中查找有objectName:"qml_item"这个标志位保存的QML节点qml_item,换成QQuickItem*类型
QQuickItem* item = root->findChild<QQuickItem*>("qml_item");
item->setProperty("color", "red");
3、使用C++访问QML对象成员
(1)所有的QML对象都会暴露在Qt的元对象系统,C++可以通过元对象系统的QMetaObject::invokeMethod()调用QML中注册到元对象系统函数。
例子:
qml中定义的函数:
function qmlFunction(msg)
{
console.log("QML get message:",msg);
}
C++调用QML函数:
QQmlEngine engine; //QML引擎
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:///main.qml"))); //加载QML
QObject* object = component.create(); //用QQmlComponent创建一个组件的实例,并且赋值给object*,这步操作非常关键,Object类型可以转换其他任意类型,比如QQuickItem
QVariant rValue;
QVariant msg = "Hello for C++";
QMetaObject::invokeMethod(object, "qmlFunction", Q_RETURN_ARG(QVariant,rValue), Q_ARG(QVariant, msg));
(2)C++可以接收所有的QML信号,QML也可以接收C++信号,在C++中可以使QObject::connect()进行接收信号槽。
例子:
qml中定义一个信号:
signal qmlSignal(string msg)
C++进行连接信号:
QQuickView view; //QQuickView对象
view.setSource( QUrl(QStringLiteral("qrc:///main.qml"))); //加载QML
view.show(); //QQuickView可以显示可视化QML对象
QQuickItem* root = view.rootObject(); //返回当前QQuickView的根节点,底下可以绑定很多节点
QObject::connect(root, SIGNAL(qmlSignal(QString)), this, SLOT(Slotqml(QString)));