事件处理之信号与槽----(Qt Quick 教程四)

  我们先看一个简单的示例, qml 中只有一个退出按钮,点击退出应用。 qml 文档为 button_quit.qml ,内容如下:

import QtQuick 2.0
import QtQuick.Controls 1.1
 
Rectangle {
    width: 320;
    height: 240;
    color: "gray";
    
    Button {
        text: "Quit";
        anchors.centerIn: parent;
        onClicked: {
            Qt.quit();
        }
    }
}

使用 qmlscene 执行 button_quit.qml 效果如图 5 所示:


            

    现在看看代码有何特别之处。其实在《Qt on Android: Qt Quick 简单教程》中我们已经见过类似的代码了: 

onClicked:{} 

    对,就是这个 onClicked ,其实就包含了 QML  中使用信号与槽的一般形式:信号处理器。

 

信号处理器

让我们回头再看信号处理器,它的名字还有点儿特别,一般是 on{Signal} 这种形式。在上节的示例中, Button 元素有一个名为 clicked() 的信号,我们提供的信号处理器是酱紫的:

        onClicked: {
            Qt.quit();
        }

    非常简单吧,仅仅是调用 Qt.quit() 来退出应用而已。
    Qt 对象是 Qt Quick 导出到 QML 环境中的对象,它的 quit() 方法退出应用。还有很多其它的方法,比如 rgba() 用于构造一个颜色(color类型), md5() 用来计算一段数据的 MD5 值……

    你看到了,当信号是 clicked() 时,信号处理器就命名为 onClicked 。就这么简单,以 on 起始后跟信号名字(第一个字母大写)。如果你点击我们的 quit 按钮,应用就真的退出了。

    上面的示例,信号处理器放在拥有信号的元素内部,当元素信号发射时处理器被调用。还有一种情况,要处理的信号不是当前元素发出来的,而是来自其它类型(对象)比如处理按键的 Keys ,这就是附加信号处理器。
 

附加信号处理器

在 QML 语言的语法中,有一个附加属性(attached properties)和附加信号处理器(attached signal handlers)的概念,这是附加到一个对象上的额外的属性。从本质上讲,这些属性是由附加类型(attaching type)来实现和提供的,它们可能被附加到另一种类型的对象上。附加属性与普通属性的区别在于,对象的普通属性是由对象本身或其基类(或沿继承层级向上追溯的祖先们)提供的。

   举个例子,下面的 Item 对象使用了附加属性和附加信号处理器:
 

import QtQuick 2.0
 
Item {
    width: 100; 
    height: 100;
 
    focus: true;
    Keys.enabled: false;
    Keys.onReturnPressed: console.log("Return key was pressed");
}

    你看, Item 对象可以访问和设置 Keys.enabled 和 Keys.onReturnPressed 的值。
    enabled 是 Keys 对象的一个属性。 

    onReturnPressed 其实是 Keys 对象的一个信号。

    对于附加信号处理器,和前面讲到的普通信号处理器又有所不同。普通信号处理器,你先要知道信号名字,然后按照 on{Signal} 的语法来定义信号处理器的名字;而附加信号处理器,信号名字本身已经是 onXXX 的形式,你只要通过附加类型名字引用它,把代码块赋值给它即可。下面是另外的代码片段:
 

Rectangle {
    width: 320;
    height: 480;
    color: "gray";
    
    focus: true;
    Keys.enabled: true;
    Keys.onEscapePressed: {
        Qt.quit();
    }
}

    Component 对象也有一些附加信号,如 Component.onCompleted() 、 Component.onDestruction() 。可以用来在 Component 创建完成或销毁时执行一些 JavaScript 代码来做与初始化或反初始化相关的工作。比如下面的代码:
 

Rectangle {
    Component.onCompleted: console.log("Completed Running!");
    Component.onDestruction: console.log("Destruction Beginning!");
}

    信号处理器与附加信号处理器有一个共性:响应信号的代码都放在元素内部,通过 JavaScript 代码块就地实现。而其实呢, Qt Quick 中还有另外一种方式来处理信号与槽,那就是:专业的 Connections 。

 

Connections

一个 Connections 对象创建一个到 QML 信号的连接。

前面两节在处理 QML 信号时,都是用 on{Signal} 这种就地代码块的方式。而在有些情况下,这样的处理并不方便。比如:

  • 你需要将多个对象连接到同一个 QML 信号上
  • 你需要在发出信号的对象的作用域之外来建立连接
  • 发射信号的对象没有在 QML 中定义(可能是通过 C++ 导出的,这很常见)

 

    Connections 有一个属性名为 target ,它呢,指向发出信号的对象。

    下面就看看 Connections 怎么使用。一般的用法:

Connections {
    target: area;
    on{Signal}: function or code block;
}

来看一个实际的示例,是酱紫的:界面上放置两个文本,一个按钮,每点按钮一次,两个文本对象都变颜色,而它们的颜色随机的。下面是示例代码:

import QtQuick 2.0
import QtQuick.Controls 1.1
 
Rectangle {
    width: 320;
    height: 240;
    color: "gray";
    
    Text {
        id: text1;
        anchors.horizontalCenter: parent.horizontalCenter;
        anchors.top: parent.top;
        anchors.topMargin: 20;
        text: "Text One";
        color: "blue";
        font.pixelSize: 28;
    }
    
    Text {
        id: text2;
        anchors.horizontalCenter: parent.horizontalCenter;
        anchors.top: text1.bottom;
        anchors.topMargin: 8;
        text: "Text Two";
        color: "blue";
        font.pixelSize: 28;
    }
    
    Button {
        id: changeButton;
        anchors.top: text2.bottom;
        anchors.topMargin: 8;
        anchors.horizontalCenter: parent.horizontalCenter;
        text: "Change";
    }
    
    Connections {
        target: changeButton;
        onClicked: {
            text1.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
            text2.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
        }
    }
}

 

  代码比较简单,除了 Connections 和 Math ,没有其它的新内容,都是 《Qt Quick 简单教程》和《QML 语言基础》中讲过的。我在 Connections 对象中指定 target 为 changeButton (Change按钮的 id),然后定义了 onClicked 信号处理器,在信号处理器中使用 Math 对象的 random() 方法构造颜色值来改变两个文本的颜色。
 

 

 到现在为止,我们说的都是如何使用 QML 中已有类型定义的信号。这些信号呢,其实又分类两类。一类是由用户输入产生的,比如按键、鼠标、触摸屏、传感器等,另一类呢是由对象状态或属性变化产生的,比如 Image 对象的 status 属性(在《Qt Quick 简单教程》有用到)。那么有一个问题,就是,怎样知道一个对象有哪些信号?
 

 

如何寻找感兴趣的信号

    怎样找到你感兴趣的信号呢?

Qt 帮助

  首先是查阅 Qt 帮助,你可以使用 Qt 帮助的索引模式,以你关心的对象名字为关键字检索,比如 Button ,检索结果如图  所示:

 

从 Qt Quick 头文件查看属性相关的信号

 

    要说呢, Qt Quick 中你看到的很多对象,都是 Qt C++ 中实现,然后导入到 QML 环境中的。所以呢,如果你关心那些被文档隐藏了的信号,可以这么做:

找到 QML 类型对应的 C++ 类型
找到 C++ 类型的头文件,查看属性声明来确认是否有信号与属性关联
    怎么找 QML 类型对应的 C++ 类型呢?很简单,只需要使用 Component.onCompleted 附加信号,在附加信号处理器中输出类型信息即可。示例代码:
 

import QtQuick 2.0
import QtQuick.Controls 1.1
 
Rectangle {
    width: 320;
    height: 240;
    color: "gray";
    
    Text {
        id: text1;
        anchors.centerIn: parent;
        text: "Hello World!";
        color: "blue";
        font.pixelSize: 32;
    }
    
    Button {
        id: button1;
        text: "A Button";
        anchors.top: text1.bottom;
        anchors.topMargin: 4;
    }
    
    Image {
        id: image1;
    }
    
    Component.onCompleted: {
        console.log("QML Text\'s C++ type - ", text1);
        console.log("QML Button\'s C++ type - ", button1);
        console.log("QML Image\'s C++ type - ", image1);
    }
}

 

qquicktext_p.h 。你的环境中根据 Qt SDK 安装目录,路径可能有所不同。

看看 QQuickText 类的声明吧(我截取了属性部分的几行代码):

class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem
{
    Q_OBJECT

    Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
    Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
    Q_PROPERTY(QColor linkColor READ linkColor WRITE setLinkColor NOTIFY linkColorChanged)
    Q_PROPERTY(TextStyle style READ style WRITE setStyle NOTIFY styleChanged)
    Q_PROPERTY(QColor styleColor READ styleColor WRITE setStyleColor NOTIFY styleColorChanged)
    Q_PROPERTY(HAlignment horizontalAlignment READ hAlign WRITE setHAlign RESET resetHAlign NOTIFY horizontalAlignmentChanged)
    Q_PROPERTY(HAlignment effectiveHorizontalAlignment READ effectiveHAlign NOTIFY effectiveHorizontalAlignmentChanged)
    Q_PROPERTY(VAlignment verticalAlignment READ vAlign WRITE setVAlign NOTIFY verticalAlignmentChanged)
    Q_PROPERTY(WrapMode wrapMode READ wrapMode WRITE setWrapMode NOTIFY wrapModeChanged)
    Q_PROPERTY(int lineCount READ lineCount NOTIFY lineCountChanged)
    Q_PROPERTY(bool truncated READ truncated NOTIFY truncatedChanged)
    Q_PROPERTY(int maximumLineCount READ maximumLineCount WRITE setMaximumLineCount NOTIFY maximumLineCountChanged RESET resetMaximumLineCount)

    Q_PROPERTY(TextFormat textFormat READ textFormat WRITE setTextFormat NOTIFY textFormatChanged)
    Q_PROPERTY(TextElideMode elide READ elideMode WRITE setElideMode NOTIFY elideModeChanged) //### elideMode?
    Q_PROPERTY(qreal contentWidth READ contentWidth NOTIFY contentWidthChanged)
    Q_PROPERTY(qreal contentHeight READ contentHeight NOTIFY contentHeightChanged)
    Q_PROPERTY(qreal paintedWidth READ contentWidth NOTIFY contentWidthChanged)  // Compatibility
    Q_PROPERTY(qreal paintedHeight READ contentHeight NOTIFY contentHeightChanged)
    Q_PROPERTY(qreal lineHeight READ lineHeight WRITE setLineHeight NOTIFY lineHeightChanged)
    Q_PROPERTY(LineHeightMode lineHeightMode READ lineHeightMode WRITE setLineHeightMode NOTIFY lineHeightModeChanged)
    Q_PROPERTY(QUrl baseUrl READ baseUrl WRITE setBaseUrl RESET resetBaseUrl NOTIFY baseUrlChanged)
    Q_PROPERTY(int minimumPixelSize READ minimumPixelSize WRITE setMinimumPixelSize NOTIFY minimumPixelSizeChanged)
    Q_PROPERTY(int minimumPointSize READ minimumPointSize WRITE setMinimumPointSize NOTIFY minimumPointSizeChanged)
    Q_PROPERTY(FontSizeMode fontSizeMode READ fontSizeMode WRITE setFontSizeMode NOTIFY fontSizeModeChanged)
    Q_PROPERTY(RenderType renderType READ renderType WRITE setRenderType NOTIFY renderTypeChanged)
    Q_PROPERTY(QString hoveredLink READ hoveredLink NOTIFY linkHovered REVISION 2)

    Q_PROPERTY(qreal padding READ padding WRITE setPadding RESET resetPadding NOTIFY paddingChanged REVISION 6)
    Q_PROPERTY(qreal topPadding READ topPadding WRITE setTopPadding RESET resetTopPadding NOTIFY topPaddingChanged REVISION 6)
    Q_PROPERTY(qreal leftPadding READ leftPadding WRITE setLeftPadding RESET resetLeftPadding NOTIFY leftPaddingChanged REVISION 6)
    Q_PROPERTY(qreal rightPadding READ rightPadding WRITE setRightPadding RESET resetRightPadding NOTIFY rightPaddingChanged REVISION 6)
    Q_PROPERTY(qreal bottomPadding READ bottomPadding WRITE setBottomPadding RESET resetBottomPadding NOTIFY bottomPaddingChanged REVISION 6)

    Q_PROPERTY(QJSValue fontInfo READ fontInfo NOTIFY fontInfoChanged REVISION 9)
    Q_PROPERTY(QSizeF advance READ advance NOTIFY contentSizeChanged REVISION 10)

public:
    QQuickText(QQuickItem *parent=nullptr);
    ~QQuickText() override;

    enum HAlignment { AlignLeft = Qt::AlignLeft,
                       AlignRight = Qt::AlignRight,
                       AlignHCenter = Qt::AlignHCenter,
                       AlignJustify = Qt::AlignJustify };
    Q_ENUM(HAlignment)
    enum VAlignment { AlignTop = Qt::AlignTop,
                       AlignBottom = Qt::AlignBottom,
                       AlignVCenter = Qt::AlignVCenter };
    Q_ENUM(VAlignment)
    enum TextStyle { Normal,
                      Outline,
                      Raised,
                      Sunken };
    Q_ENUM(TextStyle)
    enum TextFormat { PlainText = Qt::PlainText,
                       RichText = Qt::RichText,
                       AutoText = Qt::AutoText,
                       StyledText = 4 };
    Q_ENUM(TextFormat)
    enum TextElideMode { ElideLeft = Qt::ElideLeft,
                          ElideRight = Qt::ElideRight,
                          ElideMiddle = Qt::ElideMiddle,
                          ElideNone = Qt::ElideNone };
    Q_ENUM(TextElideMode)

    enum WrapMode { NoWrap = QTextOption::NoWrap,
                    WordWrap = QTextOption::WordWrap,
                    WrapAnywhere = QTextOption::WrapAnywhere,
                    WrapAtWordBoundaryOrAnywhere = QTextOption::WrapAtWordBoundaryOrAnywhere, // COMPAT
                    Wrap = QTextOption::WrapAtWordBoundaryOrAnywhere
                  };
    Q_ENUM(WrapMode)

    enum RenderType { QtRendering,
                      NativeRendering
                    };
    Q_ENUM(RenderType)

    enum LineHeightMode { ProportionalHeight, FixedHeight };
    Q_ENUM(LineHeightMode)

    enum FontSizeMode { FixedSize = 0x0, HorizontalFit = 0x01, VerticalFit = 0x02,
                        Fit = HorizontalFit | VerticalFit };
    Q_ENUM(FontSizeMode)

 

 

那么多的 Q_PROPERTY 宏啊。  Q_PROPERTY 宏就是用来定义 QML 中可访问属性的,当你看到 NOTIFY 字样,它后面的字段就是与属性绑定的信号的名字。 Qt 实现了动态属性绑定,当你为 QML Text 的属性 color 赋值时,实际上会调用到 QQuickText 的 setColor() 函数,也会触发 colorChanged() 信号。
再来看看 text 和 color 对应的信号原型:

class Q_QUICK_PRIVATE_EXPORT QQuickText : public QQuickImplicitSizeItem
{
    Q_OBJECT
    ...
Q_SIGNALS:
    void textChanged(const QString &text);
    void colorChanged();   
}

看到了吧, textChanged 信号有个 text 参数。我们来看看 textChanged 信号怎样在 QML 中使用。示例代码:

import QtQuick 2.0
import QtQuick.Controls 1.1
 
Rectangle {
    width: 320;
    height: 240;
    color: "gray";
    
    Text {
        id: hello;
        anchors.centerIn: parent;
        text: "Hello World!";
        color: "blue";
        font.pixelSize: 32;
        onTextChanged: {
            console.log(text);
        }
    }
    
    Button {
        anchors.top: hello.bottom;
        anchors.topMargin: 8;
        anchors.horizontalCenter: parent.horizontalCenter;
        text: "Change";
        onClicked: {
            hello.text = "Hello Qt Quick";
        }
    }
}

当用户点击按钮时,改变 Text 对象的文本为 "Hello Qt Quick" 。而我在 Text 对象中实现了 onTextChanged 信号处理器,使用 console.log 输出新的文本。
    注意啦,QML 信号的参数名字,可以直接在信号处理器中访问。之前只用没说,这里特意说一下这点。如果你通过头文件找属性绑定的信号,就可以观察信号的参数,在 QML 中使用。
 

 

自定义信号与使用

如果你自己定义新的 QML 类型,可以使用 signal 关键字给你的类型添加信号。其语法如下:

signal <name>[([<type> <parameter name>[, ...]])]

这是你的类型通告自己状态的最好的方式,符合 Qt 的风格,作为使用 Qt 的开发人员,还是崇德向善吧。
    信号其实是个方法(函数),所以呢,它的发射,实际是通过调用以信号名为名的方法达成的。

    举个实例的例子:我们在界面上放一个字符串,两个代表颜色的小方块,点小方块,字符串的颜色就变成小方块的颜色。先看代码

import QtQuick 2.0
import QtQuick.Controls 1.1
 
Rectangle {
    width: 320;
    height: 240;
    color: "#C0C0C0";
    
    Text {
        id: coloredText;
        anchors.horizontalCenter: parent.horizontalCenter;
        anchors.top: parent.top;
        anchors.topMargin: 4;
        text: "Hello World!";
        font.pixelSize: 32;
    }
    
    Component {
        id: colorComponent;
        Rectangle {
            id: colorPicker;
            width: 50;
            height: 30;
            signal colorPicked(color clr);
            MouseArea {
                anchors.fill: parent
                onPressed: colorPicker.colorPicked(colorPicker.color);
            }
        }
    }
    
    Loader{
        id: redLoader;
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        sourceComponent: colorComponent;
        onLoaded:{
            item.color = "red";
        }
    }
    
    Loader{
        id: blueLoader;
        anchors.left: redLoader.right;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        sourceComponent: colorComponent;
        onLoaded:{
            item.color = "blue";
        }
    }
    
    Connections {
        target: redLoader.item;
        onColorPicked:{
            coloredText.color = clr;
        }
    }
    
    Connections {
        target: blueLoader.item;
        onColorPicked:{
            coloredText.color = clr;
        }
    }
}

 

 

首先定义了一个 Text 对象,id 为 coloredText ,后面会根据这个 id 来改变它的颜色。

    然后我定义了一个组件,组件内有一个 Rectangle 对象,这里是我们定义信号的地方。我设置 Rectangle 的尺寸,然后定义了信号 colorPicked ,语句如下:

signal colorPicked(color clr);
    为了触发信号,我给 Rectangle 引入了 MouseArea 。MouseArea 是专门处理鼠标操作的 item ,这里我们先知道它有一个 onClicked() 信号就行了。我们给这个信号指定信号处理器,在信号处理器中调用我们刚定义的信号:colorPicker.colorPicked(colorPicker.color); ……如你所见,信号的触发就是一个函数调用。
    组件是可以重复利用的,一个组件可以在一个单独的 qml 文件中定义,也可以嵌入到其它 qml 文档中来定义以方便简单组件的使用。这里我选择在主 qml 文档内嵌入组件的定义。定义好了组件,就可以使用 Loader 来加载组件了。
    Loader 是专门用来动态创建组件的,它可以从 qml 文件中创建组件,也可以指定 sourceComponent 来创建,这里的示例,因为组件是嵌入在主 qml 文件中定义的,所以使用了 sourceComponent 方式。我给每个 Loader 一个 id ,以便后面连接时使用。我还使用 anchors 为 Loader 布局。最后呢,在 Loader 的 onLoaded 信号处理器内给 Rectangle 对象配置颜色。

    创建完 Loader ,就是建立连接了。这里使用 Connections 来建立信号的连接,target 指向刚才说的 Loader 对象的 item 属性, item 属性实际指向 Loader 创建的对象。在 Connections 对象中,通过 onColorPicked 信号处理器响应用户点击操作。你看到了,我们定义 colorPicked 信号时命名的参数是 clr ,所以 Connections 的信号处理器中可以直接使用它给 coloredText 对象复制。
 

连接信号与槽

 

    前面我们使用信号时,要么通过信号处理器,要么使用 Connections 对象。其实在 QML 中还有一种更一般的方式。先回想下 Qt C++ 中我们如何使用信号与槽…… QObject::connection() ,诺,木错,就是它了。对应的,在 QML 中,其实 signal 是个对象,它也有一个 connect() 方法,你可以使用它连接到任意的方法上哦。有 connect() 就有 disconnect() ,正确, signal 对象的的确确有这两个方法,允许我们使用它维护连接。

    signal 对象的 connect() 方法允许你连接一个信号到另外一个信号或者方法。其实没差别,信号本身也是个方法(函数)。当信号发射时,连接到信号上的其它信号或方法就会被调用。

    signal 对象的这种连接方式,使用起来比信号处理器更加灵活。前面介绍信号处理器时,已经提到,信号处理器和信号是一对一的关系。而 signal 对象的这种连接方式,使得一个信号能够连接多个方法。

    举个简单的例子来看看如何使用吧。下面的代码(来自 Qt 帮助), messageReceived 信号通过 connect() 方法连接到了三个方法上。


 

Rectangle {
    id: relay;
 
    signal messageReceived(string person, string notice);
 
    Component.onCompleted: {
        relay.messageReceived.connect(sendToPost);
        relay.messageReceived.connect(sendToTelegraph);
        relay.messageReceived.connect(sendToEmail);
        relay.messageReceived("Tom", "Happy Birthday");
    }
 
    function sendToPost(person, notice) {
        console.log("Sending to post: " + person + ", " + notice);
    }
    function sendToTelegraph(person, notice) {
        console.log("Sending to telegraph: " + person + ", " + notice);
    }
    function sendToEmail(person, notice) {
        console.log("Sending to email: " + person + ", " + notice);
    }
}
Rectangle {
    id: forwarder;
    width: 100; 
    height: 100;
 
    signal send();
    onSend: console.log("Send clicked");
 
    MouseArea {
        id: mousearea;
        anchors.fill: parent;
        onClicked: console.log("MouseArea clicked");
    }
 
    Component.onCompleted: {
        mousearea.clicked.connect(send);
    }
}

    这里我们给 Rectangle 定义了一个信号 send() ,在 Component.onCompleted 附加信号处理器中,把 MouseArea 对象的 clicked 信号连接到 Rectangle 的 send() 信号上。

 

 

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlexFang0904

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值