找回密码
 立即注册
Qt开源社区 门户 查看内容

充分理解QML的属性绑定

2019-2-18 18:58| 发布者: admin| 查看: 1639| 评论: 0

摘要: 作者:Qt开发技术 大家新年好!这是猪年的第一篇。这里我想给大家讲讲Qt QML里非常重要的一个概念:属性绑定(Property binding)。现代化的开发语言、框架都讲究自动化、智能化,在笔者看来,属性绑定则是QML中这 ...
作者:Qt开发技术

大家新年好!这是猪年的第一篇。这里我想给大家讲讲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(){returntextField.text;});
}
该方法的好处是可以写在任何js执行代码里。

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

QML属性绑定原理


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

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

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

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

    假设Buttontext属性的WRITE方法为setText,用C++的形式描述这个槽函数大概是下面这样子:
    publicslots:
    voidtextBindingSlot(){
       QStringnewText=
    evaluateJavaScript("function(){return textField.text;}();");
       setText(newText);
    }

    1. 计算冒号右边的表达式的值。

    2. 将该值通过调用左边属性的WRITE方法赋值。

  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
}
functionfunc(){
   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函数:
voidsetText(QStringnewText){
   // 如果没修改,则直接返回
   if(text==newText) return;
   text=newText;
   emittextChanged();
}


-------------------------------------------------------------------------
我们尊重原创,也注重分享,如若侵权请联系qter@qter.org。
-------------------------------------------------------------------------

1人点赞鲜花

握手

雷人

路过

鸡蛋

刚表态过的朋友 (1 人)


公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了