[Quick module of Qt] 6. Detailed explanation of QML syntax_4 Integrating QML and JavaScript

1. JavaScript expressions and property binding

The QML language allows expressions and methods to be defined using JavaScript functions.

1.1 Attribute binding

  1. General Binding
    When you create a property binding and set an expression for the property, the expression evaluates to the property's value.
import QtQuick

Window {
    
    
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Rectangle{
    
    
        width: 100
        height: parent.height/2
        border.color: "red"
        anchors.centerIn: parent
    }
}

As above, when Window's heightchanges, Rectangle' heightwill automatically update the new value.
image.png

You can also use other JavaScript expressions or statements, as follows:

import QtQuick

Window {
    
    
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")


    Rectangle{
    
    
        width: 100
        height: parent.height > 480 ? parent.height/2 : parent.height/3
        border.color: "red"
        anchors.centerIn: parent
    }
}

When the Window's parent.height value changes, the QML engine will recalculate these expressions and set the updated value to the height property of QRectangle. At that time As
you can see from its syntax, the binding is complex, so it is not recommended to include too much code in the binding. And if the binding is complicated, you can consider refactoring the binding code and put it into a function. : If you assign a value again after binding, the binding will be invalid. For example, set the width attribute of Qrectangle to the width of another item:parent.height > 480
image.png
parent.height < 480
image.png

注意

Rectangle{
    
    
width:otherItem.width
}

When set to the following code:

Rectangle{
    
    
Component.OnCompleted:{
    
    
	width = otherItem.width
}
}

When the program initialization is completed, the binding will be invalid.
2. Use binding()binding
. Once a property is bound to an expression, the property will be automatically updated based on the expression. If a static value is assigned to the property again, the binding relationship will be invalid.
If that's the demand, then no problem. If you want to re-establish a new binding, you need to use Qt.binding()to implement it, Qt.binding()passing a function to return the required results.
as follows:

import QtQuick

Window {
    
    
    id:root
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")


    Rectangle{
    
    
        id:recttangle
        width: 100
        height: parent.height > 480 ? parent.height/2 : parent.height/3
        border.color: "red"
        anchors.centerIn: parent
    }

    Component.onCompleted: {
    
    
        recttangle.height = Qt.binding(function(){
    
    return root.height/4})
    }
}

image.png
When initialization is complete, a new binding is created. 3. Using the keyword this
in property binding can be used to eliminate ambiguity in property binding. When creating a property binding using JavaScript, QML allows the use of keywords to reference the object to which the property binding will be allocated. As shown below, when property binding is performed in , what follows is the object, not the object.this

this
Component.onCompletedrecttangle.heightthisrectangleWindow

import QtQuick

Window {
    
    
    id:root
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")


    Rectangle{
    
    
        id:recttangle
        width: 100
        height: parent.height > 480 ? parent.height/2 : parent.height/3
        border.color: "red"
        anchors.centerIn: parent
    }

    Component.onCompleted: {
    
    
        recttangle.height = Qt.binding(function(){
    
    return this.width*2})
    }
}

Print recttangle.height, the result is 200.

1.2 JavaScript functions

  1. Custom function or method
import QtQuick

Window {
    
    
    id:root
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Text {
    
    
        id: text
        text: qsTr("text")
        anchors.centerIn: parent
        font.pixelSize: 23
    }

    function add(a, b){
    
    
        return a+b
    }

    Component.onCompleted: {
    
    
        text.text = add(1, 2)
    }
}

When initialization is complete, the center of the screen will be displayed 3.
2. Import functions in JavaScript files.
When there is a js file, define an add method in it,
as follows:

// funAdd.js
function add(a, b) {
    
    
    return a+b
}

Import:

import QtQuick
import "./funAdd.js" as FunAdd

Window {
    
    
    id:root
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    Text {
    
    
        id: text
        text: qsTr("text")
        anchors.centerIn: parent
        font.pixelSize: 23
    }

    Component.onCompleted: {
    
    
        text.text = FunAdd.add(1, 2)
	console.log(text.text) // "3"
    }
}
  1. Associating signals with JavaScript functions
    The QML object type that emits a signal provides a default signal handler. But sometimes you need to emit a signal from one object to trigger a function defined in another object. In this case, you need to use connect()a function.
// funAdd.js
function prin(){
    
    
    console.log("helloworld")
}

transfer:

import QtQuick
import "./funAdd.js" as FunAdd

Window {
    
    
    id:root
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
    color: "gray"

    MouseArea {
    
    
        id: mouseArea
        anchors.fill: parent
    }

    Component.onCompleted :{
    
    
        mouseArea.clicked.connect(FunAdd.prin) // 当点击屏幕时,打印"helloworld"
    }
}

Insert image description here

2. Dynamically create QML objects from JavaScript

QML supports dynamic creation of objects from within JavaScript. This helps delay instantiation of objects until necessary, thereby improving application startup time. It also allows visuals to be dynamically created and added to the scene based on user input or other events.

2.1 Dynamically create objects

There are two ways to dynamically create objects from JavaScript. One is to call Qt.createComponent() to dynamically create a Component object; the other is to use Qt.createQmlObject() to create an object from a QML string. The first way is better if you have an existing component defined in a QML document and you want to dynamically create an instance of that component. The second way can be used when the object QML itself is generated at runtime.

2.1.1 Qt.createComponent() dynamically creates components

To dynamically load components defined in a QML file, you can call Qt. createcomponent()functions in the Qt object. This function takes the URL of the QML file as its only parameter and creates a Component object from that URL.
Once you have a component, you can call its createObject()methods to create an instance of the component. This function can accept one or two parameters:

  • The first is the parent object of the new object. The parent object can be a graphical object (i.e. Item type) or a non-graphical object (i.e. QtObject or c++ QObject type). Only graphics objects that have a graphics parent are rendered into the Qt Quick visual canvas. You can pass null to this function if you wish to set the parent class later.
  • The second, optional, is a map of property-value pairs that defines the object's initial property values. The property values ​​specified by this parameter are applied to the object before its creation is complete, thus avoiding binding errors that can occur when specific properties must be initialized to enable additional property bindings. Additionally, there are some performance benefits compared to defining property values ​​and bindings after the object is created.
    Example: Create a file
    firstSprite.qml
 import QtQuick 2.0
 Rectangle {
    
     width: 80; height: 50; color: "red" }

Create another componentCreation.jsfile

 var component;
 var sprite;

 function createSpriteObjects() {
    
    
     component = Qt.createComponent("Sprite.qml");
     if (component.status == Component.Ready)
         finishCreation();
     else
         component.statusChanged.connect(finishCreation);
 }

 function finishCreation() {
    
    
     if (component.status == Component.Ready) {
    
    
         sprite = component.createObject(appWindow, {
    
    x: 100, y: 100});
         if (sprite == null) {
    
    
             // 错误处理
             console.log("Error creating object");
         }
     } else if (component.status == Component.Error) {
    
    
         // 错误处理
         console.log("Error loading component:", component.errorString());
     }
 }

Finally main.qmlmake the call in

 import QtQuick 2.0
 import "componentCreation.js" as MyScript

 Rectangle {
    
    
     id: appWindow
     width: 300; height: 300

     Component.onCompleted: MyScript.createSpriteObjects();
 }

createObjectThe component's status is checked before the call Component.Ready, because if the QML file is loaded from the network, it will not be immediately available.
If you are loading a local file, you can createSpriteObjectscall createObjectthe method directly within the function.

 function createSpriteObjects() {
    
    
     component = Qt.createComponent("Sprite.qml");
     sprite = component.createObject(appWindow, {
    
    x: 100, y: 100});

     if (sprite == null) {
    
    
         // 错误处理
         console.log("Error creating object");
     }
 }

Note that in both instances, when createObject() is called, appWindow is passed as the parent parameter because the dynamically created object is a visual (Qt Quick) object. The object created will become a child of the appWindow object in main. Qml and appear in the scene.

When using files with relative paths, the path should be relative to the file in which Qt.createComponent() is executed.

To connect a signal to (or receive a signal from) a dynamically created object, you can use the signal connect() method.
Components can also be instantiated through the incubateObject() function, which is non-blocking.

2.1.2 Qt.createQmlObject() creates objects from QML strings

If QML is not defined until runtime, you can use the Qt.createQmlObject() function to create a QML object from a QML string, as in the following example:

 const newObject = Qt.createQmlObject(`
     import QtQuick 2.0

     Rectangle {
    
    
         color: "red"
         width: 20
         height: 20
     }
     `,
     parentItem,
     "myDynamicSnippet"
 );
  • The first parameter is the QML string to be created. Just like in a new file, you need to import any types you wish to use.
  • The second parameter is the parent object of the new object. The same parent parameter semantics that apply to components also apply to createQmlObject().
  • The third parameter is the file path to be associated with the new object; this is used for error reporting.

If the QML string import file uses a relative path, the path should be relative to the file in which the parent object (the second parameter of the method) is defined.
Important: When building a static QML application, the QML file will be scanned to detect import dependencies. This way all necessary plugins and resources can be resolved at compile time. However, only explicit import statements (those found at the top of the QML file) are considered, not import statements contained within string literals. Therefore, in order to support static builds, you need to ensure that QML files using Qt.createQmlObject() include all necessary imports explicitly at the top of the file, rather than in string literals.

2.2 Maintain dynamically created objects

When managing dynamically created objects, you must ensure that the creation context outlives the created object. Otherwise, bindings and signal handlers in dynamic objects will no longer work if the creation context is destroyed first.
The actual creation context depends on how the object is created:

  • If Qt.createComponent() is used, the creation context is the QQmlContext that calls this method
  • If Qt.createQmlObject() is called, the creation context is the context of the parent object passed to the method
  • If a Component{} object is defined and createObject() or cucubateobject() is called on that object, the creation context is the context in which the component is defined.

Also, please note that while dynamically created objects can be used like other objects, they do not have ids in QML.

2.3 Dynamically delete objects

In many user interfaces, it is sufficient to set the visual object's opacity to 0 or to move the visual object off the screen rather than delete it. However, if you have many dynamically created objects, you may gain valuable performance benefits if you delete unused objects.
Note, never manually delete objects dynamically created by convenience QML object factories such as Loader and Repeater. Additionally, you should avoid deleting objects that you did not dynamically create yourself.
Items can be deleted using the destroy() method. This method has an optional parameter (defaults to 0) that specifies the approximate millisecond delay before the object is destroyed.
The following example: application.qml creates 5 SelfDestroyingRect.qml components. Each instance runs a NumberAnimation and when the animation ends, calls its root object destroy() to destroy itself:

// application.qml
 import QtQuick 2.0

 Item {
    
    
     id: container
     width: 500; height: 100

     Component.onCompleted: {
    
    
         var component = Qt.createComponent("SelfDestroyingRect.qml");
         for (var i=0; i<5; i++) {
    
    
             var object = component.createObject(container);
             object.x = (object.width + 10) * i;
         }
     }
 }
// SelfDestroyingRect.qml
 import QtQuick 2.0

 Rectangle {
    
    
     id: rect
     width: 80; height: 80
     color: "red"

     NumberAnimation on opacity {
    
    
         to: 0
         duration: 1000

         onRunningChanged: {
    
    
             if (!running) {
    
    
                 console.log("Destroying...")
                 rect.destroy();
             }
         }
     }
 }

Alternatively, application.qml can destroy the created object by calling object.destroy().

Note that it is safe to call destroy() on an object inside an object. The object is not destroyed the instant destroy() is called, but is cleaned up sometime between the end of the script block and the next frame (unless a non-zero delay is specified).

Also note that if you create a SelfDestroyingRect instance statically like this:

 Item {
    
    
     SelfDestroyingRect {
    
    
         // ...
     }
 }

This will cause an error because objects can only be dynamically destroyed if they are dynamically created.

Objects created using Qt.createQmlObject() can also be destroyed using destroy().

 const newObject = Qt.createQmlObject(`
     import QtQuick 2.0

     Rectangle {
    
    
         color: "red"
         width: 20
         height: 20
     }
     `,
     parentItem,
     "myDynamicSnippet"
 );
 newObject.destroy(1000);

3. Define JavaScript resources in QML

The program logic of a QML application can be defined in JavaScript. JavaScript code can be defined inline in a QML document or separated into JavaScript files (called JavaScript resources in QML).

Two different types of JavaScript resources are supported in QML: code-behind implementation files and shared (library) files. Both JavaScript resources can be imported by other JavaScript resources or included in QML modules.

3.1 Code-behind implementation resources

Most JavaScript files imported into QML documents are stateful. In these cases, each instance of the QML object type defined in the document requires a separate copy of the JavaScript object and state to function correctly.

The default behavior when importing JavaScript files is to provide a unique, isolated copy of each QML component instance. If the JavaScript file does not import any resources or modules using .import statements, its code will run in the same scope as the QML component instance and can therefore access and manipulate objects and properties declared in that QML component. Otherwise, it will have its own unique scope, and the QML component's objects and properties should be passed as arguments to the JavaScript file's functions, if needed.

An example of a code-behind implementation resource is as follows:

 // my_button_impl.js
 var clickCount = 0;   // this state is separate for each instance of MyButton
 function onClicked(button) {
    
    
     clickCount += 1;
     if ((clickCount % 5) == 0) {
    
    
         button.color = Qt.rgba(1,0,0,1);
     } else {
    
    
         button.color = Qt.rgba(0,1,0,1);
     }
 }
 // MyButton.qml
 import QtQuick 2.0
 import "my_button_impl.js" as Logic // A new instance of this JavaScript resource
                                     // is loaded for each instance of Button.qml.

 Rectangle {
    
    
     id: rect
     width: 200
     height: 100
     color: "red"

     MouseArea {
    
    
         id: mousearea
         anchors.fill: parent
         onClicked: Logic.onClicked(rect)
     }
 }

Generally speaking, simple logic should be defined inline in the QML file, but more complex logic should be separated into code-behind implementation resources for maintainability and readability.

3.2 Shared JavaScript resource library

By default, JavaScript files imported from QML share their context with QML components. This means that JavaScript files have access to the same QML objects and can modify them. Therefore, each import must have a unique copy of these files.

The previous section introduced stateful import of JavaScript files. However, some JavaScript files are stateless and more like reusable libraries in that they provide a set of helper functions that don't require anything from where they are imported. If you mark these libraries with special pragmas, you can save a lot of memory and speed up the instantiation of QML components, as shown in the example below.

 // factorial.js
 .pragma library

 var factorialCount = 0;

 function factorial(a) {
    
    
     a = parseInt(a);

     // factorial recursion
     if (a > 0)
         return a * factorial(a - 1);

     // shared state
     factorialCount += 1;

     // recursion base-case.
     return 1;
 }

 function factorialCallCount() {
    
    
     return factorialCount;
 }

A pragma declaration must appear before any JavaScript code other than comments.

Note that multiple QML documents can import "factorial.js" and call the factorial and factorialCallCount functions it provides. State imported by JavaScript is shared between the QML documents that import it, so when the factoralcallcount function is called from a QML document that has not called the factorial function, its return value may be non-zero.

For example:

 // Calculator.qml
 import QtQuick 2.0
 import "factorial.js" as FactorialCalculator // This JavaScript resource is only
                                              // ever loaded once by the engine,
                                              // even if multiple instances of
                                              // Calculator.qml are created.

 Text {
    
    
     width: 500
     height: 100
     property int input: 17
     text: "The factorial of " + input + " is: " + FactorialCalculator.factorial(input)
 }

Because they are shared, .pragma library files cannot directly access QML component instance objects or properties, although QML values ​​can be passed as function parameters.

4. Import JavaScript resources into QML

JavaScript resources can be imported through QML documents and other JavaScript resources. JavaScript resources can be imported via relative or absolute URLs. In the case of relative URLs, the location is resolved relative to the location containing the imported QML document or JavaScript resource. If the script file is inaccessible, an error will occur. If JavaScript needs to be obtained from a network resource, the component's status is set to "Loading" until the script is downloaded.

JavaScript resources can also import QML modules and other JavaScript resources. The syntax of import statements in JavaScript resources is slightly different from that in QML documents, which is explained in detail below.

4.1 Import JavaScript resources into QML documents

QML documents can import JavaScript resources using the following syntax:

 import "ResourceURL" as Qualifier

like:

 import "jsfile.js" as Logic

Imported JavaScript resources are always qualified using the "as" keyword. Qualifiers for JavaScript resources must start with a capital letter and must be unique, so there is always a one-to-one mapping between qualifiers and JavaScript files. (This also means that qualifiers cannot be named the same as built-in JavaScript objects such as Date and Math.)

Functions defined in imported JavaScript files are available to objects defined in imported QML documents via the "Qualifier.functionName(params)" syntax. Functions in JavaScript resources can accept parameters, which can be of type any supported QML primitive or object type, or ordinary JavaScript types. When calling these functions from QML, normal data type conversion rules apply to parameters and return values.

4.2 Import in JavaScript resources

In QtQuick 2.0, support was added to allow JavaScript resources to import other JavaScript resources and QML type namespaces using a variant of the standard QML import syntax (where all the rules and qualifications described previously apply).
Since JavaScript resources can be imported into another script or QML module in this way in QtQuick 2.0, some additional semantics are defined:

  • A script with imports does not inherit imports (and therefore access components) from the QML document that imports it. (e.g. Component.errorString cannot be accessed)
  • A script without imports will inherit imports from the QML document that imported it (and therefore access components). (For example, you can access Component.errorString)
  • A shared script (i.e., defined as a .pragma library) does not inherit any QML document's imports, even if it does not import other scripts or modules

The first semantics is conceptually correct, since a specific script can be imported by any number of QML files. The second semantics is retained for backward compatibility. The third semantics remains the same as the current semantics of shared scripts, but is clarified here for new possibilities (scripts importing other scripts or modules).
Import a JavaScript resource from another JavaScript resource:

 import * as MathFunctions from "factorial.mjs";

or

 .import "filename.js" as Qualifier

The former is the standard ECMAScript syntax for importing ECMAScript modules and will only work within ECMAScript modules represented by the mjs file extension. The latter is an extension to JavaScript provided by the QML engine and can also work with non-modules. As an extension superseded by the ECMAScript standard, its use is discouraged.

When a JavaScript file is imported this way, it is imported with qualifiers. The functions in this file can then be accessed from the import script via the qualifier (i.e. qualifier.functionname (params)).

Sometimes it is desirable to make functions available in the import context without qualifying them. In this case, ECMAScript modules and JavaScript import statements should be made without the as qualifier.

For example, the following QML code calls showcalculation() in script.mjs in a script. In script.mjs, the factorial() function in factorial.mjs is called.

 import QtQuick 2.0
 import "script.mjs" as MyScript

 Item {
    
    
     width: 100; height: 100

     MouseArea {
    
    
         anchors.fill: parent
         onClicked: {
    
    
             MyScript.showCalculations(10)
             console.log("Call factorial() from QML:",
                 MyScript.factorial(10))
         }
     }
 }
 // script.mjs
 import {
    
     factorial } from "factorial.mjs"
 export {
    
     factorial }

 export function showCalculations(value) {
    
    
     console.log(
         "Call factorial() from script.js:",
         factorial(value));
 }
 // factorial.mjs
 export function factorial(a) {
    
    
     a = parseInt(a);
     if (a <= 0)
         return 1;
     else
         return a * factorial(a - 1);
 }

5. JavaScript hosting environment

QML provides a JavaScript hosting environment specifically for writing QML applications. This environment is different from the hosting environment provided by the browser or the server-side JavaScript environment (such as Node.js). For example, QML does not provide window objects or DOM APIs that are common in browser environments.

Guess you like

Origin blog.csdn.net/MrHHHHHH/article/details/135310862