Qt6 QML Book/Qt for Python/构建应用程序

Building an Application

构建应用程序

In this chapter we will look at how you can combine Python and QML. The most natural way to combine the two worlds is to do as with C++ and QML, i.e. implement the logic in Python and the presentation in QML.

在本章中,我们将介绍如何将Python和QML结合起来。组合两个世界最自然的方式是用C++和QML来实现,即在Python中实现逻辑,在QML中实现显示。

To do this, we need to understand how to combine QML and Python into a single program, and then how to implement interfaces between the two worlds. In the sub-sections below, we will look at how this is done. We will start simple and progress to an example exposing the capabilities of a Python module to QML through a Qt item model.

要做到这一点,我们需要了解如何将QML和Python组合到一个程序中,然后了解如何实现这两个世界之间的接口。在下面的小节中,我们将了解如何做到这一点。我们将从简单开始,并通过一个Qt项模型向QML展示Python模块的功能。

Running QML from Python

从Python运行QML

The very first step is to create a Python program that can host the Hello World QML program shown below.

第一步是创建一个Python程序,该程序可以托管如下所示的Hello World QML程序。

import QtQuick
import QtQuick.Window

Window {
    width: 640
    height: 480
    visible: true
    title: "Hello Python World!"
}

To do this, we need a Qt mainloop provided by QGuiApplication from the QtGui module. We also need a QQmlApplicationEngine from the QtQml module. In order to pass the reference to the source file to the QML application engine, we also need the QUrl class from the QtCore module.

为此,我们需要QGuiApplication从QtGui模块提供一个Qt主循环。我们还需要来自QtQml模块的QQmlApplicationEngine。为了将对源文件的引用传递给QML应用程序引擎,我们还需要来自QtCore模块的QUrl类。

In the code below we emulate the functionality of the boilerplate C++ code generated by Qt Creator for QML projects. It instanciates the application object, and creates a QML application engine. It then loads the QML and then ensures that the QML was loaded by checking if a root object was created. Finally, it exits and returns the value returned by the exec method of the application object.

在下面的代码中,我们模拟Qt Creator为QML项目生成的样板C++代码的功能。它实例化应用程序对象,并创建QML应用程序引擎。然后加载QML,然后通过检查是否创建了根对象来确保加载了QML。最后,它退出并返回应用程序对象的exec方法返回的值。

import sys
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QUrl

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    engine.load(QUrl("main.qml"))
    
    if not engine.rootObjects():
        sys.exit(-1)
    
    sys.exit(app.exec())

Executing the example results in a window with the title Hello Python World.

执行该示例将导致出现一个标题为Hello Python World的窗口。

TIP

The example assumes that it is executed from the directory containing the main.qml source file. You can termine the location of the Python file being executed using the __file__ variable. This can be used to locate the QML files relative to the Python file as shown in this blog post.

​该示例假定它是从包含main.qml源文件的目录执行。您可以使用_file__变量来确定正在执行的Python文件的位置。这可以用来定位相对于Python文件的QML文件,如本文所示。

Exposing Python Objects to QML

向QML公开Python对象

The easiest way to share information between Python and QML is to expose a Python object to QML. This is done by registering a context property through the QQmlApplicationEngine. Before we can do that, we need to define a class so that we have an object to expose.

在Python和QML之间共享信息的最简单方法是向QML公开Python对象。这是通过QQmlApplicationEngine注册上下文属性来完成的。在此之前,我们需要定义一个类,这样我们就有了一个要公开的对象。

Qt classes come with a number of features that we want to be able to use. These are: signals, slots and properties. In this first example, we will restrict ourselves to a basic pair of signal and slot. The rest will be covered in the examples further on.

Qt类附带了许多我们希望能够使用的功能。这些是:信号、槽和属性。在第一个例子中,我们将把自己限制为一对基本的信号和槽。其余部分将在后面的示例中介绍。

Signals and Slots

信号与槽

We start with the class NumberGenerator. It has a constructor, a method called updateNumber and a signal called nextNumber. The idea is that when you call updateNumber, the signal nextNumber is emitted with a new random number. You can see the code for the class below, but first we will look at the details.

我们从类NumberGenerator开始。它有一个构造函数、一个名为updateNumber的方法和一个名为nextNumber的信号。其思想是,当您调用updateNumber时,信号nextNumber将以一个新的随机数发出。您可以在下面看到这个类的代码,但首先我们将查看详细信息。

First of all we make sure to call QObject.__init__ from our constructor. This is very important, as the example will not work without it.

首先,我们要确保调用QObject.__init__从我们的构造函数初始化。这一点非常重要,因为没有它,示例将无法工作。

Then we declare a signal by creating an instance of the Signal class from the PySide6.QtCore module. In this case, the signal carries an integer value, hence the int. The signal parameter name, number, is defined in the arguments parameter.

然后,我们通过从PySide6.QtCore创建signal类的实例来声明一个信号。在这种情况下,信号携带一个整数值,因此是int。信号参数名number在arguments参数中定义。

Finally, we decorate the updateNumber method with the @Slot() decorator, thus turning it into a slot. There is not concept of invokables in Qt for Python, so all callable methods must be slots.

最后,我们用@Slot()装饰器装饰updateNumber方法,从而将其变成一个槽。在Qt for Python中没有可调用的概念,所以所有可调用的方法都必须是槽。

In the updateNumber method we emit the nextNumber signal using the emit method. This is a bit different than the syntax for doing so from QML or C++ as the signal is represented by an object instead of being a callable function.

在updateNumber方法中,我们使用emit方法发出nextNumber信号。这与从QML或C++中这样做的语法不同,因为信号由对象表示而不是可调用函数。

import random

from PySide6.QtCore import QObject, Signal, Slot

class NumberGenerator(QObject):
    def __init__(self):
        QObject.__init__(self)
    
    nextNumber = Signal(int, arguments=['number'])

    @Slot()
    def updateNumber(self):
        self.nextNumber.emit(random.randint(0, 99))

Next up is to combine the class we just created with the boilerplate code for combining QML and Python from earlier. This gives us the following entry-point code.

下一步是将我们刚刚创建的类与前面的用于组合QML和Python的样板代码结合起来。这为我们提供了以下入口点代码。

The interesting lines are the one where we first instatiate a NumberGenerator. This object is then exposed to QML using the setContextProperty method of the rootContext of the QML engine. This exposes the object to QML as a global variable under the name numberGenerator.

有趣的是我们第一次实现NumberGenerator。然后,使用QML引擎的rootContext的setContextProperty方法将该对象公开给QML。这会将对象作为名为numberGenerator的全局变量公开给QML。

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    
    number_generator = NumberGenerator()
    engine.rootContext().setContextProperty("numberGenerator", number_generator)
    
    engine.load(QUrl("main.qml"))
    
    if not engine.rootObjects():
        sys.exit(-1)    
    
    sys.exit(app.exec())

Continuing to the QML code, we can see that we’ve created a Qt Quick Controls 2 user interface consisting of a Button and a Label. In the button’s onClicked handler, the numberGenerator.updateNumber() function is called. This is the slot of the object instantiated on the Python side.

继续QML代码,我们可以看到我们已经创建了一个Qt Quick Controls 2用户界面,由一个按钮Button和一个标签Label组成。在按钮的onClicked处理器中,调用numberGenerator.updateNumber()函数。这是Python端实例化的对象的槽。

To receive a signal from an object that has been instantiated outside of QML we need to use a Connections element. This allows us to attach a signal hanlder to an existing target.

要从在QML之外实例化的对象接收信号,我们需要使用Connections元素类型。这使我们能够将信号发射器连接到现有目标上。

import QtQuick
import QtQuick.Window
import QtQuick.Controls

Window {
    id: root
    
    width: 640
    height: 480
    visible: true
    title: "Hello Python World!"
    
    Flow {
        Button {
            text: "Give me a number!"
            onClicked: numberGenerator.updateNumber()
        }
        Label {
            id: numberLabel
            text: "no number"
        }
    }

    Connections {
        target: numberGenerator
        function onNextNumber(number) {
            numberLabel.text = number
        }
    }
}

Properties

属性

Instead of relying soley on signals and slots, the common way to expose state to QML is through properties. A property is a combination of a setter, getter and notification signal. The setter is optional, as we can also have read-only properties.

与单纯依赖信号和槽不同,向QML暴露状态的常用方法是通过属性。属性是setter、getter和通知信号的组合。setter是可选的,因为我们也可以有只读属性。

To try this out we will update the NumberGenerator from the last example to a property based version. It will have two properties: number, a read-only property holding the last random number, and maxNumber, a read-write property holding the maximum value that can be returned. It will also have a slot, updateNumber that updates the random number.

为了尝试这一点,我们将把NumberGenerator从上一个示例更新为基于属性的版本。它将有两个属性:number(保存最后一个随机数的只读属性)和maxNumber(保存可返回的最大值的读写属性)。它还将有一个更新随机数的槽updateNumber。

Before we dive into the details of properties, we create a basic Python class for this. It consists of the relevant getters and setters, but not Qt signalling. As a matter of fact, the only Qt part here is the inheritance from QObject. Even the names of the methods are Python style, i.e. using underscores instead of camelCase.

在深入研究属性的细节之前,我们先为它创建一个基本的Python类。它由相关的getter和setter组成,但不包括Qt信号。事实上,这里唯一的Qt部分是来自QObject的继承。甚至方法的名称也是Python风格的,即使用下划线而不是大小写。

Take notice of the underscores (“__”) at the beginning of the __set_number method. This implies that it is a private method. So even when the number property is read-only, we provide a setter. We just don’t make it public. This allows us to take actions when changing the value (e.g. emitting the notification signal).

请注意__set_number方法开头的下划线(“__”) 。这意味着它是一种私有方法。因此,即使number属性是只读的,我们也提供了一个setter。我们只是不公开。这允许我们在更改值时采取行动(例如,发出通知信号)。

class NumberGenerator(QObject):
    def __init__(self):
        QObject.__init__(self)
        self.__number = 42
        self.__max_number = 99
    
    def set_max_number(self, val):
        if val < 0:
            val = 0
        
        if self.__max_number != val:
            self.__max_number = val
            
        if self.__number > self.__max_number:
            self.__set_number(self.__max_number)
    
    def get_max_number(self):
        return self.__max_number

    def __set_number(self, val):
        if self.__number != val:
            self.__number = val
    
    def get_number(self):
        return self.__number

In order to define properties, we need to import the concepts of SignalSlot, and Property from PySide2.QtCore. In the full example, there are more imports, but these are the ones relevant to the properties.

为了定义属性,我们需要从PySide2.QtCore导入信号、槽和属性的概念。在完整的示例中,有更多的导入,但这些是与属性相关的导入。

from PySide6.QtCore import QObject, Signal, Slot, Property

Now we are ready to define the first property, number. We start off by declaring the signal numberChanged, which we then invoke in the __set_number method so that the signal is emitted when the value is changed.

现在我们准备好定义第一个属性number。我们首先声明信号numberChanged,然后在__set_number方法中调用它,以便在值更改时发出信号。

After that, all that is left is to instantiate the Property object. The Property contructor takes three arguments in this case: the type (int), the getter (get_number) and the notification signal which is passed as a named argument (notify=numberChanged). Notice that the getter has a Python name, i.e. using underscore rather than camelCase, as it is used to read the value from Python. For QML, the property name, number, is used.

之后,剩下的就是实例化Property对象。在这种情况下,属性构造函数接受三个参数:类型(int)、getter(get_number)和作为命名参数传递的通知信号(notify=numberChanged)。请注意,getter有一个Python名称,即使用下划线而不是驼峰命名法,因为它用于从Python读取值。对于QML,使用属性名number。

class NumberGenerator(QObject):

    # ...
    
    # number
    
    numberChanged = Signal(int)
    
    def __set_number(self, val):
        if self.__number != val:
            self.__number = val
            self.numberChanged.emit(self.__number)
    
    def get_number(self):
        return self.__number
    
    number = Property(int, get_number, notify=numberChanged)

This leads us to the next property, maxNumber. This is a read-write property, so we need to provide a setter, as well as everything that we did for the number property.

这就引出了下一个属性maxNumber。这是一个读写属性,所以我们需要提供一个setter,以及我们为number属性所做的一切。

First up we declare the maxNumberChanged signal. This time, using the @Signal decorator instead of instantiating a Signal object. We also provide a setter slot, setMaxNumber with a Qt name (camelCase) that simply calls the Python method set_max_number alongside a getter with a Python name. Again, the setter emits the change signal when the value is updated.

首先,我们声明maxNumberChanged信号。这一次,使用@Signal而不是实例化一个Signal对象。我们还提供了一个setter槽,setMaxNumber和一个Qt名称(驼峰命名法),它简单地调用Python方法set_max_number和一个带有Python名称的getter。同样,setter在值更新时发出更改信号。

Finally we put the pieces together into a read-write property by instantiating a Property object taking the type, getter, setter and notification signal as arguments.

最后,我们通过以类型、getter、setter和通知信号为参数实例化一个Property 对象,将这些片段组合成一个读写属性。

class NumberGenerator(QObject):

    # ...

    # maxNumber

    @Signal
    def maxNumberChanged(self):
        pass

    @Slot(int)
    def setMaxNumber(self, val):
        self.set_max_number(val)

    def set_max_number(self, val):
        if val < 0:
            val = 0
        
        if self.__max_number != val:
            self.__max_number = val
            self.maxNumberChanged.emit()
            
        if self.__number > self.__max_number:
            self.__set_number(self.__max_number)
    
    def get_max_number(self):
        return self.__max_number

    maxNumber = Property(int, get_max_number, set_max_number, notify=maxNumberChanged)

Now we have properties for the current random number, number, and the maximum random number, maxNumber. All that is left is a slot to produce a new random number. It is called updateNumber and simply sets a new random number.

现在我们有了当前随机数和最大随机数的属性。剩下的就是一个产生新随机数的槽。它被称为updateNumber,只需设置一个新的随机数。

class NumberGenerator(QObject):

    # ...
    
    @Slot()
    def updateNumber(self):
        self.__set_number(random.randint(0, self.__max_number))

Finally, the number generator is exposed to QML through a root context property.

最后,数字生成器通过根上下文属性向QML暴露。

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    
    number_generator = NumberGenerator()
    engine.rootContext().setContextProperty("numberGenerator", number_generator)
    
    engine.load(QUrl("main.qml"))
    
    if not engine.rootObjects():
        sys.exit(-1)    
    
    sys.exit(app.exec())

In QML, we can bind to the number as well as the maxNumber properties of the numberGenerator object. In the onClicked handler of the Button we call the updateNumber method to generate a new random number and in the onValueChanged handler of the Slider we set the maxNumber property using the setMaxNumber method. This is because altering the property directly through Javascript would destroy the bindings to the property. By using the setter method explicitly, this is avoided.

在QML中,我们可以绑定numberGenerator对象的number和maxNumber属性。在按钮的onClicked处理器中,我们调用updateNumber方法来生成一个新的随机数,在Slider的onValueChanged处理器中,我们使用setMaxNumber方法设置maxNumber属性。这是因为直接通过Javascript修改属性会破坏与属性的绑定。通过显式使用setter方法,可以避免这种情况。

import QtQuick
import QtQuick.Window
import QtQuick.Controls

Window {
    id: root
    
    width: 640
    height: 480
    visible: true
    title: "Hello Python World!"
    
    Column {
        Flow {
            Button {
                text: "Give me a number!"
                onClicked: numberGenerator.updateNumber()
            }
            Label {
                id: numberLabel
                text: numberGenerator.number
            }
        }
        Flow {
            Slider {
                from: 0
                to: 99
                value: numberGenerator.maxNumber
                onValueChanged: numberGenerator.setMaxNumber(value)
            }
        }
    }
}

Exposing a Python class to QML

向QML暴露Python类

Up until now, we’ve instantiated an object Python and used the setContextProperty method of the rootContext to make it available to QML. Being able to instantiate the object from QML allows better control over object life-cycles from QML. To enable this, we need to expose the class, instead of the object, to QML.

到目前为止,我们已经实例化了一个对象Python,并使用rootContext的setContextProperty方法使其可供QML使用。能够从QML实例化对象可以更好地控制QML中的对象生命周期。为了实现这一点,我们需要将类而不是暴露给QML。

The class that is being exposed to QML is not affected by where it is intantiated. No change is needed to the class definition. However, instead of calling setContextProperty, the qmlRegisterType function is used. This function comes from the PySide2.QtQml module and takes five arguments:

暴露于QML的类不受其显示位置的影响。不需要更改类定义。但是,不调用setContextProperty,而是使用qmlRegisterType函数。这个函数来自PySide2.QtQml模块,并接受五个参数:

  • A reference to the class, NumberGenerator in the example below.
  • 下面示例中对NumberGenerator类的引用。
  • A module name, 'Generators'.
  • 模块名 'Generators'
  • A module version consisting of a major and minor number, 1 and 0 meaning 1.0.
  • 一个模块版本,由一个大版本和一个小版本组成,1和0表示1.0。
  • The QML name of the class, 'NumberGenerator'
  • 类的QML名称'NumberGenerator'
import random
import sys

from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
from PySide6.QtCore import QUrl, QObject, Signal, Slot


class NumberGenerator(QObject):
    def __init__(self):
        QObject.__init__(self)
    
    nextNumber = Signal(int, arguments=['number'])

    @Slot()
    def updateNumber(self):
        self.nextNumber.emit(random.randint(0, 99))


if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    
    qmlRegisterType(NumberGenerator, 'Generators', 1, 0, 'NumberGenerator')
    
    engine.load(QUrl("main.qml"))
    
    if not engine.rootObjects():
        sys.exit(-1)    
    
    sys.exit(app.exec())

In QML, we need to import the module, e.g. Generators 1.0 and then instantiate the class as NumberGenerator { ... }. The instance now works like any other QML element.

在QML中,我们需要导入模块,例如Generator 1.0,然后将该类实例化为NumberGenerator{…}。该实例现在与任何其他QML元素一样工作。

import QtQuick
import QtQuick.Window
import QtQuick.Controls

import Generators

Window {
    id: root
    
    width: 640
    height: 480
    visible: true
    title: "Hello Python World!"
    
    Flow {
        Button {
            text: "Give me a number!"
            onClicked: numberGenerator.updateNumber()
        }
        Label {
            id: numberLabel
            text: "no number"
        }
    }
    
    NumberGenerator {
        id: numberGenerator
    }
    
    Connections {
        target: numberGenerator
        function onNextNumber(number) {
            numberLabel.text = number
        }
    }
}

A Model from Python

来自Python的模型

One of the more interesting types of objects or classes to expose from Python to QML are item models. These are used with various views or the Repeater element to dynamically build a user interface from the model contents.

从Python到QML暴露的对象或类的一种更有趣的类型是项模型。它们与各种视图或Repeater元素类型一起使用,以根据模型内容动态构建用户界面。

In this section we will take an existing python utility for monitoring CPU load (and more), psutil, and expose it to QML via a custom made item model called CpuLoadModel. You can see the program in action below:

在本节中,我们将使用一个用于监视CPU负载(以及更多)的现有python实用程序psutil,并通过一个名为CpuLoadModel的定制项模型将其暴露给QML。您可以在下面看到正在运行的程序:

TIP

The psutil library can be found at https://pypi.org/project/psutil/.

​psutil库可在以下位置找到:https://pypi.org/project/psutil/.

“psutil (process and system utilities) is a cross-platform library for retrieving information on running processes and system utilization (CPU, memory, disks, network, sensors) in Python.”

“psutil(进程和系统实用程序)是一个跨平台的库,用于检索Python中运行的进程和系统利用率(CPU、内存、磁盘、网络、传感器)的信息。”

You can install psutil using pip install psutil.

您可以使用pip install psutil安装psutil。

We will use the psutil.cpu_percent function (documentation) to sample the CPU load per core every second. To drive the sampling we use a QTimer. All of this is exposed through the CpuLoadModel which is a QAbstractListModel.

​我们将使用psutil.cpu_percent函数(文档),用于每秒对每个内核的cpu负载进行采样。为了驱动采样,我们使用了一个QTimer。所有这些都通过CpulodModel暴露,CpulodModel是一个QAbstractListModel。

Item models are interesting. They allow you to represent a two dimensional data set, or even nested data sets, if using the QAbstractItemModel. The QAbstractListModel that we use allow us to represent a list of items, so a one dimensional set of data. It is possible to implement a nested set of lists, creating a tree, but we will only create one level.

项模型很有趣。如果使用QAbstractItemModel,它们允许您表示二维数据集,甚至是嵌套数据集。我们使用的QAbstractListModel允许我们表示一个项目列表,也就是一组一维数据。可以实现一组嵌套列表,创建一棵树,但我们只创建一个级别。

To implement a QAbstractListModel, it is necessary to implement the methods rowCount and data. The rowCount returns the number of CPU cores which we get using the psutil.cpu_count method. The data method returns data for different roles. We only support the Qt.DisplayRole, which corresponds to what you get when you refer to display inside the delegate item from QML.

要实现QAbstractListModel,必须实现rowCount和data方法。rowCount返回我们使用psutil.cpu_count获得的CPU内核数。data方法返回不同角色的数据。我们只支持Qt.DisplayRole,它对应于从QML中引用委托项内部的display时得到的内容。

Looking at the code for the model, you can see that the actual data is stored in the __cpu_load list. If a valid request is made to data, i.e. the row, column and role is correct, we return the right element from the __cpu_load list. Otherwise we return None which corresponds to an uninitialized QVariant on the Qt side.

查看模型的代码,可以看到实际数据存储在__cpu_load列表中。如果对数据发出了有效的请求,即行、列和角色正确,我们将从__cpu_load列表返回正确的元素。否则,我们将返回与Qt端未初始化的QVariant相对应的None。

Every time the update timer (__update_timer) times out, the __update method is triggered. Here, the __cpu_load list is updated, but we also emit the dataChanged signal, indicating that all data was changed. We do not do a modelReset as that also implies that the number of items might have changed.

每次更新计时器(__update_timer)超时时,都会触发__update方法。这里,__cpu_load列表被更新,但我们也会发出dataChanged信号,指示所有数据都已更改。我们不做模型重置,因为这也意味着项目的数量可能已经改变。

Finally, the CpuLoadModel is exposed to QML are a registered type in the PsUtils module.

最后,CpuLoadModel暴露给QML,QML是PsUtils模块中的注册类型。

import psutil
import sys

from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
from PySide6.QtCore import Qt, QUrl, QTimer, QAbstractListModel


class CpuLoadModel(QAbstractListModel):
    def __init__(self):
        QAbstractListModel.__init__(self)
        
        self.__cpu_count = psutil.cpu_count()
        self.__cpu_load = [0] * self.__cpu_count
        
        self.__update_timer = QTimer(self)
        self.__update_timer.setInterval(1000)
        self.__update_timer.timeout.connect(self.__update)
        self.__update_timer.start()
        
        # The first call returns invalid data
        psutil.cpu_percent(percpu=True)
            
    def __update(self):
        self.__cpu_load = psutil.cpu_percent(percpu=True)
        self.dataChanged.emit(self.index(0,0), self.index(self.__cpu_count-1, 0))
        
    def rowCount(self, parent):
        return self.__cpu_count
    
    def data(self, index, role):
        if (role == Qt.DisplayRole and
            index.row() >= 0 and
            index.row() < len(self.__cpu_load) and
            index.column() == 0):
            return self.__cpu_load[index.row()]
        else:
            return None

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    
    qmlRegisterType(CpuLoadModel, 'PsUtils', 1, 0, 'CpuLoadModel')
    
    engine.load(QUrl("main.qml"))
    
    if not engine.rootObjects():
        sys.exit(-1)    
    
    sys.exit(app.exec())

On the QML side we use a ListView to show the CPU load. The model is bound to the model property. For each item in the model a delegate item will be instantiated. In this case that means a Rectangle with a green bar (another Rectangle) and a Text element displaying the current load.

在QML端,我们使用ListView来显示CPU负载。模型绑定到模型属性。对于模型中的每个项目,将实例化一个委托项目。在本例中,这意味着一个带有绿色条(另一个矩形)和显示当前负载的文本元素的矩形。

import QtQuick
import QtQuick.Window
import QtQuick.Controls

import PsUtils

Window {
    id: root
    
    width: 640
    height: 480
    visible: true
    title: "CPU Load"
    
    ListView {
        anchors.fill: parent
        model: CpuLoadModel { }
        delegate: Rectangle { 
            id: delegate

            required property int display

            width: parent.width
            height: 30
            color: "white"
            
            Rectangle {
                id: bar
                
                width: parent.width * delegate.display / 100.0
                height: 30
                color: "green"
            }
            
            Text {
                anchors.verticalCenter: parent.verticalCenter
                x: Math.min(bar.x + bar.width + 5, parent.width-width)
                text: delegate.display + "%"
            }   
        }
    }
}

示例源码下载 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值