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 Signal
, Slot
, 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
and0
meaning1.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 + "%"
}
}
}
}
示例源码下载