• (转载)充分理解QML的属性绑定


    原文地址:

    https://zhuanlan.zhihu.com/p/56401271

    感谢作者!

    这里我想给大家讲讲Qt QML里非常重要的一个概念:属性绑定(Property binding)。现代化的开发语言、框架都讲究自动化、智能化,在笔者看来,属性绑定则是QML中这方面的代表。用好属性绑定可以极大提高我们的开发效率。本文首先介绍何为QML属性绑定,然后用通俗易懂的说法来阐释其底层原理,最后和大家讨论开发时几个常见的问题。

    如何实现QML属性绑定

    在QML中实现属性绑定有三种方法,每种方法都有其各自的优缺点。

    1. 冒号绑定

    这是最常见的绑定方法,在定义属性时使用QML的冒号语法,所以笔者称之为“冒号绑定”。

    例如下面的QML代码就实现了一个属性绑定:

    TextField{id: textField}
    Button{
        id: button
        text: textField.text
    }
    

    上面第4行代码将textFieldtext属性绑定到了buttontext上。只要前者发生变化(例如用户输入、修改),按钮上的文字就会跟着变动。

    这种方法的优点是简单方便,是三种方法中代码量最少的。

    缺点是缺乏弹性,控制能力小,主要有两方面:

    • 解绑麻烦。一旦绑定,如果要解绑,需要在js代码中使用赋值操作;对于需要反复绑定、解绑的情况更是麻烦。
    • 无法绑定不在当前QML文件的对象。因为冒号绑定只能写在QML对象定义的地方,所以对于别处传进来的对象,例如通过setContextProperty传进来的对象,就爱莫能助了。

    2. 使用Binding

    QML中专门提供了一个类型Binding来实现属性绑定。上面的例子如果改用Biding来写则代码如下:

    TextField{id: textField}
    Button{id: button}
    Binding{
        target: button
        property: "text"
        value: textField.text
    }
    

    这种方法的优点主要有两个:

    • 绑定控制能力强。查阅Binding的文档,可以看到它有一个when属性,当其为true时,绑定生效;为false时绑定无效,也就是解绑。
    • 可以绑定任意对象。target属性除了可以设置为当前QML文件中的对象,也可以设置任何地方传进来的对象。

    这两个可以说完美解决了上面第一种属性绑定的问题。

    该方法有两个缺点:

    • 代码量多。每个属性绑定都用Binding那是不能忍受的。
    • 构造Binding对象需要花一点时间,这个对于特别大的程序可能会有一定影响。

    所以只在需要的时候用该方法。

    3. 使用Qt.binding()函数

    这是最后一种属性绑定方法。上面的例子改用该方法的话代码如下:

    TextField{id: textField}
    Button{id: button}
    Component.onCompleted:{
        button.text = Qt.binding(function(){return textField.text;});
    }
    

    该方法的好处是可以写在任何js执行代码里。

    缺点是只能运行时绑定。由于前两种方法都是QML语法申明,QML执行引擎在初始化的时候有机会使用JIT技术和Cache技术进行优化,而动态执行的js语句是没法进行这种优化的,因此这种方法的执行效率是三种方法中最低的。

    QML属性绑定原理

    在实际开发中,笔者发现很多人会用属性绑定,但经常出错,发生意外的解绑、循环绑定等问题。究其原因,往往是因为对QML属性绑定的底层原理不甚清楚,一旦程序变复杂很容易糊涂。

    所谓属性绑定的原理,用直白点的话来说,就是:为什么一个属性变化了,和它绑定的属性能跟着变化?

    我们还是用上节中冒号绑定的例子。当我们写下text: textField.text这行代码的时候,QML引擎实际上做了下面这些事情:

    1.构造一个槽函数。 该槽函数执行时会干两件事情:

      1. 计算冒号右边的表达式的值。
      2. 将该值通过调用左边属性的WRITE方法赋值。

    假设Buttontext属性的WRITE方法为setText,用C++的形式描述这个槽函数大概是下面这样子:

    public slots:
    void textBindingSlot(){
        QString newText = evaluateJavaScript("function(){return textField.text;}();");
        setText(newText);
    }
    

    2.扫描冒号右边的表达式,找到所有具有NOTIFY信号的属性。 所谓NOTIFY信号是指Qt定义属性时候的NOTIFY字段,具体可以参看文档Qt Property System。在这个例子中,textField.text是一个具有NOTIFY信号的属性,也就是说它被修改后发射NOTIFY信号。这里假设该信号为textChanged()

    3.将这些属性的NOTIFY信号和上面的槽函数连接。 使用connect连接:connect(textField, SIGNAL(textChanged()), button, SLOT(textBindingSlot));

    所以当任何冒号左边表达式里的具有NOTIFY信号属性值改变时,相关信号发射,然后构造的槽函数得到执行,计算出新的值,最后将该值赋给被绑定的属性。

    上述过程对于第二和第三种绑定方法也是大体一样的。

    根据上述过程描述,我们也得出另一个结论:QML的属性绑定是单向的。所谓单向,是指textField.text的改变会引起button.text的改变;但反过来则不会,因为并没有经过上述的构造过程。

    常见问题讨论

    下面就笔者在实际开发中遇到的几个问题展开讨论。

    1. 我在QObject派生类中自定义了一个属性,然后在QML中绑定到了其他属性,为什么改变该属性,绑定不起作用?

    根据上节阐述的绑定原理,导致绑定不起作用的原因可能有两个:

    1. 仔细查看属性定义,是否添加了NOTIFY字段?所有属性绑定执行、推演的原动力都是这个NOTIFY信号,如果你的自定义属性没有定义该信号,那属性绑定很多不会起作用。
    2. 如果你的属性值是在C++中被修改,那是否发射了NOTIFY指定的信号?有很多人定义了NOTIFY信号,但是在修改属性值的时候,却忘记发射这个信号。这个是非常常见的错误。

    注意:属性绑定的原动力是那个NOTIFY信号。其实不管属性值是否真的改变,只要你发射了该信号,属性绑定都会被重新计算一次(只不过如果值确实没变,每次计算结果也不变)。

    2. 我的属性绑定之前好好的,为什么忽然不起作用了?

    这也是很常见的错误,它往往是在js代码中直接对属性赋值导致的,例如下面的代码:

    TextField{id: textField}
    Button{
        id: button
        text: textField.text
    }
    function func(){
        button.text = "Hello";
    }
    

    正如前面冒号绑定里说的,buttontext属性已经成功绑定到了textFieldtext属性上。但是一旦func函数执行,buttontext属性被直接赋值,实际上就和之前构造出来的槽函数(上节中的textBindingSlot)解了绑。

    如果直接赋值不可避免,又想在赋值之后重新绑定,那么可以用第三种绑定方法进行再绑定。或者一开始就用Binding来绑定,用它的when属性来控制绑定是否起作用。

    3. 如何做双向绑定?

    前面我们提到了QML的属性绑定是单向的,但如果我们确实需要做双向绑定该怎么办?

    对于都是Qt Quick自带类型,可以很简单,例如:

    TextField{
        id: textField
        text: button.text
    }
    Button{
        id: button
        text: textField.text
    }
    

    也就是说,上面各自做一次属性绑定即可。

    但如果是自定义属性,要特别注意WRITE部分要检查属性值是否真的被修改;只有真的被修改才往外发射NOTIFY信号,否则很可能进入死循环。例如下面的WRITE函数:

    void setText(QString newText){
        // 如果没修改,则直接返回
        if(text == newText) return;
        text = newText;
        emit textChanged();
    }
  • 相关阅读:
    ASP.NET WebForm Best Practice 之ViewState
    我会为开源和自由学习,使用JAVA.但我决不会为了开源和自由放弃.NET
    EXT调用ASP.NET AJAX WebService
    我的2007
    博客园迎新春对联
    NBear.Mapping使用教程(2):NBear.Mapping的配置系统
    NBear.Mapping使用教程(3):第一个简单例子
    使用LumaQQ来开发QQ机器人
    解决方案迁移到Visual Studio 2008的一些相关问题
    NBear.Mapping使用教程(4):实体对象与ADO.NET对象的转换
  • 原文地址:https://www.cnblogs.com/laiyingpeng/p/12929138.html
Copyright © 2020-2023  润新知