自定义 Qt Quick 控件#

一套用于在 Qt Quick 中创建用户界面的 UI 控件

Qt Quick 控件由一系列项目(树状结构)组成。为了提供自定义的外观和感觉,每个项目默认的 QML 实现可以被自定义实现替换。

自定义控件#

有时您可能需要为您 UI 的某个特定部分创建一个“一次性”的外观,并在其他所有地方使用完整的样式。也许您对您所使用的样式感到满意,但有一个按钮有着特殊的意义。

创建此按钮的第一个方法是直接在需要的位置定义它。例如,如果您不满意基本样式的按钮有方形角落,要使其圆润,可以覆盖 background 项目并将矩形属性设置为半径

注意

由于构成任意风格的控制的不同项目都是设计在一起工作的,可能会需要覆盖其他项目以得到所需的外观。此外,并非所有样式都可以自定义。有关更多信息,请参阅 自定义参考 中的注释。

创建按钮的第二个方法是如果您打算在多个位置使用您圆润的按钮时很好。它涉及到将代码移至项目中的自己的 QML 文件。

为此方法,我们将从基本样式的 Button.qml 中复制背景代码。此文件可以在以下路径中找到您的 Qt 安装中

$QTDIR/qml/QtQuick/Controls/Basic/Button.qml

完成之后,我们只需添加以下行

radius: 4

为了避免与模块本身中的控件混淆,我们将该文件命名为 MyButton.qml。要使用控件,请按其文件名引用

创建按钮的第三种方法从文件系统的结构和在 QML 中的用途方面都更加结构化。首先,按照上面的方法复制现有文件,但这次将其放置在项目中的一个名为(例如)controls 的子文件夹中。要使用控件,首先将文件夹导入到名称空间中

现在您有了 MyControls 命名空间,您可以按照 Qt Quick 控件模块中实际的对应项来命名控件。您可以为要添加的任何控件重复此过程。

这些三种方法的额外好处是您不需要从头开始实现模板。

注意

这里提到的三种方法不适用于自定义附加的 ToolTip,因为它是内部创建的共享项。要执行 ToolTip 的“一次性”自定义,请参阅 自定义工具提示。要自定义附加的 ToolTip,它必须作为 您自己的样式的一部分 提供。

创建自定义样式#

创建自己的样式有几种方法。下面,我们将解释各种方法。

样式的定义#

在Qt Quick Controls中,样式本质上是在单个目录内的一系列QML文件。样式要能使用则需要满足四个条件

  • 必须至少有一个QML文件,其名称与控件匹配(例如,Button.qml)。

  • 每个QML文件必须包含从QtQuick.Templates导入的相关类型作为根项。例如,Button.qml必须包含一个按钮模板作为它的根项。

    如果我们像上一节那样使用来自QtQuick.Controls的相应类型,它将不起作用:我们定义的控件将试图从自身继承。

  • 一个qmldir文件必须与QML文件一起存在。以下是提供按钮的样式的简单qmldir文件的示例

    module MyStyle
    Button 2.15 Button.qml
    

    如果您正在使用编译时样式选择,则qmldir还应该导入回退样式

    # ...
    import QtQuick.Controls.Basic auto
    

    这也可以用于运行时样式选择,而不使用,例如,setFallbackStyle() .

    这种样式的目录结构如下所示

    MyStyle
    ├─── Button.qml
    └─── qmldir
    
  • 文件必须在可通过QML导入路径找到的目录中。

    例如,如果上面提到的MyStyle目录的路径是/home/user/MyApp/MyStyle,那么必须将/home/user/MyApp添加到QML导入路径。

    要在MyApp中使用MyStyle,请按名称引用它

    • ./MyApp -style MyStyle

    样式名称必须与样式目录的大小写匹配;不支持传递mystyleMYSTYLE

默认情况下,样式系统使用基本样式作为未实现控件的回退。要自定义或扩展任何其他内置样式,可以使用QQuickStyle指定不同的回退样式。

这意味着您可以为您自定义的样式实现尽可能多的控件,并将它们放置在几乎任何位置。这也允许用户为您应用程序创建自己的样式。

在Qt Quick Designer中预览自定义样式#

使用上述方法,可以在Qt Quick Designer中预览自定义样式。为此,确保项目有一个qtquickcontrols2.conf文件,并且存在以下条目

[Controls]
Style=MyStyle

有关更多信息,请参阅Flat Style示例 .

样式特定的C++扩展#

有时您可能需要使用C++来扩展您的自定义样式。

使用CMake#

qt_add_qml_module(ACoolItem
    URI MyItems
    VERSION 1.0
    SOURCES
        acoolcppitem.cpp acoolcppitem.h
)

使用QMake#

CONFIG += qmltypes
QML_IMPORT_NAME = MyItems
QML_IMPORT_MAJOR_VERSION = 1

如果该类声明的头文件无法从您的项目包含路径中访问,您可能需要修改包含路径,以便生成的注册代码可以编译。

INCLUDEPATH += MyItems

有关更多信息,请参阅从C++定义QML类型和构建QML应用。

  • 如果使用该类型的是应用程序中唯一使用的样式,请通过添加QML_ELEMENT宏并将文件作为QML模块的一部分来使用QML引擎注册该类型

  • 如果使用该类型的样式是应用程序中使用的许多样式之一,考虑将每个样式放入单独的模块。然后将在需要时加载这些模块。

自定义样式的注意事项#

在实现自己的样式和自定义控件时,有一些要点需要注意,以确保您的应用程序尽可能性能出色。

避免为样式实现的项委托分配id#

定义样式中所述,当您为控件实现自己的样式时,您从该控件的相应模板开始。例如,样式中的Button.qml将与以下结构类似

当您在应用程序中使用按钮时,将创建和将backgroundcontentItem项作为根Button项的子项。

假设您需要进行一次性的按钮自定义(如自定义控件中所述)

在QML中,这通常会导致默认的background实现和一次性自定义的background项被创建。Qt Quick Controls使用一种技术来避免创建这两个项,而只创建自定义的background,这大大提高了控件创建性能。

这项技术依赖于该项在样式实现中不存在id。如果分配了id,则该技术无法工作,并且将创建这两个项。例如,将id分配给backgroundcontentItem可能具有诱惑力,以便文件中的其他对象可以引用这些项

使用此代码时,每次创建具有自定义背景的按钮实例时,都会创建两个背景,从而导致次优的创建性能。

在Qt 5.15之前,旧的无用背景会被删除以释放与之相关的资源。然而,由于控件不拥有项,因此不应删除它们。从Qt 5.15开始,旧项不再被删除,因此backgroundRect项将比需要的时间长地存在——通常是应用程序退出的时间。虽然旧项将被隐藏,从视觉上从控件中解除父项,并且从可访问性树中删除,但在这种环境中分配id时,应注意这些未使用项的创建时间和内存使用。

避免对自定义项进行命令式赋值#

上述章节中提到的方法仅在第一次声明性分配项时工作,因此命令式赋值会导致孤立的项目。始终尽可能使用声明性绑定在分配自定义项时进行。

不要在QML实现中导入QtQuick.Controls#

在编写控制式样实现的QML时,需要注意不要导入QtQuick.Controls。这样做将防止QML被QML编译器编译。

实现其他类型使用的类型#

假设您在应用程序中使用滚动视图,并决定要自定义它们的滚动条。直接使用自定义的SnackBar .qml,并让公司的它自动选择一个自定义的 SnackBar。然而,这不会起作用。您必须同时实现 SnackBar .qml 和 公司的 .qml。

附加属性#

通常,一个样式会有一些适用于所有控制的属性或属性。附加属性是扩展QML中项的好方法,无需修改该物品现有的任何C ++。例如,MaterialUniversal 样式都有一个附加主题属性,用于控制项目及其子项目是否以亮色或暗色主题渲染。

作为一个例子,让我们添加一个控制擡高的附加属性。我们的样式将通过投射阴影来显示擡高;擡高越高,阴影越大。

第一步是在Qt Creator中创建一个新的Qt Quick Controls应用程序。创建一个新的Qt Quick Controls应用程序之后,我们添加一个C++类型来存储擡高。由于该类型将用于我们风格支持的所有控件,并且我们可能希望在以后添加其他附加属性,所以我们将其称为MyStyle。以下是

#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();
}

然后,我们将从基本样式中的 $QTDIR/qml/QtQuick/Controls/Basic/Button.qml 复制到一个新创建的myproject文件夹中。将新复制的 Button.qml 添加到 qml.qrc 中,这是我们包含QML文档的资源文件。

接下来,我们给按钮的背景委托添加一个阴影

// ...
import QtGraphicalEffects
import MyStyle
// ...

background: Rectangle {
    // ...

    layer.enabled: control.enabled && control.MyStyle.elevation > 0
    layer.effect: DropShadow {
        verticalOffset: 1
        color: control.visualFocus ? "#330066ff" : "#aaaaaa"
        samples: control.MyStyle.elevation
        spread: 0.5
    }
}

注意,我们:

  • 当擡高为 0 时,不用关心阴影

  • 根据按钮是否有焦点来更改阴影的颜色

  • 让阴影的大小取决于擡高

要尝试附加属性,我们在main.qml中创建一个包含两个按钮的行

其中一个按钮没有擡高,另一个的擡高为 10

在配置就绪后,我们可以运行我们的示例。要告诉应用程序使用我们的新样式,我们可将 -style MyStyle 作为应用程序参数传递,但指定使用的样式的 方法有很多

最终效果

../_images/qtquickcontrols-customize-buttons.png

注意,import MyStyle 1.0 语句仅因为使用了 MyStyle 所属的属性。即使我们去掉了导入,两个按钮也会使用我们自定义的样式。

自定义参考#

以下示例代码演示了如何使用与 自定义控件 部分相同的方案,来对 Basic 样式的控件进行自定义。这段代码可以作为实现自定义外观和感觉的起点。

注意

不适用于自定义的样式有 macOSWindows 样式。相反,建议始终在一个所有平台上都有的样式之上自定义控件,例如 Basic 样式Fusion 样式Imagine 样式Material 样式Universal 样式。通过这样做,你可以确保它始终在相同的样子,无论应用程序运行时使用哪个样式。要了解如何使用不同的样式,请参阅 在 Qt Quick 控件中使用样式。或者,您也可以 创建自己的样式

自定义 ApplicationWindow#

ApplicationWindow 包含一个视觉项目:背景

import QtQuick
import QtQuick.Controls

ApplicationWindow {
    visible: true

    background: Rectangle {
        gradient: Gradient {
            GradientStop { position: 0; color: "#ffffff" }
            GradientStop { position: 1; color: "#c1bbf9" }
        }
    }
}

自定义 BusyIndicator#

BusyIndicator 包含两个视觉项目:背景内容项

../_images/qtquickcontrols-busyindicator-custom.png
import QtQuick
import QtQuick.Controls

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#

Button 包含两个视觉项目:背景内容项

../_images/qtquickcontrols-button-custom.png
import QtQuick
import QtQuick.Controls

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 包含三个视觉项目:背景内容项指示器

../_images/qtquickcontrols-checkbox-custom.png
import QtQuick
import QtQuick.Controls

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 由三个视觉项组成:背景内容项指示器

../_images/qtquickcontrols-checkdelegate-custom.png
import QtQuick
import QtQuick.Controls

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 由以下部分组成:背景内容项弹出菜单指示器,以及代理

../_images/qtquickcontrols-combobox-custom.png
pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Controls

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 由两个视觉项组成:背景内容项

../_images/qtquickcontrols-delaybutton-custom.png
import QtQuick
import QtQuick.Controls

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#

Spin dial 由两个视觉项组成:背景

../_images/qtquickcontrols-dial-custom.png
import QtQuick
import QtQuick.Controls

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"
    }
}

自定义 Frame#

Frame 由一个视觉项组成:背景

../_images/qtquickcontrols-frame-custom.png
import QtQuick
import QtQuick.Controls

Frame {
    background: Rectangle {
        color: "transparent"
        border.color: "#21be2b"
        radius: 2
    }

    Label {
        text: qsTr("Content goes here!")
    }
}

自定义 GroupBox#

GroupBox 由两个视觉项组成:背景标签

../_images/qtquickcontrols-groupbox-custom.png
import QtQuick
import QtQuick.Controls

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 由两个视觉项组成:背景内容项

../_images/qtquickcontrols-itemdelegate-custom.png
import QtQuick
import QtQuick.Controls

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
        }
    }
}

自定义 Label#

Label 可以有一个视觉上的 背景 项目。

../_images/qtquickcontrols-label-custom.png
import QtQuick
import QtQuick.Controls

Label {
    text: qsTr("Label")
    color: "#21be2b"
}

自定义 Menu#

../_images/qtquickcontrols-menu-custom.png

自定义菜单栏#

菜单栏(MenuBar)可以有一个可视的 背景 项目,并且 MenuBarItem 由两个可视项目组成:背景内容项

../_images/qtquickcontrols-menubar-custom.png

自定义分页指示器#

分页指示器(PageIndicator)由一个 背景内容项代理 组成。

../_images/qtquickcontrols-pageindicator-custom.png
import QtQuick
import QtQuick.Controls

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
            }
        }
    }
}

自定义面板#

面板由一个 背景 组成。

../_images/qtquickcontrols-pane-custom.png
import QtQuick
import QtQuick.Controls

Pane {
    background: Rectangle {
        color: "#eeeeee"
    }

    Label {
        text: qsTr("Content goes here!")
    }
}

自定义弹出窗口#

弹出窗口由一个 背景内容项 组成。

../_images/qtquickcontrols-popup-custom.png
.. _overviews_customizing-progressbar:

自定义进度条#

进度条(ProgressBar)由两个可视项目组成:背景内容项

../_images/qtquickcontrols-progressbar-custom.png
import QtQuick
import QtQuick.Controls

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
                }
            }
        }
    }
}

如上,内容项也是动画的,以表示一个 不确定的 进度条状态。

自定义单选按钮#

单选按钮(RadioButton)由三个可视项目组成:背景内容项指示器

../_images/qtquickcontrols-radiobutton-custom.png
import QtQuick
import QtQuick.Controls

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)由三个可视项目组成:背景内容项指示器

../_images/qtquickcontrols-radiodelegate-custom.png
import QtQuick
import QtQuick.Controls

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 由三个视觉元素组成:背景第一把手柄 以及 第二把手柄

../_images/qtquickcontrols-rangeslider-custom.png
import QtQuick
import QtQuick.Controls

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

ScrollBar 由两个视觉元素组成:背景内容项

../_images/qtquickcontrols-scrollbar-custom.png
import QtQuick
import QtQuick.Controls

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 由两个视觉元素组成:背景内容项

../_images/qtquickcontrols-scrollindicator-custom.png
import QtQuick
import QtQuick.Controls

ScrollIndicator {
    id: control
    size: 0.3
    position: 0.2
    active: true
    orientation: Qt.Vertical

    contentItem: Rectangle {
        implicitWidth: 2
        implicitHeight: 100
        color: "#c2f4c6"
    }
}

自定义ScrollView

ScrollView 由一个 背景 元素以及水平和垂直滚动条组成。

../_images/qtquickcontrols-scrollview-custom.png
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

Slider 由两个视觉元素组成:背景手柄

../_images/qtquickcontrols-slider-custom.png
import QtQuick
import QtQuick.Controls

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 由四个视觉元素组成:背景内容项向上指示器向下指示器

../_images/qtquickcontrols-spinbox-custom.png
import QtQuick
import QtQuick.Controls

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 由一个视觉 手柄委托 组成。

../_images/qtquickcontrols-splitview-custom.png
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 可以有一个视觉 背景 元素,并且允许自定义用于推送(push)、弹出(pop)和替换(replace)操作的应用过渡。

import QtQuick
import QtQuick.Controls

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.leftswipe.right 以及 swipe.behind

../_images/qtquickcontrols-swipedelegate-custom.png
import QtQuick
import QtQuick.Controls

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

SwipeView {
    id: control

    background: Rectangle {
        color: "#eeeeee"
    }
}

自定义切换#

切换由三个可视化元素组成:背景内容元素指示器

../_images/qtquickcontrols-switch-custom.png
import QtQuick
import QtQuick.Controls

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 由三个可视化元素组成:背景内容元素指示器

../_images/qtquickcontrols-switchdelegate-custom.png
import QtQuick
import QtQuick.Controls

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 由两个可视化元素组成:背景内容元素

../_images/qtquickcontrols-tabbar-custom.png
import QtQuick
import QtQuick.Controls

TabBar {
    id: control

    background: Rectangle {
        color: "#eeeeee"
    }

    TabButton {
        text: qsTr("Home")
    }
    TabButton {
        text: qsTr("Discover")
    }
    TabButton {
        text: qsTr("Activity")
    }
}

自定义 TabButton#

TabButton 可以像 Button 一样自定义。

自定义 TextArea#

TextArea 由一个 背景元素组成。

../_images/qtquickcontrols-textarea-custom.png
import QtQuick
import QtQuick.Controls

TextArea {
    id: control
    placeholderText: qsTr("Enter description")

    background: Rectangle {
        implicitWidth: 200
        implicitHeight: 40
        border.color: control.enabled ? "#21be2b" : "transparent"
    }
}

自定义 TextField#

TextField 由一个 背景元素组成。

../_images/qtquickcontrols-textfield-custom.png
import QtQuick
import QtQuick.Controls

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 由一个可视化元素组成:背景

../_images/qtquickcontrols-toolbar-custom.png
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 由两个可视化元素组成:背景内容元素

../_images/qtquickcontrols-toolbutton-custom.png
import QtQuick
import QtQuick.Controls

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 由两个可视化元素组成:背景内容元素

../_images/qtquickcontrols-toolseparator-custom.png
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#

ToolTip 由两个可视化元素组成:背景内容元素

注意

要自定义附加的Tooltip,必须将其作为您自己的样式的一部分提供。要一次性自定义一个ToolTip,请参阅自定义Tooltip

自定义滚轮#

滚轮包含三个视觉元素:背景内容项,和代理

../_images/qtquickcontrols-tumbler-custom.png
import QtQuick
import QtQuick.Controls

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作为根项。对于包装型滚轮,请使用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
    }
}