自定义 Qt Quick 控件
Qt Quick 控件由一个项目(树)组成。为了提供自定义外观和感觉,每个项目的默认 QML 实现可以被自定义实现所替代。
自定义一个控件
有时候你可能希望为 UI 的某个特定部分创建一个“一次性”的外观,并在其他地方使用完整的样式。也许你对当前使用的样式感到满意,但有一个特定的按钮具有特殊的意义。
创建这个按钮的第一种方法是简单地将其定义在任何需要它的地方。例如,也许你对基本样式的按钮的方形角落不满意。为了使其圆润,可以覆盖 背景 项目并将矩形的半径属性设置为圆形
import QtQuick import QtQuick.Controls ApplicationWindow { width: 400 height: 400 visible: true Button { id: button text: "A Special Button" background: Rectangle { implicitWidth: 100 implicitHeight: 40 color: button.down ? "#d6d6d6" : "#f6f6f6" border.color: "#26282a" border.width: 1 radius: 4 } } }
注意:由于任何给定样式中组成控件的不同项目都被设计成可以协同工作,因此可能需要覆盖其他项目以获得您想要的外观。此外,并非所有样式都可以自定义。有关更多信息,请参阅自定义参考中的说明。
创建按钮的第二种方法是如果你计划在多个位置使用你的圆角按钮。它包括将代码移动到项目中的自己的 QML 文件。
为此方法,我们将从基本样式的 Button.qml
中复制背景代码。此文件可以在以下路径中的 Qt 安装中找到
$QTDIR/qml/QtQuick/Controls/Basic/Button.qml
完成此操作后,我们将添加以下行
radius: 4
为了避免与模块本身的控件混淆,我们将文件命名为 MyButton.qml
。为了在您的应用程序中使用控件,请通过其文件名引用它
import QtQuick.Controls ApplicationWindow { MyButton { text: qsTr("A Special Button") } }
创建按钮的第三种方法在文件系统的结构和 QML 中使用的方面都更加结构化。首先,如上所述复制现有的文件,但是这次,将其放入项目中的名为(例如)controls
的子文件夹。要使用控件,首先将文件夹导入一个名称空间
import QtQuick.Controls import "controls" as MyControls ApplicationWindow { MyControls.Button { text: qsTr("A Special Button") } }
现在您有了 MyControls
名称空间,您可以为控件命名,使其与 Qt Quick 控件模块中的实际对应项相匹配。您可以为任何希望添加的控制重复此过程。
这三个方法的好处之一是您不必从头开始实现模板。
注意:这里提到的三种方法不适用于自定义附加的 ToolTip,因为这是一个内部创建的共享项。要自定义一个单独的 ToolTip
,请参阅 自定义工具提示。要自定义附加的 ToolTip
,它必须是作为您自己的样式的一部分提供的。
创建自定义样式
创建自定义样式有几个方法。以下,我们将介绍不同的方法。
样式的定义
在 Qt Quick Controls 中,样式基本上是在单个目录内的 QML 文件集合。要使样式可用需要满足以下四个条件
- 必须至少存在一个与控件(例如,
Button.qml
)名称匹配的 QML 文件。 - 每个 QML 文件必须包含从 QtQuick.Templates 导入的相关类型作为根项。例如,Button.qml 必须包含一个 Button 模板作为其根项。
如果我们像前一个章节那样使用来自 QtQuick.Controls 的相应类型,它将不会工作:我们正在定义的控件会尝试从自己派生。
- 必须与 QML 文件(们)一起存在一个 qmldir 文件。以下是提供一个按钮样式的简单
qmldir
文件示例module MyStyle Button 2.15 Button.qml
如果你使用的是 编译时样式选择,则 qmldir 也应导入回退样式
# ... import QtQuick.Controls.Basic auto
也可以用于 运行时样式选择,而不是使用,例如,QQuickStyle::setFallbackStyle。
此类样式的目录结构如下
MyStyle ├─── Button.qml └─── qmldir
- 文件必须在可以通过 QML 导入路径 查找的目录中。
例如,如果上述 MyStyle 目录的路径是
/home/user/MyApp/MyStyle
,则必须将/home/user/MyApp
添加到 QML 导入路径。要在 MyApp 中使用 MyStyle,通过名称引用它
./MyApp -style MyStyle
样式名称必须与样式目录的 casing 匹配;传递 mystyle 或 MYSTYLE 不受支持。
默认情况下,样式系统使用基本样式作为未实现控件的回退。要自定义或扩展任何其他内置样式,可以使用 QQuickStyle 指定不同的回退样式。
这意味着你可以实现尽可能多的控件来为你的自定义样式,并将它们放置在几乎任何地方。这还有助于用户为你自己的应用程序创建他们自己的样式。
在 Qt Quick Designer 中预览自定义样式
使用上述方法,可以预览 Qt Quick Designer 中的自定义样式。为此,请确保项目有一个 qtquickcontrols2.conf 文件,并且存在以下条目
[Controls] Style=MyStyle
有关更多信息,请参阅 Flat Style 示例。
样式特定的 C++ 扩展
有时您可能需要使用 C++ 来扩展您的自定义样式。
- 如果使用该类型的样式是应用程序使用的唯一样式,则通过添加 QML_ELEMENT 宏将类型注册到 QML 引擎,并使文件成为您 QML 模块的一部分
qt_add_qml_module(ACoolItem URI MyItems VERSION 1.0 SOURCES acoolcppitem.cpp acoolcppitem.h )
CONFIG += qmltypes QML_IMPORT_NAME = MyItems QML_IMPORT_MAJOR_VERSION = 1
如果声明类的头文件无法从您的项目包含路径访问,则可能必须修改包含路径,以便编译生成的注册代码。
INCLUDEPATH += MyItems
参阅从C++定义QML类型和构建QML应用程序获取更多信息。
- 如果应用的样式是多钟样式之一,考虑将每个样式放入一个单独的模块中。这样模块将在需要时加载。
自定义样式的注意事项
在实现自己的样式并自定义控件时,需要注意一些要点,以确保您的应用程序尽可能高效。
避免为样式实现的item委托分配id
如样式定义中所述,实现控件的自定义样式时,从该控件的相应模板开始。例如,样式的Button.qml
将与以下结构类似
T.Button { // ... background: Rectangle { // ... } contentItem: Text { // ... } // ... }
在您的应用程序中使用Button时,将会创建并添加到根Button
项的background
和contentItem
项
// Creates the Button root item, the Rectangle background, // and the Text contentItem. Button { text: qsTr("Confirm") }
假设您需要对Button进行一次性的自定义(如自定义控件所述)
import QtQuick import QtQuick.Controls ApplicationWindow { width: 400 height: 400 visible: true Button { id: button text: "A Special Button" background: Rectangle { implicitWidth: 100 implicitHeight: 40 color: button.down ? "#d6d6d6" : "#f6f6f6" border.color: "#26282a" border.width: 1 radius: 4 } } }
在QML中,这通常会导致创建默认的background
实现和一次性自定义的background
项。Qt Quick Controls使用一种技术,可以避免创建这两个项,而是只创建自定义的background
,大大提高了控件创建的性能。
这种技术依赖于在样式的该项实现中不存在id。如果分配了id,该技术将无法工作,并且将创建两个项。例如,给background
或contentItem
分配一个id可能会很诱人,以便文件内的其他对象可以引用这些项
T.Button { // ... background: Rectangle { id: backgroundRect // ... } contentItem: Text { // Use backgroundRect in some way... } // ... }
使用此代码,每次创建具有自定义背景的Button实例时,都将创建两个背景,从而导致创建性能不佳。
在Qt 5.15之前,不再使用的旧背景会被删除以释放其资源。然而,由于控件不拥有这些项,因此不应删除它们。从Qt 5.15开始,旧项不再被删除,因此backgroundRect
项的寿命会比所需时间长——通常直到应用程序退出。尽管旧对象已被隐藏,从视觉上与控件分离,并从可访问性树中删除,但在分配id时仍需注意这些未使用项的创建时间和内存使用。
避免对自定义项进行命令式分配
上述章节中提到的技术仅在项首次声明时才有效,因此命令式分配会导致无父项的对象。尽可能使用声明性绑定来分配自定义项。
不要在QML实现中导入QtQuick.Controls
在编写风格的控件实现QML时,重要的一点是不导入QtQuick.Controls
。这样做会阻止QML编译器编译QML。
实现其他类型使用的数据类型
假设您在应用程序中使用ScrollViews,并且决定要自定义其滚动条。直接实现自定义ScrollBar.qml并让ScrollView自动拾取自定义的ScrollBar,这看起来很吸引人。然而,这不会奏效。您必须实现ScrollBar.qml和ScrollView.qml。
附属属性
样式通常具有某些应用所有控件的属性或属性。使用附属属性是扩展QML中的项而无需修改该项隶属的任何现有C++代码的绝佳方式。例如,Material和Universal样式都有一个附属主题属性,用于控制项及其子项是否以亮色或暗色主题渲染。
以一个示例,让我们添加一个控制抬高的附属属性。我们的样式将通过阴影展示抬高;抬高值越高,阴影越大。
第一步是在Qt Creator中创建一个新Qt Quick Controls应用程序。之后,我们添加一个C++类型来存储抬高。由于此类型将用于我们风格支持的所有控件,并且我们可能希望在将来添加其他附属属性,我们将其命名为MyStyle。下面是MyStyle.h
#ifndef MYSTYLE_H #define MYSTYLE_H #include <QObject> #include <QtQml> class MyStyle : public QObject { Q_OBJECT Q_PROPERTY(int elevation READ elevation WRITE setElevation NOTIFY elevationChanged) public: explicit MyStyle(QObject *parent = nullptr); static MyStyle *qmlAttachedProperties(QObject *object); int elevation() const; void setElevation(int elevation); signals: void elevationChanged(); private: int m_elevation; }; QML_DECLARE_TYPEINFO(MyStyle, QML_HAS_ATTACHED_PROPERTIES) #endif // MYSTYLE_H
MyStyle.cpp
:
#include "mystyle.h" MyStyle::MyStyle(QObject *parent) : QObject(parent), m_elevation(0) { } MyStyle *MyStyle::qmlAttachedProperties(QObject *object) { return new MyStyle(object); } int MyStyle::elevation() const { return m_elevation; } void MyStyle::setElevation(int elevation) { if (elevation == m_elevation) return; m_elevation = elevation; emit elevationChanged(); }
在MyStyle
类型中是特殊的,因为它不应该被实例化,而应该用于其附属属性。因此,我们在main.cpp
中以以下方式注册它
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "mystyle.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); qmlRegisterUncreatableType<MyStyle>("MyStyle", 1, 0, "MyStyle", "MyStyle is an attached property"); QQmlApplicationEngine engine; // Make the directory containing our style known to the QML engine. engine.addImportPath(":/"); engine.load(QUrl(QLatin1String("qrc:/main.qml"))); return app.exec(); }
然后,我们将Button.qml
从Basic样式中的$QTDIR/qml/QtQuick/Controls/Basic/
复制到项目中myproject
文件夹。将新复制的Button.qml
添加到qml.qrc
,这是我们包含QML文件的资源文件。
接下来,我们为Button的background代表添加一个阴影。
// ... import QtQuick.Effects import MyStyle // ... background: Rectangle { // ... layer.enabled: control.enabled && control.MyStyle.elevation > 0 layer.effect: MultiEffect { shadowEnabled: true shadowHorizontalOffset: 3 shadowVerticalOffset: 3 shadowColor: control.visualFocus ? "#330066ff" : "#aaaaaa" shadowBlur: control.pressed ? 0.8 : 0.4 } }
请注意,我们在
- 当抬高等于
0
时,无需使用阴影 - 根据按钮是否具有焦点更改阴影的颜色
- 使阴影的大小取决于抬高
为了尝试附属属性,我们在main.qml
中创建一个带有两个按钮的Row
import QtQuick import QtQuick.Controls import MyStyle 1.0 ApplicationWindow { id: window width: 400 height: 400 visible: true Row { spacing: 20 anchors.centerIn: parent Button { text: "Button 1" } Button { text: "Button 2" MyStyle.elevation: 10 } } }
一个按钮没有抬高,而另一个的抬高为10
。
这样一来,我们就可以运行示例了。要告诉应用程序使用我们新的风格,我们将-style MyStyle
作为应用程序参数传递,但指定要使用的风格的方法有很多。
最终效果
请注意,import MyStyle 1.0
语句只因为我们在使用属于MyStyle
的附属属性。即使我们删除导入,两个按钮也将使用我们的自定义风格。
自定义参考
以下代码片段展示了使用与自定义控件部分相同的方法自定义Basic样式的控件。代码可以作为实现自定义外观和感受的起点。
注意:以下样式不适用于自定义:macOS 和 Windows。建议始终以所有平台都有的单个样式为基础进行自定义,例如 基本样式、融合样式、想象样式、材质样式、通用样式。这样,无论应用运行在哪种样式中,都可以保证外观始终相同。要了解如何使用不同的样式,请参阅 在 Qt Quick 控件中使用样式。或者,您可以 创建自己的样式。
自定义 ApplicationWindow
ApplicationWindow 包含一个视觉项目:背景。
import QtQuick import QtQuick.Controls.Basic ApplicationWindow { visible: true background: Rectangle { gradient: Gradient { GradientStop { position: 0; color: "#ffffff" } GradientStop { position: 1; color: "#c1bbf9" } } } }
自定义 BusyIndicator
BusyIndicator 包含两个视觉项目:背景 和 内容项目。
import QtQuick import QtQuick.Controls.Basic BusyIndicator { id: control contentItem: Item { implicitWidth: 64 implicitHeight: 64 Item { id: item x: parent.width / 2 - 32 y: parent.height / 2 - 32 width: 64 height: 64 opacity: control.running ? 1 : 0 Behavior on opacity { OpacityAnimator { duration: 250 } } RotationAnimator { target: item running: control.visible && control.running from: 0 to: 360 loops: Animation.Infinite duration: 1250 } Repeater { id: repeater model: 6 Rectangle { id: delegate x: item.width / 2 - width / 2 y: item.height / 2 - height / 2 implicitWidth: 10 implicitHeight: 10 radius: 5 color: "#21be2b" required property int index transform: [ Translate { y: -Math.min(item.width, item.height) * 0.5 + 5 }, Rotation { angle: delegate.index / repeater.count * 360 origin.x: 5 origin.y: 5 } ] } } } } }
自定义 Button
import QtQuick import QtQuick.Controls.Basic Button { id: control text: qsTr("Button") contentItem: Text { text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: control.down ? "#17a81a" : "#21be2b" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } background: Rectangle { implicitWidth: 100 implicitHeight: 40 opacity: enabled ? 1 : 0.3 border.color: control.down ? "#17a81a" : "#21be2b" border.width: 1 radius: 2 } }
自定义 CheckBox
CheckBox 包含三个视觉项目:背景、内容项目 和 指示器。
import QtQuick import QtQuick.Controls.Basic CheckBox { id: control text: qsTr("CheckBox") checked: true indicator: Rectangle { implicitWidth: 26 implicitHeight: 26 x: control.leftPadding y: parent.height / 2 - height / 2 radius: 3 border.color: control.down ? "#17a81a" : "#21be2b" Rectangle { width: 14 height: 14 x: 6 y: 6 radius: 2 color: control.down ? "#17a81a" : "#21be2b" visible: control.checked } } contentItem: Text { text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: control.down ? "#17a81a" : "#21be2b" verticalAlignment: Text.AlignVCenter leftPadding: control.indicator.width + control.spacing } }
自定义 CheckDelegate
CheckDelegate 包含三个视觉项目:背景、内容项目 和 指示器。
import QtQuick import QtQuick.Controls.Basic CheckDelegate { id: control text: qsTr("CheckDelegate") checked: true contentItem: Text { rightPadding: control.indicator.width + control.spacing text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: control.down ? "#17a81a" : "#21be2b" elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } indicator: Rectangle { implicitWidth: 26 implicitHeight: 26 x: control.width - width - control.rightPadding y: control.topPadding + control.availableHeight / 2 - height / 2 radius: 3 color: "transparent" border.color: control.down ? "#17a81a" : "#21be2b" Rectangle { width: 14 height: 14 x: 6 y: 6 radius: 2 color: control.down ? "#17a81a" : "#21be2b" visible: control.checked } } background: Rectangle { implicitWidth: 100 implicitHeight: 40 visible: control.down || control.highlighted color: control.down ? "#bdbebf" : "#eeeeee" } }
自定义 ComboBox
ComboBox 包含 背景、内容项目、弹出窗口、指示器 和 代理。
pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls.Basic ComboBox { id: control model: ["First", "Second", "Third"] delegate: ItemDelegate { id: delegate required property var model required property int index width: control.width contentItem: Text { text: delegate.model[control.textRole] color: "#21be2b" font: control.font elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } highlighted: control.highlightedIndex === index } indicator: Canvas { id: canvas x: control.width - width - control.rightPadding y: control.topPadding + (control.availableHeight - height) / 2 width: 12 height: 8 contextType: "2d" Connections { target: control function onPressedChanged() { canvas.requestPaint(); } } onPaint: { context.reset(); context.moveTo(0, 0); context.lineTo(width, 0); context.lineTo(width / 2, height); context.closePath(); context.fillStyle = control.pressed ? "#17a81a" : "#21be2b"; context.fill(); } } contentItem: Text { leftPadding: 0 rightPadding: control.indicator.width + control.spacing text: control.displayText font: control.font color: control.pressed ? "#17a81a" : "#21be2b" verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } background: Rectangle { implicitWidth: 120 implicitHeight: 40 border.color: control.pressed ? "#17a81a" : "#21be2b" border.width: control.visualFocus ? 2 : 1 radius: 2 } popup: Popup { y: control.height - 1 width: control.width implicitHeight: contentItem.implicitHeight padding: 1 contentItem: ListView { clip: true implicitHeight: contentHeight model: control.popup.visible ? control.delegateModel : null currentIndex: control.highlightedIndex ScrollIndicator.vertical: ScrollIndicator { } } background: Rectangle { border.color: "#21be2b" radius: 2 } } }
如 ComboBox 模型角色 所述,ComboBox 支持多种类型的模型。
由于 所有模型都提供了一个匿名属性 modelData
,以下表达式可以在所有情况下检索到正确的文本
text: model[control.textRole]
当您提供一个特定的 textRole
以及一个包含结构化数据且提供选定角色的模型时,此表达式是常规属性查找。当您提供一个如字符串列表的单个数据模型且一个空的 textRole
时,这个表达式就检索 modelData
。
自定义 DelayButton
DelayButton 包含两个视觉项目:背景 和 内容项目。
import QtQuick import QtQuick.Controls.Basic DelayButton { id: control checked: true text: qsTr("Delay\nButton") contentItem: Text { text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: "white" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } background: Rectangle { implicitWidth: 100 implicitHeight: 100 opacity: enabled ? 1 : 0.3 color: control.down ? "#17a81a" : "#21be2b" radius: size / 2 readonly property real size: Math.min(control.width, control.height) width: size height: size anchors.centerIn: parent Canvas { id: canvas anchors.fill: parent Connections { target: control function onProgressChanged() { canvas.requestPaint(); } } onPaint: { var ctx = getContext("2d") ctx.clearRect(0, 0, width, height) ctx.strokeStyle = "white" ctx.lineWidth = parent.size / 20 ctx.beginPath() var startAngle = Math.PI / 5 * 3 var endAngle = startAngle + control.progress * Math.PI / 5 * 9 ctx.arc(width / 2, height / 2, width / 2 - ctx.lineWidth / 2 - 2, startAngle, endAngle) ctx.stroke() } } } }
自定义 Dial
import QtQuick import QtQuick.Controls.Basic Dial { id: control background: Rectangle { x: control.width / 2 - width / 2 y: control.height / 2 - height / 2 implicitWidth: 140 implicitHeight: 140 width: Math.max(64, Math.min(control.width, control.height)) height: width color: "transparent" radius: width / 2 border.color: control.pressed ? "#17a81a" : "#21be2b" opacity: control.enabled ? 1 : 0.3 } handle: Rectangle { id: handleItem x: control.background.x + control.background.width / 2 - width / 2 y: control.background.y + control.background.height / 2 - height / 2 width: 16 height: 16 color: control.pressed ? "#17a81a" : "#21be2b" radius: 8 antialiasing: true opacity: control.enabled ? 1 : 0.3 transform: [ Translate { y: -Math.min(control.background.width, control.background.height) * 0.4 + handleItem.height / 2 }, Rotation { angle: control.angle origin.x: handleItem.width / 2 origin.y: handleItem.height / 2 } ] } }
自定义 Drawer
抽屉可以包含一个视觉 背景 项。
background: Rectangle { Rectangle { x: parent.width - 1 width: 1 height: parent.height color: "#21be2b" } }
定制框架
框架由一个视觉项组成: 背景。
import QtQuick import QtQuick.Controls.Basic Frame { background: Rectangle { color: "transparent" border.color: "#21be2b" radius: 2 } Label { text: qsTr("Content goes here!") } }
定制分组框
import QtQuick import QtQuick.Controls.Basic GroupBox { id: control title: qsTr("GroupBox") background: Rectangle { y: control.topPadding - control.bottomPadding width: parent.width height: parent.height - control.topPadding + control.bottomPadding color: "transparent" border.color: "#21be2b" radius: 2 } label: Label { x: control.leftPadding width: control.availableWidth text: control.title color: "#21be2b" elide: Text.ElideRight } Label { text: qsTr("Content goes here!") } }
定制项代理
import QtQuick import QtQuick.Controls.Basic ItemDelegate { id: control text: qsTr("ItemDelegate") contentItem: Text { rightPadding: control.spacing text: control.text font: control.font color: control.enabled ? (control.down ? "#17a81a" : "#21be2b") : "#bdbebf" elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } background: Rectangle { implicitWidth: 100 implicitHeight: 40 opacity: enabled ? 1 : 0.3 color: control.down ? "#dddedf" : "#eeeeee" Rectangle { width: parent.width height: 1 color: control.down ? "#17a81a" : "#21be2b" anchors.bottom: parent.bottom } } }
定制标签
标签可以有一个视觉 背景 项。
import QtQuick import QtQuick.Controls.Basic Label { text: qsTr("Label") color: "#21be2b" }
定制菜单
import QtQuick import QtQuick.Controls.Basic Menu { id: menu Action { text: qsTr("Tool Bar"); checkable: true } Action { text: qsTr("Side Bar"); checkable: true; checked: true } Action { text: qsTr("Status Bar"); checkable: true; checked: true } MenuSeparator { contentItem: Rectangle { implicitWidth: 200 implicitHeight: 1 color: "#21be2b" } } Menu { title: qsTr("Advanced") // ... } topPadding: 2 bottomPadding: 2 delegate: MenuItem { id: menuItem implicitWidth: 200 implicitHeight: 40 arrow: Canvas { x: parent.width - width implicitWidth: 40 implicitHeight: 40 visible: menuItem.subMenu onPaint: { var ctx = getContext("2d") ctx.fillStyle = menuItem.highlighted ? "#ffffff" : "#21be2b" ctx.moveTo(15, 15) ctx.lineTo(width - 15, height / 2) ctx.lineTo(15, height - 15) ctx.closePath() ctx.fill() } } indicator: Item { implicitWidth: 40 implicitHeight: 40 Rectangle { width: 26 height: 26 anchors.centerIn: parent visible: menuItem.checkable border.color: "#21be2b" radius: 3 Rectangle { width: 14 height: 14 anchors.centerIn: parent visible: menuItem.checked color: "#21be2b" radius: 2 } } } contentItem: Text { leftPadding: menuItem.indicator.width rightPadding: menuItem.arrow.width text: menuItem.text font: menuItem.font opacity: enabled ? 1.0 : 0.3 color: menuItem.highlighted ? "#ffffff" : "#21be2b" horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } background: Rectangle { implicitWidth: 200 implicitHeight: 40 opacity: enabled ? 1 : 0.3 color: menuItem.highlighted ? "#21be2b" : "transparent" } } background: Rectangle { implicitWidth: 200 implicitHeight: 40 color: "#ffffff" border.color: "#21be2b" radius: 2 } }
定制菜单栏
菜单栏 可以有一个视觉 背景 项,并且 菜单栏项 由两个视觉项组成: 背景 和 内容项。
import QtQuick import QtQuick.Controls.Basic MenuBar { id: menuBar Menu { title: qsTr("File") } Menu { title: qsTr("Edit") } Menu { title: qsTr("View") } Menu { title: qsTr("Help") } delegate: MenuBarItem { id: menuBarItem contentItem: Text { text: menuBarItem.text font: menuBarItem.font opacity: enabled ? 1.0 : 0.3 color: menuBarItem.highlighted ? "#ffffff" : "#21be2b" horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } background: Rectangle { implicitWidth: 40 implicitHeight: 40 opacity: enabled ? 1 : 0.3 color: menuBarItem.highlighted ? "#21be2b" : "transparent" } } background: Rectangle { implicitWidth: 40 implicitHeight: 40 color: "#ffffff" Rectangle { color: "#21be2b" width: parent.width height: 1 anchors.bottom: parent.bottom } } }
定制页指示器
import QtQuick import QtQuick.Controls.Basic PageIndicator { id: control count: 5 currentIndex: 2 delegate: Rectangle { implicitWidth: 8 implicitHeight: 8 radius: width / 2 color: "#21be2b" opacity: index === control.currentIndex ? 0.95 : pressed ? 0.7 : 0.45 required property int index Behavior on opacity { OpacityAnimator { duration: 100 } } } }
定制面板
面板由一个 背景 组成。
import QtQuick import QtQuick.Controls.Basic Pane { background: Rectangle { color: "#eeeeee" } Label { text: qsTr("Content goes here!") } }
定制弹出窗口
import QtQuick import QtQuick.Controls.Basic Popup { id: popup background: Rectangle { implicitWidth: 200 implicitHeight: 200 border.color: "#444" } contentItem: Column {} }
定制进度条
import QtQuick import QtQuick.Controls.Basic ProgressBar { id: control value: 0.5 padding: 2 background: Rectangle { implicitWidth: 200 implicitHeight: 6 color: "#e6e6e6" radius: 3 } contentItem: Item { implicitWidth: 200 implicitHeight: 4 // Progress indicator for determinate state. Rectangle { width: control.visualPosition * parent.width height: parent.height radius: 2 color: "#17a81a" visible: !control.indeterminate } // Scrolling animation for indeterminate state. Item { anchors.fill: parent visible: control.indeterminate clip: true Row { spacing: 20 Repeater { model: control.width / 40 + 1 Rectangle { color: "#17a81a" width: 20 height: control.height } } XAnimator on x { from: 0 to: -40 loops: Animation.Infinite running: control.indeterminate } } } } }
上面的内容项也进行了动画处理,以表示一个 不可定 的进度条状态。
定制单选按钮
import QtQuick import QtQuick.Controls.Basic RadioButton { id: control text: qsTr("RadioButton") checked: true indicator: Rectangle { implicitWidth: 26 implicitHeight: 26 x: control.leftPadding y: parent.height / 2 - height / 2 radius: 13 border.color: control.down ? "#17a81a" : "#21be2b" Rectangle { width: 14 height: 14 x: 6 y: 6 radius: 7 color: control.down ? "#17a81a" : "#21be2b" visible: control.checked } } contentItem: Text { text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: control.down ? "#17a81a" : "#21be2b" verticalAlignment: Text.AlignVCenter leftPadding: control.indicator.width + control.spacing } }
定制单选代理
RadioDelegate 由三个视觉元素组成:背景、内容元素和指示器。
import QtQuick import QtQuick.Controls.Basic RadioDelegate { id: control text: qsTr("RadioDelegate") checked: true contentItem: Text { rightPadding: control.indicator.width + control.spacing text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: control.down ? "#17a81a" : "#21be2b" elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } indicator: Rectangle { implicitWidth: 26 implicitHeight: 26 x: control.width - width - control.rightPadding y: parent.height / 2 - height / 2 radius: 13 color: "transparent" border.color: control.down ? "#17a81a" : "#21be2b" Rectangle { width: 14 height: 14 x: 6 y: 6 radius: 7 color: control.down ? "#17a81a" : "#21be2b" visible: control.checked } } background: Rectangle { implicitWidth: 100 implicitHeight: 40 visible: control.down || control.highlighted color: control.down ? "#bdbebf" : "#eeeeee" } }
自定义 RangeSlider
RangeSlider 由三个视觉元素组成:背景、第一个句柄和第二个句柄。
import QtQuick import QtQuick.Controls.Basic RangeSlider { id: control first.value: 0.25 second.value: 0.75 background: Rectangle { x: control.leftPadding y: control.topPadding + control.availableHeight / 2 - height / 2 implicitWidth: 200 implicitHeight: 4 width: control.availableWidth height: implicitHeight radius: 2 color: "#bdbebf" Rectangle { x: control.first.visualPosition * parent.width width: control.second.visualPosition * parent.width - x height: parent.height color: "#21be2b" radius: 2 } } first.handle: Rectangle { x: control.leftPadding + control.first.visualPosition * (control.availableWidth - width) y: control.topPadding + control.availableHeight / 2 - height / 2 implicitWidth: 26 implicitHeight: 26 radius: 13 color: control.first.pressed ? "#f0f0f0" : "#f6f6f6" border.color: "#bdbebf" } second.handle: Rectangle { x: control.leftPadding + control.second.visualPosition * (control.availableWidth - width) y: control.topPadding + control.availableHeight / 2 - height / 2 implicitWidth: 26 implicitHeight: 26 radius: 13 color: control.second.pressed ? "#f0f0f0" : "#f6f6f6" border.color: "#bdbebf" } }
自定义 RoundButton
RoundButton 可以以与按钮相同的方式进行自定义。
自定义 ScrollBar
import QtQuick import QtQuick.Controls.Basic ScrollBar { id: control size: 0.3 position: 0.2 active: true orientation: Qt.Vertical contentItem: Rectangle { implicitWidth: 6 implicitHeight: 100 radius: width / 2 color: control.pressed ? "#81e889" : "#c2f4c6" // Hide the ScrollBar when it's not needed. opacity: control.policy === ScrollBar.AlwaysOn || (control.active && control.size < 1.0) ? 0.75 : 0 // Animate the changes in opacity (default duration is 250 ms). Behavior on opacity { NumberAnimation {} } } }
自定义 ScrollIndicator
ScrollIndicator 由两个视觉元素组成:背景和内容元素。
import QtQuick import QtQuick.Controls.Basic ScrollIndicator { id: control size: 0.3 position: 0.2 active: true orientation: Qt.Vertical contentItem: Rectangle { implicitWidth: 2 implicitHeight: 100 color: "#c2f4c6" } }
自定义 ScrollView
ScrollView 包含一个 背景元素以及水平和垂直滚动条。
ScrollView { id: control width: 200 height: 200 focus: true Label { text: "ABC" font.pixelSize: 224 } ScrollBar.vertical: ScrollBar { parent: control x: control.mirrored ? 0 : control.width - width y: control.topPadding height: control.availableHeight active: control.ScrollBar.horizontal.active } ScrollBar.horizontal: ScrollBar { parent: control x: control.leftPadding y: control.height - height width: control.availableWidth active: control.ScrollBar.vertical.active } background: Rectangle { border.color: control.activeFocus ? "#21be2b" : "#bdbebf" } }
自定义 Slider
import QtQuick import QtQuick.Controls.Basic Slider { id: control value: 0.5 background: Rectangle { x: control.leftPadding y: control.topPadding + control.availableHeight / 2 - height / 2 implicitWidth: 200 implicitHeight: 4 width: control.availableWidth height: implicitHeight radius: 2 color: "#bdbebf" Rectangle { width: control.visualPosition * parent.width height: parent.height color: "#21be2b" radius: 2 } } handle: Rectangle { x: control.leftPadding + control.visualPosition * (control.availableWidth - width) y: control.topPadding + control.availableHeight / 2 - height / 2 implicitWidth: 26 implicitHeight: 26 radius: 13 color: control.pressed ? "#f0f0f0" : "#f6f6f6" border.color: "#bdbebf" } }
自定义 SpinBox
SpinBox 包含四个视觉元素:背景、内容元素、向上指示器和向下指示器。
import QtQuick import QtQuick.Controls.Basic SpinBox { id: control value: 50 editable: true contentItem: TextInput { z: 2 text: control.textFromValue(control.value, control.locale) font: control.font color: "#21be2b" selectionColor: "#21be2b" selectedTextColor: "#ffffff" horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter readOnly: !control.editable validator: control.validator inputMethodHints: Qt.ImhFormattedNumbersOnly } up.indicator: Rectangle { x: control.mirrored ? 0 : parent.width - width height: parent.height implicitWidth: 40 implicitHeight: 40 color: control.up.pressed ? "#e4e4e4" : "#f6f6f6" border.color: enabled ? "#21be2b" : "#bdbebf" Text { text: "+" font.pixelSize: control.font.pixelSize * 2 color: "#21be2b" anchors.fill: parent fontSizeMode: Text.Fit horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } down.indicator: Rectangle { x: control.mirrored ? parent.width - width : 0 height: parent.height implicitWidth: 40 implicitHeight: 40 color: control.down.pressed ? "#e4e4e4" : "#f6f6f6" border.color: enabled ? "#21be2b" : "#bdbebf" Text { text: "-" font.pixelSize: control.font.pixelSize * 2 color: "#21be2b" anchors.fill: parent fontSizeMode: Text.Fit horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } background: Rectangle { implicitWidth: 140 border.color: "#bdbebf" } }
自定义 SplitView
SplitView { id: splitView anchors.fill: parent handle: Rectangle { implicitWidth: 4 implicitHeight: 4 color: SplitHandle.pressed ? "#81e889" : (SplitHandle.hovered ? Qt.lighter("#c2f4c6", 1.1) : "#c2f4c6") } Rectangle { implicitWidth: 150 color: "#444" } Rectangle { implicitWidth: 50 color: "#666" } }
自定义 StackView
StackView 可以包含一个视觉 背景元素,并允许自定义用于推送、弹出和替换操作的转换。
import QtQuick import QtQuick.Controls.Basic StackView { id: control popEnter: Transition { XAnimator { from: (control.mirrored ? -1 : 1) * -control.width to: 0 duration: 400 easing.type: Easing.OutCubic } } popExit: Transition { XAnimator { from: 0 to: (control.mirrored ? -1 : 1) * control.width duration: 400 easing.type: Easing.OutCubic } } }
自定义 SwipeDelegate
SwipeDelegate 由六个视觉元素组成:背景、内容元素、指示器、swipe.left
、swipe.right
和swipe.behind
。
import QtQuick import QtQuick.Controls.Basic SwipeDelegate { id: control text: qsTr("SwipeDelegate") Component { id: component Rectangle { color: SwipeDelegate.pressed ? "#333" : "#444" width: parent.width height: parent.height clip: true Label { text: qsTr("Press me!") color: "#21be2b" anchors.centerIn: parent } } } swipe.left: component swipe.right: component contentItem: Text { text: control.text font: control.font color: control.enabled ? (control.down ? "#17a81a" : "#21be2b") : "#bdbebf" elide: Text.ElideRight verticalAlignment: Text.AlignVCenter Behavior on x { enabled: !control.down NumberAnimation { easing.type: Easing.InOutCubic duration: 400 } } } }
自定义 SwipeView
SwipeView 可以包含一个视觉 背景元素。导航是通过内容元素实现的。
import QtQuick import QtQuick.Controls.Basic SwipeView { id: control background: Rectangle { color: "#eeeeee" } }
自定义 Switch
import QtQuick import QtQuick.Controls.Basic Switch { id: control text: qsTr("Switch") indicator: Rectangle { implicitWidth: 48 implicitHeight: 26 x: control.leftPadding y: parent.height / 2 - height / 2 radius: 13 color: control.checked ? "#17a81a" : "#ffffff" border.color: control.checked ? "#17a81a" : "#cccccc" Rectangle { x: control.checked ? parent.width - width : 0 width: 26 height: 26 radius: 13 color: control.down ? "#cccccc" : "#ffffff" border.color: control.checked ? (control.down ? "#17a81a" : "#21be2b") : "#999999" } } contentItem: Text { text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: control.down ? "#17a81a" : "#21be2b" verticalAlignment: Text.AlignVCenter leftPadding: control.indicator.width + control.spacing } }
自定义SwitchDelegate
SwitchDelegate由三个可视元素组成:背景、内容项和指示器。
import QtQuick import QtQuick.Controls.Basic SwitchDelegate { id: control text: qsTr("SwitchDelegate") checked: true contentItem: Text { rightPadding: control.indicator.width + control.spacing text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: control.down ? "#17a81a" : "#21be2b" elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } indicator: Rectangle { implicitWidth: 48 implicitHeight: 26 x: control.width - width - control.rightPadding y: parent.height / 2 - height / 2 radius: 13 color: control.checked ? "#17a81a" : "transparent" border.color: control.checked ? "#17a81a" : "#cccccc" Rectangle { x: control.checked ? parent.width - width : 0 width: 26 height: 26 radius: 13 color: control.down ? "#cccccc" : "#ffffff" border.color: control.checked ? (control.down ? "#17a81a" : "#21be2b") : "#999999" } } background: Rectangle { implicitWidth: 100 implicitHeight: 40 visible: control.down || control.highlighted color: control.down ? "#bdbebf" : "#eeeeee" } }
自定义TabBar
import QtQuick import QtQuick.Controls.Basic TabBar { id: control background: Rectangle { color: "#eeeeee" } TabButton { text: qsTr("Home") } TabButton { text: qsTr("Discover") } TabButton { text: qsTr("Activity") } }
自定义TabButton
自定义TextArea
import QtQuick import QtQuick.Controls.Basic TextArea { id: control placeholderText: qsTr("Enter description") background: Rectangle { implicitWidth: 200 implicitHeight: 40 border.color: control.enabled ? "#21be2b" : "transparent" } }
自定义TextField
import QtQuick import QtQuick.Controls.Basic TextField { id: control placeholderText: qsTr("Enter description") background: Rectangle { implicitWidth: 200 implicitHeight: 40 color: control.enabled ? "transparent" : "#353637" border.color: control.enabled ? "#21be2b" : "transparent" } }
自定义ToolBar
ToolBar { id: control background: Rectangle { implicitHeight: 40 color: "#eeeeee" Rectangle { width: parent.width height: 1 anchors.bottom: parent.bottom color: "transparent" border.color: "#21be2b" } } RowLayout { anchors.fill: parent ToolButton { text: qsTr("Undo") } ToolButton { text: qsTr("Redo") } } }
自定义ToolButton
ToolButton由两个可视元素组成:背景和内容项。
import QtQuick import QtQuick.Controls.Basic ToolButton { id: control text: qsTr("ToolButton") width: 120 contentItem: Text { text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: control.down ? "#17a81a" : "#21be2b" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } background: Rectangle { implicitWidth: 40 implicitHeight: 40 color: Qt.darker("#33333333", control.enabled && (control.checked || control.highlighted) ? 1.5 : 1.0) opacity: enabled ? 1 : 0.3 visible: control.down || (control.enabled && (control.checked || control.highlighted)) } }
自定义ToolSeparator
ToolSeparator由两个可视元素组成:背景和内容项。
ToolBar { RowLayout { anchors.fill: parent ToolButton { text: qsTr("Action 1") } ToolButton { text: qsTr("Action 2") } ToolSeparator { padding: vertical ? 10 : 2 topPadding: vertical ? 2 : 10 bottomPadding: vertical ? 2 : 10 contentItem: Rectangle { implicitWidth: parent.vertical ? 1 : 24 implicitHeight: parent.vertical ? 24 : 1 color: "#c3c3c3" } } ToolButton { text: qsTr("Action 3") } ToolButton { text: qsTr("Action 4") } Item { Layout.fillWidth: true } } }
自定义ToolTip
import QtQuick import QtQuick.Controls.Basic ToolTip { id: control text: qsTr("A descriptive tool tip of what the button does") contentItem: Text { text: control.text font: control.font color: "#21be2b" } background: Rectangle { border.color: "#21be2b" } }
自定义Tumbler
import QtQuick import QtQuick.Controls.Basic Tumbler { id: control model: 15 background: Item { Rectangle { opacity: control.enabled ? 0.2 : 0.1 border.color: "#000000" width: parent.width height: 1 anchors.top: parent.top } Rectangle { opacity: control.enabled ? 0.2 : 0.1 border.color: "#000000" width: parent.width height: 1 anchors.bottom: parent.bottom } } delegate: Text { text: qsTr("Item %1").arg(modelData + 1) font: control.font horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter opacity: 1.0 - Math.abs(Tumbler.displacement) / (control.visibleItemCount / 2) required property var modelData required property int index } Rectangle { anchors.horizontalCenter: control.horizontalCenter y: control.height * 0.4 width: 40 height: 1 color: "#21be2b" } Rectangle { anchors.horizontalCenter: control.horizontalCenter y: control.height * 0.6 width: 40 height: 1 color: "#21be2b" } }
如果您想定义自己的内容项,可以使用ListView或PathView作为根项。对于包装式Tumbler,使用PathView。
Tumbler { id: tumbler contentItem: PathView { id: pathView model: tumbler.model delegate: tumbler.delegate clip: true pathItemCount: tumbler.visibleItemCount + 1 preferredHighlightBegin: 0.5 preferredHighlightEnd: 0.5 dragMargin: width / 2 path: Path { startX: pathView.width / 2 startY: -pathView.delegateHeight / 2 PathLine { x: pathView.width / 2 y: pathView.pathItemCount * pathView.delegateHeight - pathView.delegateHeight / 2 } } property real delegateHeight: tumbler.availableHeight / tumbler.visibleItemCount } }
对于非包装式Tumbler,使用ListView。
Tumbler { id: tumbler contentItem: ListView { model: tumbler.model delegate: tumbler.delegate snapMode: ListView.SnapToItem highlightRangeMode: ListView.StrictlyEnforceRange preferredHighlightBegin: height / 2 - (height / tumbler.visibleItemCount / 2) preferredHighlightEnd: height / 2 + (height / tumbler.visibleItemCount / 2) clip: true } }
© 2024 Qt公司有限公司。本文件的文档贡献属于各自的版权所有者。本文件提供的文档是根据免费的软件基金会发布的、由自由软件基金会发布的 GNU自由文档许可协议第1.3版进行许可的。