ML(Qt Modeling Language)是一种用于描述用户界面的声明性语言,它是Qt框架中用于创建现代、动态用户界面的一种重要方式。QML提供了丰富的功能,包括但不限于以下几个方面:
总之,QML作为Qt框架中重要的一部分,提供了丰富的功能和灵活的特性,使得开发者能够快速构建现代化的用户界面,并可以轻松实现丰富的交互效果和动画效果。
间因素
开发程序时,必须尽可能实现一致的60帧/秒刷新率。60帧/秒意味着每帧之间大约有16毫秒可以进行处理,其中包括将绘图基元上传到图形硬件所需的处理。
那么,就需要注意以下几个重要的点:
1.尽可能使用异步,事件驱动编程
2.使用工作线程进行重要处理
3.永远不要手动控制事件循环
4.在阻塞函数中,每帧的花费不要超过几毫秒
如果不这样做,那么将会发生调整,影响用户体验。
注意:永远不应该使用的模式是创建自己的QEventLoop或调用QCoreApplication :: processEvents(),以避免在从QML调用的C ++代码块中阻塞。这样做非常危险,因为当在信号处理程序或绑定中输入事件循环时,QML引擎继续运行其他绑定,动画,转换等。然后这些绑定会导致副作用,例如,破坏包含整体层次结构事件循环。
剖析
最重要的提示是:使用Qt Creator附带的QML分析器。了解应用程序在何处花费时间将使您能够专注于实际存在的问题区域,而不是可能存在的问题区域。有关如何使用QML分析工具的更多信息,请参阅Qt creator 帮助文档。
如果不进行分析而直接去优化代码,可能效果并不会很明显,借助分析器将会更快的定位到消耗性能的模块,然后再进行重新设计,以便提高性能。
JavaScript代码
大多数QML应用程序将以动态函数、信号处理程序和属性绑定表达式的形式包含大量JavaScript代码。这通常不是问题,由于QML引擎中的一些优化,例如对绑定编译器所做的那些优化,它可以(在某些用例中)比调用C ++函数更快。但是,必须注意确保不会意外触发不必要的处理。
绑定
QML中有两种类型的绑定:优化绑定和非优化绑定。保持绑定表达式尽可能简单是一个好主意,因为QML引擎使用优化的绑定表达式求值程序,它可以评估简单的绑定表达式,而无需切换到完整的JavaScript执行环境。与更复杂(非优化)的绑定相比,这些优化的绑定的评估效率更高。优化绑定的基本要求是在编译时必须知道所访问的每个符号的类型信息。
绑定表达式时要避免的事情,以达到最大的优化:
1.声明中间JavaScript变量
2.访问“var”属性
3.调用JavaScript函数
4.构造闭包或在绑定表达式中定义函数
5.访问直接评估范围之外的属性
6.写作其他属性作为副作用
立即评估范围可以概括为它包含:
1.表达式范围对象的属性(对于绑定表达式,这是属性绑定所属的对象)
2.组件中任何对象的ID
3.组件中根项的属性
来自其他组件的对象和任何此类对象的属性,以及JavaScript导入中定义或包含的符号都不在直接评估范围内,因此不会优化访问任何这些对象的绑定。
类型转换
使用JavaScript的一个主要成本是,在大多数情况下,当访问QML类型的属性时,会创建一个包含底层C ++数据(或对它的引用)的外部资源的JavaScript对象。在大多数情况下,这是不会太影响性能,但在其他情况下,它可能相当消耗性能。比如是将C ++ QVariantMap Q_PROPERTY分配给QML“variant”属性。列表也可能是有损性能的,尽管(特定类型的序列的QList为int, qreal,布bool,QString,和QUrl)应该相对来说不会太影响, 其他列表类型可能会带来昂贵的转换成本(创建新的JavaScript数组,逐个添加新类型,从C ++类型实例到JavaScript值的每类型转换)。
在一些基本属性类型(例如“string”和“url”属性)之间转换也可能很影响性能。使用最接近的匹配属性类型将避免不必要的转换。
如果必须将QVariantMap公开给QML,请使用“var”属性而不是“variant”属性。一般来说,对于来自QtQuick 2.0及更新版本的每个用例,“property var”应该被认为优于“property variant” (注意“property variant”被标记为过时),因为它允许真正的JavaScript引用存储(可以减少某些表达式中所需的转换次数)。
解决属性
虽然在某些情况下可以缓存和重用查找结果,但如果可能的话,最好完全避免完成不必要的工作。
在下面的示例中,我们有一个经常运行的代码块(在这种情况下,它是显式循环的内容;但它可能是一个通常评估的绑定表达式,例如),在其中,我们解决了具有“rect”id及其“color”属性的对象多次调用:
// bad.qml
import QtQuick 2.3
Item {
width: 400
height: 200
Rectangle {
id: rect
anchors.fill: parent
color: "blue"
}
function printValue(which, value) {
console.log(which + "=" + value);
}
Component.onCompleted: {
var t0=new Date();
for (var i=0; i < 1000; ++i) {
printValue("red", rect.color.r);
printValue("green", rect.color.g);
printValue("blue", rect.color.b);
printValue("alpha", rect.color.a);
}
var t1=new Date();
console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
}
}
我们可以在块中只解析一次公共基数:
// good.qml
import QtQuick 2.3
Item {
width: 400
height: 200
Rectangle {
id: rect
anchors.fill: parent
color: "blue"
}
function printValue(which, value) {
console.log(which + "=" + value);
}
Component.onCompleted: {
var t0=new Date();
for (var i=0; i < 1000; ++i) {
var rectColor=rect.color; // resolve the common base.
printValue("red", rectColor.r);
printValue("green", rectColor.g);
printValue("blue", rectColor.b);
printValue("alpha", rectColor.a);
}
var t1=new Date();
console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
}
}
只需这一简单的改变就可以显着提高性能。请注意,上面的代码可以进一步改进(因为在循环处理期间查找的属性永远不会改变),通过将属性解析提升出循环,如下所示:
// better.qml
import QtQuick 2.3
Item {
width: 400
height: 200
Rectangle {
id: rect
anchors.fill: parent
color: "blue"
}
function printValue(which, value) {
console.log(which + "=" + value);
}
Component.onCompleted: {
var t0=new Date();
var rectColor=rect.color; // resolve the common base outside the tight loop.
for (var i=0; i < 1000; ++i) {
printValue("red", rectColor.r);
printValue("green", rectColor.g);
printValue("blue", rectColor.b);
printValue("alpha", rectColor.a);
}
var t1=new Date();
console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
}
}
属性绑定
如果更改了引用的任何属性,则将重新评估属性绑定表达式。因此,绑定表达式应尽可能简单。
如果你有一个循环来进行某些处理,但只有处理的最终结果很重要,通常最好更新一个临时累加器,然后将其分配给需要更新的属性,而不是逐步更新属性本身,以避免在累积的中间阶段触发重新评估结合表达。
以下的例子说明了这一点:
// bad.qml
import QtQuick 2.3
Item {
id: root
width: 200
height: 200
property int accumulatedValue: 0
Text {
anchors.fill: parent
text: root.accumulatedValue.toString()
onTextChanged: console.log("text binding re-evaluated")
}
Component.onCompleted: {
var someData=[ 1, 2, 3, 4, 5, 20 ];
for (var i=0; i < someData.length; ++i) {
accumulatedValue=accumulatedValue + someData[i];
}
}
}
onCompleted处理程序中的循环导致“text”属性绑定被重新评估六次(然后导致依赖于文本值的任何其他属性绑定,以及onTextChanged信号处理程序,每次重新评估时间,并列出每次显示的文本)。在这种情况下,这显然是不必要的,因为我们只关心最终的值。
那么,以上代码可以改成这样:
// good.qml
import QtQuick 2.3
Item {
id: root
width: 200
height: 200
property int accumulatedValue: 0
Text {
anchors.fill: parent
text: root.accumulatedValue.toString()
onTextChanged: console.log("text binding re-evaluated")
}
Component.onCompleted: {
var someData=[ 1, 2, 3, 4, 5, 20 ];
var temp=accumulatedValue;
for (var i=0; i < someData.length; ++i) {
temp=temp + someData[i];
}
accumulatedValue=temp;
}
}
序列提示
如前所述,某些序列类型很快(例如,QList ,QList ,QList ,QList < QString >,QStringList和QList < QUrl >),而其他序列类型则要慢得多。除了尽可能使用这些类型而不是较慢类型之外,还需要注意一些其他与性能相关的语法以获得最佳性能。
首先,对于序列类型的两种不同的实现:一个是当序列是Q_PROPERTY一个的QObject的(我们称此为参考序列),另一个用于在序列从返回Q_INVOKABLE一个功能的QObject(我们将这称为复制序列)。
通过QMetaObject :: property()读取和写入引用序列,因此读取和写入QVariant。这意味着从JavaScript更改序列中任何元素的值将导致三个步骤发生:将从QObject读取完整序列(作为QVariant,但随后转换为正确类型的序列); 指定索引处的元素将在该序列中更改; 并且完整的序列将被写回QObject(作为QVariant)。
复制序列更简单,因为实际序列存储在JavaScript对象的资源数据中,因此不会发生读取/修改/写入循环(而是直接修改资源数据)。
因此,对参考序列的元素的写入将比写入复制序列的元素慢得多。实际上,写入N元素参考序列的单个元素与将N元素复制序列分配给该参考序列的成本相当大,因此通常最好修改临时复制序列,然后将结果分配给计算过程中的参考序列。
假设以下C ++类型存在,并且已经正常注册过:
class SequenceTypeExample : public QQuickItem
{
Q_OBJECT
Q_PROPERTY (QList<qreal> qrealListProperty READ qrealListProperty WRITE setQrealListProperty NOTIFY qrealListPropertyChanged)
public:
SequenceTypeExample() : QQuickItem() { m_list << 1.1 << 2.2 << 3.3; }
~SequenceTypeExample() {}
QList<qreal> qrealListProperty() const { return m_list; }
void setQrealListProperty(const QList<qreal> &list) { m_list=list; emit qrealListPropertyChanged(); }
signals:
void qrealListPropertyChanged();
private:
QList<qreal> m_list;
};
以下示例在多次循环中写入引用序列的元素,从而导致性能下降:
// bad.qml
import QtQuick 2.3
import Qt.example 1.0
SequenceTypeExample {
id: root
width: 200
height: 200
Component.onCompleted: {
var t0=new Date();
qrealListProperty.length=100;
for (var i=0; i < 500; ++i) {
for (var j=0; j < 100; ++j) {
qrealListProperty[j]=j;
}
}
var t1=new Date();
console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
}
}
由表达式引起的内部循环中的QObject属性读取和写入"qrealListProperty[j]=j"使得此代码非常不理想。相反,更好的一种方法是:
// good.qml
import QtQuick 2.3
import Qt.example 1.0
SequenceTypeExample {
id: root
width: 200
height: 200
Component.onCompleted: {
var t0=new Date();
var someData=[1.1, 2.2, 3.3]
someData.length=100;
for (var i=0; i < 500; ++i) {
for (var j=0; j < 100; ++j) {
someData[j]=j;
}
qrealListProperty=someData;
}
var t1=new Date();
console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
}
}
其次,如果属性中的任何元素发生变化,则会发出属性的更改信号。如果你对序列属性中的特定元素有很多绑定,最好创建一个绑定到该元素的动态属性,并将该动态属性用作绑定表达式中的符号而不是sequence元素,因为它将只有在其值发生变化时才会重新评估绑定。
// bad.qml
import QtQuick 2.3
import Qt.example 1.0
SequenceTypeExample {
id: root
property int firstBinding: qrealListProperty[1] + 10;
property int secondBinding: qrealListProperty[1] + 20;
property int thirdBinding: qrealListProperty[1] + 30;
Component.onCompleted: {
var t0=new Date();
for (var i=0; i < 1000; ++i) {
qrealListProperty[2]=i;
}
var t1=new Date();
console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
}
}
请注意,即使在循环中仅修改索引2处的元素,也会重新评估三个绑定,因为更改信号的粒度是整个属性已更改。因此,添加中间绑定有时可能是有益的:
// good.qml
import QtQuick 2.3
import Qt.example 1.0
SequenceTypeExample {
id: root
property int intermediateBinding: qrealListProperty[1]
property int firstBinding: intermediateBinding + 10;
property int secondBinding: intermediateBinding + 20;
property int thirdBinding: intermediateBinding + 30;
Component.onCompleted: {
var t0=new Date();
for (var i=0; i < 1000; ++i) {
qrealListProperty[2]=i;
}
var t1=new Date();
console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
}
}
在上面的示例中,每次仅重新评估中间绑定,从而导致显著的性能提升。
值类型提示
值类型属性(font,color,vector3d等)具有类似的QObject属性,并将通知语义更改为序列类型属性。因此,上面给出的序列提示也适用于值类型属性。虽然它们通常不是值类型的问题(因为值类型的子属性的数量通常远小于序列中元素的数量),所以重新评估的绑定数量的任何增加不必要地会对绩效产生负面影响。
其他JavaScript对象
不同的JavaScript引擎提供不同的优化。Qt Quick 2使用的JavaScript引擎针对对象实例化和属性查找进行了优化,但它提供的优化依赖于某些标准。如果你的应用程序不符合标准,则JavaScript引擎会回退到“慢速路径”模式,性能会更差。因此,请始终尽量确保您符合以下条件:
1.尽可能避免使用eval()
2.不要删除对象的属性
【领QT开发教程学习资料,点击下方链接莬费领取↓↓,先码住不迷路~】
点击这里:「链接」
t Quick 为 Qt 引入了一门叫 QML(Qt Meta/Modeling Language)的脚本语言,它是 ECMAScript 标准的实现。所以 QML 语法是在 ECMAScript 语法的基础上实现的。
ECMAScript 语言的标准是由 Netscape、Sun、微软、Borland 等公司基于 JavaScript 和 JScript 锤炼、定义出来的。
ECMAScript 仅仅是一个描述,定义了脚本语言的所有属性、方法和对象。其他语言可以实现 ECMAScript 来作为功能的基础,正如 JavaScript 那样。这个实现又可以被扩展,包含特定于宿主环境的新特性,比如 QML 就引入了 Qt 对象系统中的信号与槽,还增加了动态属性绑定等非常有特色的新功能。
作为一种全新的编程语言,QML 有三个核心:
下面一个个来看 ECMAScript 的语法。(QML 是 ECMAScript 标准的实现,所以两者语法是基本一样的)
1. 1、区分大小写
与 C++ —样,变量、函数名、运算符以及其他一切东西都是区分大小写的,也就是说, 变量 area 和 Area 是不同的。
1.2、弱类型
与 C++ 不同,ECMAScript 中的变量没有特定的类型,定义变量时只用 var 运算符,可以将它初始化为任意的值,你可以随时改变变量所存储的数据类型(实际上应当尽量避免这样做)。例如:
var i=0
console.log(i)
i="hello"
console.log(i)
尽管在语法上这么做没有问题,但好的编码习惯是一个变量始终存放相同类型的值。
1.3. 语句后的分号可有可无
C、C++、Java 等语言都要求每条语句以分号(;)结束。ECMAScript 则允许开发者自行决定是否以分号结束一行代码。如果没有分号,ECMAScript 就把这行代码的结尾看作该语句的结束(与 Lua、Python、Visual Basic 相似)。至于加不加分号,那就看自己的喜好了。下面两行代码的语法都是正确的:
var background="white"
var i=0
在 ECMAScript 中使用 var 运算符声明变量,与 C++ 类似,变量名需要遵循一定的规则。
2.1、变量声明
变量用 var 运算符加变量名来定义。例如:
var i=0
在这个例子中,声明了变量 i 并把它初始化为 0。你也可以不初始化,在用到时再初始化。
一个 var 语句可以定义多个变量。例如:
var i=0 , name="j"
这个例子定义了变量 i,初始化为数字;还定义了变量 name,初始化为字符串。你看到了,这和 C++ 或 Java 不同,一个 var 语句定义的多个变量可以有不同的类型。
2.2、变量命名规则
变量命名需要遵守两条简单的规则:
下面这些变量名都是合法的:
var test
var objectName
var —phone
var $1
为了代码的可读性,在命名变量时还应该遵循一定的命名风格。因为 Qt 是基于 C++ 的应用框架,QML 又是 Qt 框架的一部分,这里建议和 Qt C++ 代码采取同样的命名风格—驼峰命名法。
对于变量(包括函数名),以小写字母开始,单词之间采用驼峰命名法。对于类名,以大写字母开始,单词之间采用驼峰命名法。
QT开发交流+赀料君羊:714620761
ECMAScript 有 5 种原始类型,即 Undefined、Null、Boolean、Number 和 String。每种原始类型定义了它包含的值的范围及其字面量表示形式。
ECMAScript 提供了 typeof 运算符来判断一个值的类型,如果这个值是原始类型,typeof 还会返回它具体的类型名字;而如果这个值是引用值,那么 typeof 统一返回 ”object” 作为类型名字。示例如下:
import QtQuick 2.2
Rectangle {
Component.onCompleted:{
var name="Zhang San Feng"
console.log(typeof name) // 输出:qml:string
console.log(typeof 60) // 输出:qml:number
}
}
变量 name 的类型是 string,字面量 60 的类型是 number。其中 “qml:” 是使用 console.log 输出信息时携带的前缀。
3.1、Undefined 类型
Undefined 类型只有一个值,即 undefined。当声明的变量未初始化时,该变量的默认值就是 undefined。例如:
var temp
上面的代码声明了变量 temp 但并未显式地讲行初始化,它的值将被设置为 undefined, 这和 C++ 不同。ECMAScript 的这一特性:未初始化的变量也有固定的初始值,我们可以将一个变量和 undefined 比较来实现一些业务逻辑。比如:
var runOnce;
...
if(runOnce==undefined) {
runOnce=true
}
else {
...
}
当函数没有明确的返回值时,返回的值也是 undefined,如下所示:
function blankFunc(){}
console.log(blankFunc()==undefined) // 输出:true
3.2、Null 类型
Null 类型也只有一个值,即 null。
你可以显式地将一个变量初始化为 null,然后据此实现一些逻辑。
3.3、Boolean 类型
Boolean 是 ECMAScript 中最常用的类型之一,它有 true 和 false 两个值。
3.4、Number 类型
Number 类型是最特殊的,它既可以表示 32 位的整数,也可以表示 64 位的浮点数。你在 QML 代码中直接输入的任何数字都被看作是 Number 类型的字面量。
下面的代码声明了存放整数值的变量:
var integer=10
数字类型的最大值是 Number.MAX_VALUE,最小值是 Number.MlN_VALUE,它们定义了 Number 值的外边界,所有的 ECMAScript 数都必须在这两个值之间。
3.5、String 类型
ECMAScript 中的 String 类型是作为原始类型存在的,它存储 Unicode 字符,对应的 Qt C++ 类型为 QString。当你混合 C++ 和 QML 编程时,所有的 QString 类型的变量都会被映射为 ECMAScript 中的 String。
字符串字面量可以用双引号(")或单引号(')来声明。而在 Qt 中,只能用双引号, 单引号表示字符。为了一致性,建议你尽可能不要使用单引号表示字符串。在 ECMAScript 中没有字符类型,这也是为什么你可以使用单引号来表示字符串的原因。下面的两行代码都是有效的:
var name='Lv Bu'
var name="Guan Yu"
如果一种编程语言不支持类型转换,那真是无法想象。在 ECMAScript 中,类型转换非常简单。
4.1、转换成字符串
Boolean、Number、String 三种原始类型,都有 toString() 方法,可以把它们的值转换为字符串。比如下面的代码在 Qt 中可以正常运行:
var name="Zhang San Feng"
console.log(name.toString())
console.log(true.toString())
var visible=false
console.log(visible.toString())
var integer=3.14159
console.log(integer.toString())
Number 类型的 toString() 方法还可以按基转换,比如:
var integer=13
console.log (integer.toString(16)) // 输出: D
如果你不指定数基,那不管原来是用什么形式声明的 Number 类型,toString() 都按十进制输出。
4.2、转换成数字
parselnt() 和 parseFloat() 可以把非数字的原始值转换成数字,前者把值转换为整数,后者把值转换成浮点数。这两个方法只能用于 String 类型,如果你对其他类型调用它们, 返回值将是奇葩的 NaN。
parselnt() 和 parseFloat() 会扫描字符串,直到遇到第一个非数字字符时停止,将转换的结果返回。比如parselnt(”2014年")将会返回 2014。对于 parseFloat(),会将第一个小数点作为有效字符,而 parselnt() 则不会。
下面是一些示例:
var numl=parselnt ("2014 年") // 输出:2014
var num2=parselnt ("OxC") // 输出:12
var num3=parselnt ("3.14") // 输出:3
var num4=parselnt ("green") // 输出:NaN_
var num5=parseFloat ("3.14159") // 输出:3.14159
var num7=parseFloat ("Am I Float") // 输出:NaN
parselnt() 还支持基模式,下面是一些示例:
var numl=parselnt ("AK47", 16) // 输出:10
var num2=parselnt ("AK47", 10) // 输出:NaN
var num3=parselnt ("010", 8) // 输出:8
var nun4=parselnt ("010", 10) // 输出:10
需要注意的是,代表浮点数的字符串必须以十进制形式表示,比如parseFloat(“OxFE”),返回 NaN。
4.3、强制类型转换
如果你是 C/C++ 程序员,对强制类型转换一定又爱又恨。ECMAScript 也支持强制类型 转换,有三种转换:
ECMAScript 中的函数,就是具名的、可重复使用的代码块。另外,ECMAScript 不支持函数重载。
5.1、函数语法
函数语法如下:
function functionName(arg1, arg2, ..., argN){
// 要执行的代码
}
function 是定义函数时必须使用的关键字。functionName可以任意取,符合变量命名规则即可。 arg1 到 argN 是函数的参数,当然也可以没有参数。花括号内是要执行的代码块。
无参函数示例:
function quitApp(){
Qt .quit ();
}
带参函数示例:
function showError(msg){
console.log("error - ", msg);
}
function travel(country, city){
console.log("Welcome to ", city, " , ", country);
}
当我们使用函数参数的时候,参数就像不带 var 运算符的变量声明一样。这与 C++ 中必须给函数参数指明类型大相径庭。
5.2、函数的返回值
ECMAScript 中的函数,默认都是有返回值的,即便你没有显式使用 return 语句,它也会返回 undefined。如果你想把函数运算的结果返回给调用它的地方,可以使用 return 语句。下面是个简单的示例:
function add(numberl, number2){
var result=number1 + number2;
console.log(number1, "+" ,number2, result);
return result;
}
QT开发交流+赀料君羊:714620761
你可以这样调用 add() 函数:var ret=add(100, 34);。
ECMAScript 的运算符和 C++、Java 等语言的差不多,具体内容不再赘述。这里只重点介绍一下关键字运算符。void、typeof、instanceof、new、delete 这些都是关键字运算符。
console 提供了输出日志信息、断言、计时器、计数器、性能分析等功能,这里只介绍前三个我们经常用到的功能。
7.1、输出日志信息
console对象提供了多个打印调试信息的方法:
7.2、断言
console.assert() 提供断言功能,它接受一个表达式,当表达式的值为 false 时会输出调试信息,打印 QML 所在行。例如:console.assert (false)。
如果你传递了额外的参数给 console.assert(),它会在控制台输出这些信息。示例:
var years=0;
for (; years < 18; years++){
console.log("I\'m minor");
continue;
console.log ("You shoult not see me"};
}
console.assert(years < 18, years);
上面的断言语句,将会输出下列信息:
18
onCompleted (file:///F:/projects/qtquick/qmls/show_type.qml:187)
需要注意的是,在 QML 中,使用 console.assert(),断言失败,程序并不会终止运行。
7.3、计时器
console 提供了计时器功能,方便我们测量某些代码的耗时情况。
console.time(tag) 启动定时器,字符串类型的 tag 是必需的。console.timeEnd(tag) 停止计时器,在控制台输出某个标签对应的耗时信息。tag 是必需的。 下面是简单的示例:
*请认真填写需求信息,我们会在24小时内与您取得联系。