自定义Qt Quick Controls
Qt Quick Controls FluinWinUI 风格控件由一个项目层次结构(树形)组成。为了提供自定义的外观和感觉,每个项目的默认 QML 实现都可以用自定义的实现来代替。
自定义控件
有时,您想为用户界面的特定部分创建 "一次性 "外观,而在其他地方使用完整的样式。也许你对目前使用的样式很满意,但有一个按钮具有特殊意义。
创建该按钮的第一种方法是在需要的地方就地定义。例如,你可能不满意基本样式的按钮边角是方形的。要使其变成圆角,可以覆盖background 项,并将半径属性设置为矩形:
import QtQuick import QtQuick.Controls.Basic 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 文件中。
在这种方法中,我们将从 Basic 样式的Button.qml
中复制背景代码。该文件可在 Qt 安装的以下路径中找到:
$QTDIR/qml/QtQuick/Controls/Basic/Button.qml
之后,我们只需添加以下一行即可:
radius: 4
为避免与模块中的控件混淆,我们将该文件命名为MyButton.qml
。要在应用程序中使用该控件,请使用其文件名:
import QtQuick.Controls.Basic ApplicationWindow { MyButton { text: qsTr("A Special Button") } }
创建按钮的第三种方法更有条理,无论是文件在文件系统中的位置,还是在 QML 中的使用方式。首先,像上面那样复制一个现有文件,但这次要把它放到项目中的一个子文件夹里,命名为(例如)controls
。要使用该控件,首先要将文件夹导入命名空间:
import QtQuick.Controls.Basic import "controls" as MyControls ApplicationWindow { MyControls.Button { text: qsTr("A Special Button") } }
由于您现在拥有MyControls
命名空间,您可以根据Qt Quick Controls 模块中的实际对应控件来命名这些控件。您可以对希望添加的任何控件重复这一过程。
这三种方法还有一个好处,那就是不必从头开始实现模板。
注意: 这里提到的三种方法不适用于定制所附的ToolTip ,因为它是内部创建的共享项目。要对ToolTip
进行一次性定制,请参阅Custom Tool Tips 。要自定义所附的ToolTip
,必须将其作为自己样式的一部分提供。
创建自定义样式
创建自己的样式有几种方法。下面,我们将解释各种方法。
样式的定义
在Qt Quick Controls 中,样式实质上是单个目录中的一组 QML 文件。样式的使用有四个要求:
- 至少要有一个名称与控件(如
Button.qml
)相匹配的 QML 文件。 - 每个 QML 文件的根项必须包含QtQuick.Templates导入的相关类型。例如,Button.qml 文件的根项必须包含 Button 模板。
如果我们像上一节那样,使用QtQuick.Controls导入中的相应类型,就不会成功:我们定义的控件会试图从自身派生。
- qmldir文件必须与 QML 文件同时存在。下面是一个提供按钮样式的简单
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
样式名称必须与样式目录的大小写一致;不支持使用mystyle或MYSTYLE。
默认情况下,样式系统使用 Basic 样式作为未实现控件的备用样式。要自定义或扩展任何其他内置样式,可以使用QQuickStyle 指定不同的后备样式。
这意味着您可以为自定义样式实现任意数量的控件,并将它们放置在几乎任何地方。它还允许用户为应用程序创建自己的样式。
在Qt Quick Designer 中预览自定义样式
使用上述方法,可以在Qt Quick Designer 中预览自定义样式。为此,请确保项目有一个qtquickcontrols2.conf文件,并存在以下条目:
[Controls] Style=MyStyle
更多信息,请参阅扁平样式示例。
特定风格的 C++ 扩展
有时,你可能需要使用 C++ 来扩展你的自定义样式。
- 如果使用该类型的样式是应用程序使用的唯一样式,请通过添加QML_ELEMENT 宏并将该文件作为 QML 模块的一部分,在 QML 引擎中注册该类型:
qmakeqt_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 应用程序。
- 如果使用该类型的样式是应用程序使用的众多样式之一,可考虑将每个样式放入一个单独的模块。模块将按需加载。
自定义样式的注意事项
在实现自己的样式和自定义控件时,需要注意以下几点,以确保您的应用程序具有尽可能高的性能。
避免为样式的项目委托实现指定 id
正如在 "样式的定义"一文中所解释的,当你为一个控件实现自己的样式时,首先要使用该控件的相关模板。例如,样式的Button.qml
结构与此类似:
T.Button { // ... background: Rectangle { // ... } contentItem: Text { // ... } // ... }
当您在应用程序中使用按钮时,将创建background
和contentItem
项,并将其作为根Button
项的父级项:
// Creates the Button root item, the Rectangle background, // and the Text contentItem. Button { text: qsTr("Confirm") }
假设您需要对 Button 进行一次性定制(如定制控件中所述):
import QtQuick import QtQuick.Controls.Basic 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++。例如,"材质"和"通用 "样式都有一个附加主题属性,可控制一个项目及其子项是以浅色还是深色主题呈现。
举例来说,让我们添加一个附加属性来控制立面。我们的样式将用阴影来说明高度;高度越高,阴影越大。
第一步是在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
从$QTDIR/qml/QtQuick/Controls/Basic/
中的 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 Controls 中的 "使用样式"。或者,您也可以创建自己的样式。
自定义应用程序窗口
ApplicationWindow 由一个可视化项组成: 。background
import QtQuick import QtQuick.Controls.Basic ApplicationWindow { visible: true background: Rectangle { gradient: Gradient { GradientStop { position: 0; color: "#ffffff" } GradientStop { position: 1; color: "#c1bbf9" } } } }
自定义繁忙指示器
BusyIndicator 包括两个可视化项目: 和 。background contentItem
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 } ] } } } } }
自定义按钮
按钮由两个可视化项目组成:background 和content item 。
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 由三个可视化项目组成:、 和 。background contentItem indicator
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 包括三个可视化项目:、 和 。background contentItem indicator
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 由 , , , 和 组成。background content item popup indicator delegate
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 height: Math.min(contentItem.implicitHeight, control.Window.height - topMargin - bottomMargin) 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 Model Roles 中所述,ComboBox 支持多种类型的模型。
由于所有模型都提供了一个匿名属性 modelData
,以下表达式在所有情况下都能检索到正确的文本:
text: model[control.textRole]
当您提供一个特定的textRole
和一个提供所选角色的结构化数据的模型时,这个表达式就是一个常规的属性查询。当你提供一个具有单数数据(如字符串列表)的模型和一个空的textRole
时,这个表达式会检索到modelData
。
自定义 DelayButton
DelayButton 由两个可视化项组成: 和 。background content item
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() } } } }
自定义拨号盘
拨号盘由两个可视化项目组成:background 和handle 。
导入 QtQuick导入 QtQuick.Controls.BasicDial{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" 不透明度:control.enabled?1:0.3}handle:Rectangle{id:handleItemx: control.background.x + control.background.width / 2 - width / 2y: 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} ] } }
自定义抽屉
抽屉可以有一个可视化background 项目。
background: Rectangle { Rectangle { x: parent.width - 1 width: 1 height: parent.height color: "#21be2b" } }
定制框架
框架由一个可视化项目组成:background 。
import QtQuick import QtQuick.Controls.Basic Frame { background: Rectangle { color: "transparent" border.color: "#21be2b" radius: 2 } Label { text: qsTr("Content goes here!") } }
自定义组框
GroupBox 由两个可视化项目组成: 和 。background label
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!") } }
自定义 ItemDelegate
ItemDelegate 包括两个可视化项目: 和 。background content item
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 } } }
自定义标签
标签可以有一个可视化background 项目。
import QtQuick import QtQuick.Controls.Basic Label { text: qsTr("Label") color: "#21be2b" }
自定义菜单
- Menu 由一个可视化 项目组成。background
- MenuItem 由四个可视化项目组成:, , , 和 。background content item indicator arrow
- MenuSeparator 由一个可视化 和 组成。background content item
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 } }
自定义菜单栏
MenuBar 可以有一个可视化 项,而 由两个可视化项组成: 和 。background MenuBarItem background content item
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 } } }
自定义页面指示器
PageIndicator 由 、 和 组成。background content item delegate
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 } } } }
自定义窗格
窗格由background 组成。
import QtQuick import QtQuick.Controls.Basic Pane { background: Rectangle { color: "#eeeeee" } Label { text: qsTr("Content goes here!") } }
自定义弹出窗口
弹出窗口由background 和content item 组成。
import QtQuick import QtQuick.Controls.Basic Popup { id: popup background: Rectangle { implicitWidth: 200 implicitHeight: 200 border.color: "#444" } contentItem: Column {} }
自定义进度条
ProgressBar 由两个可视项组成: 和 。background content item
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 } } } } }
上图中,内容项也以动画形式显示indeterminate 进度条状态。
自定义 RadioButton
RadioButton 由三个可视化项目组成:、 和 。background content item indicator
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
RadioDelegate 包括三个可视化项目:、 和 。background contentItem indicator
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 包括三个可视化项目: 和 。background first.handle second.handle
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 的自定义方式与Button 相同。
自定义滚动条
ScrollBar 包括两个可视化项: 和 。background content item
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 包括两个可视化项: 和 。background content item
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 包括一个 项目以及水平和垂直滚动条。background
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" } }
自定义滑块
滑块由两个可视化项目组成:background和handle 。
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 由四个可视项组成:, , , 和 。background contentItem up indicator down indicator
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 可以有一个可视化 项,并允许自定义用于推送、弹出和替换操作的过渡效果。background
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 由六个可视化项组成:, , , , , 和 。background content item indicator 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 可以有一个可视化 项目。导航由 实现。background content item
import QtQuick import QtQuick.Controls.Basic SwipeView { id: control background: Rectangle { color: "#eeeeee" } }
自定义开关
Switch 包含三个可视化项目:background、content item 和indicator 。
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 由三个可视化项目组成:、 和 。background contentItem indicator
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
TabBar 由两个可视化项目组成:和 。background contentItem
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 由 项组成。background
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
TextField 由 项组成。background
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 由一个可视化项目组成: 。background
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 由两个可视化项组成: 和 。background content item
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 由两个可视化项组成: 和 。background content item
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 由两个可视化项组成: 和 。background content item
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" } }
注意: 要自定义attached ToolTip ,必须将其作为自己风格的一部分。要对ToolTip
进行一次性定制,请参阅Custom Tool Tips 。
自定义不倒翁
Tumbler 由三个可视项目组成:background、contentItem 和delegate 。
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" } }
如果要定义自己的 contentItem,请使用ListView 或PathView 作为根项目。对于包装滚动条,请使用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 } }
对于非包裹型滚动条,请使用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 } }
自定义 TableViewDelegate
TableViewDelegate 继承于 ,这意味着它由两个可视项组成: 和 。ItemDelegate background contentItem
如果您的需求超出了默认编辑委托所提供的范围,您可以随时将自己的自定义编辑委托分配给editDelegate 。
delegate: TableViewDelegate { id: tableCell checked: column === 0 ? checkBox.checked : tableView.itemAtIndex(tableView.index(row, 0)).checked selected: checked background: Item { Rectangle { anchors.fill: parent anchors.margins: tableCell.current ? 3 : 1 color: tableCell.selected ? "blue" : "white" } Rectangle { anchors.fill: parent color: "transparent" border.color: "darkblue" border.width: tableCell.current ? 2 : 0 } } contentItem: Item { implicitHeight: 40 visible: !tableCell.editing RowLayout { anchors.fill: parent CheckBox { id: checkBox implicitWidth: height Layout.fillHeight: true checked: false visible: tableCell.column === 0 } Text { Layout.leftMargin: 4 Layout.fillWidth: true Layout.fillHeight: true verticalAlignment: Text.AlignVCenter color: tableCell.selected ? "white" : "black" text: tableCell.model.display } } } TableView.editDelegate: FocusScope { width: parent.width height: parent.height TableView.onCommit: { let qaim = tableCell.tableView.model if (!qaim) return const index = qaim.index(tableCell.row, tableCell.column) // instead of the edit role, any custom role supported by the model can be checked // e.g. if (!tableCell.checked || !tableCell.model.customRole) if (!tableCell.checked || !tableCell.model.edit) return // instead of the edit role, any custom role supported by the model can be set // e.g. tableCell.model.customRole = textField.text tableCell.model.edit = textField.text tableCell.model.display = textField.text } Component.onCompleted: textField.selectAll() TextField { id: textField anchors.fill: parent text: tableCell.model.edit ?? tableCell.model.display ?? "" focus: true } } }
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.