范围和命名解析

QML 属性绑定、内联函数和导入的 JavaScript 文件都在 JavaScript 范围内运行。范围控制表达式可以访问哪些变量,当两个或更多名称冲突时,哪个变量具有优先级。

JavaScript 的内置范围机制非常简单,QML 对其进行了增强,以更好地适应 QML 语言扩展。

JavaScript 范围

QML 的范围扩展不会干扰 JavaScript 的自然范围。JavaScript 程序员可以在 QML 中编写函数、属性绑定或导入的 JavaScript 文件时,再利用现有的知识。

在以下示例中,addConstant() 方法将像程序员预期的那样将 13 添加到传入的参数中,而不论 QML 对象的 ab 属性的值。

QtObject {
    property int a: 3
    property int b: 9

    function addConstant(b) {
        var a = 13;
        return b + a;
    }
}

QML 服从 JavaScript 的正常范围规则,即使应用于绑定。这个极其邪恶、令人厌恶的绑定将把 12 赋值给 QML 对象的 a 属性。

QtObject {
    property int a

    a: { var a = 12; a; }
}

QML 中每个 JavaScript 表达式、函数或文件都有一个自己独特的变量对象。在一个中声明的局部变量永远不会与在另一个中声明的局部变量冲突。

类型名称和导入的 JavaScript 文件

QML 文档 包含导入语句,定义了文档可见的类型名称和 JavaScript 文件。除用于 QML 声明本身外,类型名称还用于 JavaScript 代码访问 附加属性 和枚举值。

导入的效果适用于 QML 文档中的每个属性绑定和 JavaScript 函数,即使是在嵌套的内联组件中。以下示例显示了一个简单的 QML 文件,该文件访问了某些枚举值并调用了导入的 JavaScript 函数。

import QtQuick 2.0
import "code.js" as Code

ListView {
    snapMode: ListView.SnapToItem

    delegate: Component {
        Text {
            elide: Text.ElideMiddle
            text: "A really, really long string that will require eliding."
            color: Code.defaultColor()
        }
    }
}

绑定范围对象

具有 属性绑定 的对象称为绑定的 范围对象。在以下示例中,Item 对象是绑定的范围对象。

Item {
    anchors.left: parent.left
}

绑定可以无限定地访问范围对象的属性。在先前的示例中,绑定直接访问了 Itemparent 属性,而不需要任何对象前缀。QML 引入了一种更加结构化、面向对象的 JavaScript 方法,因此不需要使用 JavaScript 的 this 属性。

在从绑定访问 附加属性 时,必须小心,因为这些属性会与作用域对象交互。从概念上讲,附加属性存在于 所有 对象上,即使它们只对其中的一小部分对象有效。因此,无限定名称的附加属性读取将始终解析为作用域对象上的附加属性,这并不总是程序员所期望的。

例如,PathView 类型会根据其在路径中的位置将其插值值属性附加到其代表者。由于 PathView 仅将此类属性有意义地附加到代表者的根对象,因此访问它们的任何子对象必须显式指定根对象,如下所示。

PathView {
    delegate: Component {
        Rectangle {
            id: root
            Image {
                scale: root.PathView.scale
            }
        }
    }
}

如果 Image 对象省略了 root 前缀,则它将意外地访问其自身的未设置 PathView.scale 附加属性。

组件作用域

QML 文档中的每个 QML 组件定义了一个逻辑作用域。每个文档至少有一个根组件,也可以有其他内联子组件。组件作用域是组件内部的对象 ID 和组件根对象的属性的并集。

Item {
    property string title

    Text {
        id: titletype
        text: "<b>" + title + "</b>"
        font.pixelSize: 22
        anchors.top: parent.top
    }

    Text {
        text: titletype.text
        font.pixelSize: 18
        anchors.bottom: parent.bottom
    }
}

上述示例显示了一个简单的 QML 组件,该组件在顶部显示一个丰富的文本标题字符串,并在底部显示相同文本的小版本。第一个 Text 类型在形成要显示的文本时直接访问组件的 title 属性。由于根类型的属性可以直接访问,因此轻松地在组件中分发数据变得很容易。

第二个 Text 类型使用 ID 直接访问第一个的文本。ID 由 QML 程序员明确指定,因此它们始终优先级高于其他属性名称(除了 JavaScript 作用域 中的名称)。例如,在不太可能的情况下,如果绑定的作用域对象在前面示例中有一个 titletype 属性,则 titletype ID 仍将具有优先级。

组件实例层次结构

在 QML 中,组件实例连接它们的组件作用域以形成作用域层次结构。组件实例可以直接访问其祖先的组件作用域。

通过内联子组件可以最容易地演示这一点,这些子组件的组件作用域隐式地作为外部组件的子元素进行限制。

Item {
    property color defaultColor: "blue"

    ListView {
        delegate: Component {
            Rectangle {
                color: defaultColor
            }
        }
    }
}

组件实例层次结构允许代理组件的实例访问 Item 类型的 defaultColor 属性。当然,如果有名为 defaultColor 的代理组件属性,它将具有优先级。

组件实例作用域层次结构也扩展到非内联组件。在以下示例中,TitlePage.qml 组件创建了两个 TitleText 实例。尽管 TitleText 类型在单独的文件中,但它仍可以在 TitlePage 中使用时访问 title 属性。QML 是一种动态作用域语言 - 依赖于其使用方式,title 属性可能解析不同。

// TitlePage.qml
import QtQuick 2.0
Item {
    property string title

    TitleText {
        size: 22
        anchors.top: parent.top
    }

    TitleText {
        size: 18
        anchors.bottom: parent.bottom
    }
}

// TitleText.qml
import QtQuick 2.0
Text {
    property int size
    text: "<b>" + title + "</b>"
    font.pixelSize: size
}

动态作用域非常强大,但必须谨慎使用以防止 QML 代码的行为变得难以预测。通常,它仅应在两个组件已经在其他方面紧密耦合的情况下使用。在构建可重用组件时,最好使用属性接口,如下所示

// TitlePage.qml
import QtQuick 2.0
Item {
    id: root
    property string title

    TitleText {
        title: root.title
        size: 22
        anchors.top: parent.top
    }

    TitleText {
        title: root.title
        size: 18
        anchors.bottom: parent.bottom
    }
}

// TitleText.qml
import QtQuick 2.0
Text {
    property string title
    property int size

    text: "<b>" + title + "</b>"
    font.pixelSize: size
}

重写属性

QML 允许在对象声明中定义的属性名称被扩展该第一个对象声明的另一个对象声明中声明的属性覆盖。例如

// Displayable.qml
import QtQuick 2.0
Item {
    property string title
    property string detail

    Text {
        text: "<b>" + title + "</b><br>" + detail
    }

    function getTitle() { return title }
    function setTitle(newTitle) { title = newTitle }
}

// Person.qml
import QtQuick 2.0
Displayable {
    property string title
    property string firstName
    property string lastName

    function fullName()  { return title + " " + firstName + " " + lastName }
}

在这里,title 这个名字同时被赋予了可显示组件的输出文本标题和Person对象的尊称标题。

被覆盖的属性将根据引用它的作用域进行解析。在Person组件的作用域内,或从引用到Person组件实例的外部作用域中,title 的解析指向Person.qml内部的声明。fullName 函数将引用Person内部声明的title 属性。

然而,在Displayable组件内部,title 指的是Displayable.qml中声明的属性。getTitle()和setTitle()函数,以及Text对象的text属性绑定的绑定都将引用Displayable组件中声明的title属性。

尽管具有相同的名称,但这两个属性是完全独立的。一个属性的变化不会触发具有相同名称的另一个属性的变化所对应的onChanged信号处理程序。任何一个属性的别名都只会指向其中一个,而不会同时指向两个。

JavaScript全局对象

QML不允许与全局对象上的属性冲突的类型、id和属性名称,以避免任何混淆。程序员可以确信Math.min(10, 9)将始终按预期工作!

更多信息请参阅JavaScript宿主环境

© 2024 Qt公司有限公司。包含在内的文档贡献为各自所有者的版权。本提供在此的文档是根据自由软件基金会发布的GNU自由文档许可第1.3版的条款授权的。Qt及其相关标志是芬兰和/或其他国家的Qt公司注册商标。所有其他商标均为各自所有者的财产。