在 QtWidgets 中,可以使用 qss 对一批需要相同样式的控件进行设置,避免大量的重复代码。
然而在 QtQuick 中,qss 完全不支持了,但是对于上述需求依然是存在的,那么在 QML 中,要如何实现呢?
自定义控件
Qt Quick Controls 由项目层次结构(树)组成。为了提供自定义的外观和感觉,每个项目的默认 QML 实现都可以替换为自定义的。
自定义单个控件
如果打算在多个地方使用相同的样式,可以采取以下方式创建元素,以自定义圆形按钮为例:
1.将基本样式 Button.qml 复制到当前项目,并另存为 MyButton.qml。该文件可以在 Qt 安装的以下路径中找到:
1 | $QTDIR/qml/QtQuick/Controls/Basic/Button.qml |
2.修改 MyButton.qml,添加:
1 | radius: 4 |
3.在应用程序中使用该控件,直接通过其文件名引用它:
1 | import QtQuick.Controls |
自定义一组控件
上述方法对于添加一组自定义控件来说,还是稍显繁琐,需要修改大量的文件名。
对此,我们可以把所有控件放到一个文件中,通过直接引入真个文件夹解决了问题。
例如,将 Button.qml 放入项目中名为 controls 的子文件夹中,按如下方式导入 QML:
1 | import QtQuick.Controls |
注意:此方法不适用于自定义附加的工具提示(ToolTip),因为这是内部创建的共享项。要对工具提示进行一次性自定义,请参阅自定义工具提示(Custom Tool Tips)。要自定义附加的工具提示,必须将其作为自定义样式的一部分提供。
创建自定义样式
风格的定义
在 Qt Quick Controls 中,样式本质上是单个目录中的一组 QML 文件。样式可用需要满足四个要求:
必须至少存在一个名称与控件(例如 Button.qml)匹配的 QML 文件。
每个 QML 文件必须包含来自 QtQuick.Templates 导入的相关类型作为根项。例如,Button.qml 必须包含一个 Button 模板作为其根项。
如果我们像上一节中那样使用 QtQuick.Controls 导入中的相应类型,它将不起作用:我们定义的控件将尝试从自身派生。
qmldir 文件必须与 QML 文件一起存在。以下是提供按钮的样式的简单 qmldir 文件示例:
1
2module MyStyle
Button 2.15 Button.qml如果使用编译时样式选择,qmldir 还应该导入后备样式:
1
2# ...
import QtQuick.Controls.Basic auto这也可以用于运行时样式选择,而不是使用 QQuickStyle::setFallbackStyle() 等。
这种样式的目录结构如下所示:
1
2
3MyStyle
├─── Button.qml
└─── qmldir这些文件必须位于可通过 QML 导入路径找到的目录中。
例如,如果上面提到的 MyStyle 目录的路径是 /home/user/MyApp/MyStyle,则 /home/user/MyApp 必须添加到 QML 导入路径中。
要在 MyApp 中使用 MyStyle,请按名称引用它:
./MyApp -style MyStyle
样式名称必须与样式目录的大小写一致;不支持传递 mystyle 或 MYSTYLE。
默认情况下,样式系统使用基本样式作为未实现的控件的后备样式。要自定义或扩展任何其他内置样式,可以使用 QQuickStyle 指定不同的后备样式。
这意味着可以为自定义样式实现任意数量的控件,并将它们放置在几乎任何地方。它还允许用户为应用程序创建自己的样式。
在 Qt Quick Designer 中预览自定义样式
使用上述方法,可以在 Qt Quick Designer 中预览自定义样式。为此,请确保项目具有 qtquickcontrols2.conf 文件,并且存在以下条目:
1 | [Controls] |
有关更多信息,请查看平面样式示例(Flat Style example)。
特定于样式的 C++ 扩展
有时可能需要使用 C++ 来扩展自定义样式。
如果使用该类型的样式是应用程序使用的唯一样式,请通过添加 QML_ELEMENT 宏并使该文件成为 QML 模块的一部分来向 QML 引擎注册该类型:
CMake:
1 | qt_add_qml_module(ACoolItem |
qmake:
1 | CONFIG += qmltypes |
如果无法从项目的包含路径访问声明类的标头,则可能需要修改包含路径,以便可以编译生成的注册代码。
1 | INCLUDEPATH += MyItems |
有关更多信息,请参阅从 C++ 定义 QML 类型(Defining QML Types from C++)和构建 QML 应用程序(Building a QML application)。
如果使用该类型的样式是应用程序使用的多种样式之一,请考虑将每种样式放入单独的模块中。然后将按需加载模块。
自定义样式的注意事项
在实现自己的样式和自定义控件时,需要记住一些要点,以确保应用程序尽可能高效。
避免将 id 分配给项目委托的样式实现
如样式定义中所述,当为控件实现自己的样式时,将从该控件的相关模板开始。例如,样式的 Button.qml 的结构与此类似:
1 | T.Button { |
当在应用程序中使用 Button 时,将创建背景和 contentItem 项并将其作为根 Button 项的父级:
1 | // Creates the Button root item, the Rectangle background, |
假设需要对按钮进行一次性自定义(如自定义控件中所述):
1 | import QtQuick |
在 QML 中,这通常会导致默认背景实现和一次性的自定义背景项被创建。 Qt Quick Controls 使用了一种避免创建这两项的技术,而只创建自定义背景,从而大大提高了控件的创建性能。
此技术依赖于该项目的样式实现中缺少 id。如果分配了 id,则该技术无法工作,并且将创建两个项目。例如,将 id 分配给背景或 contentItem 可能很诱人,以便文件中的其他对象可以引用这些项目:
1 | T.Button { |
使用此代码,每次创建具有自定义背景的 Button 实例时,都会创建两个背景,从而导致创建性能不佳。
在 Qt 5.15 之前,旧的、未使用的背景将被删除以释放与其关联的资源。但是,由于控件不拥有这些项目,因此不应删除它们。从 Qt 5.15 开始,旧项目不再被删除,因此 backgroundRect 项目的生存时间将比它需要的时间更长——通常直到应用程序退出为止。尽管旧项目将被隐藏,在视觉上从控件中取消父子关系,并从可访问性树中删除,但在此上下文中分配 id 时,请务必记住这些未使用项目的创建时间和内存使用情况。
避免自定义项目的强制分配
上一节中提到的技术仅在第一次以声明方式分配项目时才有效,因此命令式分配将导致孤立项目。如果可能,请始终使用声明性绑定来分配自定义项。
不要在 QML 实现中导入 QtQuick.Controls
当为控件的样式实现编写 QML 时,重要的是不要导入 QtQuick.Controls。这样做将阻止 QML 编译器编译 QML。
实现其他类型使用的类型
假设在应用程序中使用 ScrollView,并决定要自定义其滚动条。仅仅实现一个自定义的 ScrollBar.qml 并让 ScrollView 自动选取自定义的 ScrollBar 是很诱人的。然而,这是行不通的,必须同时实现 ScrollBar.qml 和 ScrollView.qml。
附加属性
样式通常具有适用于所有控件的某些特性或特性。附加属性是在 QML 中扩展项目的好方法,而无需修改属于该项目的任何现有 C++。例如,Material 和 Universal 样式都有一个附加的主题属性,用于控制项目及其子项是否以浅色主题或深色主题渲染。
作为示例,让我们添加一个控制海拔的附加属性。我们的风格将用投影来说明立面;海拔越高,阴影越大。
第一步是在 Qt Creator 中创建一个新的 Qt Quick Controls 应用程序(create a new Qt Quick Controls application)。之后,我们添加一个存储海拔的 C++ 类型(add a C++ type)。由于该类型将用于我们的样式支持的每个控件,并且因为我们可能希望稍后添加其他附加属性,所以我们将其称为 MyStyle。这是 MyStyle.h:
1 |
|
MyStyle.cpp:
1 |
|
MyStyle 类型很特殊,因为它不应被实例化,而应用于其附加属性。因此,我们在 main.cpp 中按以下方式注册它:
1 |
|
然后,我们将 $QTDIR/qml/QtQuick/Controls/Basic/ 中的 Basic 样式中的 Button.qml 复制到项目目录中的新 myproject 文件夹中。将新复制的 Button.qml 添加到 qml.qrc,这是包含我们的 QML 文件的资源文件。
接下来,我们向 Button 的背景委托添加阴影:
1 | // ... |
请注意:
- 当海拔为 0 时,不要费心使用投影
- 根据按钮是否具有焦点来更改阴影的颜色
- 使阴影的大小取决于海拔
为了尝试附加属性,我们在 main.qml 中创建一个带有两个按钮的 Row:
1 | import QtQuick |
一个按钮没有高程,另一个按钮的高程为 10。
完成后,我们就可以运行我们的示例了。为了告诉应用程序使用我们的新样式,我们将 -style MyStyle 作为应用程序参数传递,但是有很多方法可以指定要使用的样式。
最终结果:
请注意,导入 MyStyle 1.0 语句仅是必要的,因为我们正在使用属于 MyStyle 的附加属性。即使我们要删除导入,这两个按钮都将使用我们的自定义样式。
定制参考
ApplicationWindow
ApplicationWindow 由一项视觉项组成:background。
1 | import QtQuick |
同样只有一个 background 的还有 Drawer、Frame、Label、Pane、TextArea、TextField、ToolBar。
BusyIndicator
BusyIndicator 由两个视觉项组成: background and contentItem
1 | import QtQuick |
Button
Button 由两个视觉项组成: background and content item
1 | import QtQuick |
RoundButton、TabButton 可以按照与 Button 相同的方式进行自定义。
CheckBox
CheckBox 由三个视觉项组成: background, contentItem and indicator
1 | import QtQuick |
CheckDelegate
CheckDelegate 由三个视觉项组成: background, contentItem and indicator
1 | import QtQuick |
ComboBox
ComboBox 由以下视觉项组成: background, content item, popup, indicator, and delegate
1 | pragma ComponentBehavior: Bound |
如 ComboBox 模型角色(ComboBox Model Roles)中所述,ComboBox 支持多种类型的模型。
由于所有模型都提供带有 modelData 的匿名属性(all the models provide an anonymous property),因此以下表达式在所有情况下都会检索正确的文本:
1 | text: model[control.textRole] |
当提供特定的 textRole 和具有提供所选角色的结构化数据的模型时,此表达式是常规属性查找。当提供具有单一数据(例如字符串列表)和空 textRole 的模型时,此表达式将检索 modelData。
DelayButton
DelayButton 由两个视觉项组成:background and content item
1 | import QtQuick |
Dial
Dial 由两个视觉部分组成:background and handle
1 | import QtQuick |
GroupBox
GroupBox 由两个视觉项组成:background and label
1 | import QtQuick |
ItemDelegate
ItemDelegate 由两个视觉项组成:background and content item
1 | import QtQuick |
Menu
- Menu consists of a visual background item
- MenuItem consists of four visual items: background, content item, indicator, and arrow.
- MenuSeparator consists of a visual background and content item.
1 | import QtQuick |
MenuBar
MenuBar 可以有 background
MenuBarItem 由两个视觉项组成:background and content item
1 | import QtQuick |
Popup
Popup consists of a background and content item.
1 | import QtQuick |
ProgressBar
ProgressBar 由两个视觉项组成:background and content item
1 | import QtQuick |
上面,内容项也被动画化以表示不确定(indeterminate)的进度条状态。
RadioButton
RadioButton 由三个视觉项组成:background, content item and indicator
1 | import QtQuick |
RadioDelegate
RadioDelegate 由三个视觉项组成:background, contentItem and indicator
1 | import QtQuick |
RangeSlider
RangeSlider 由三个视觉项组成:background, first.handle and second.handle
1 | import QtQuick |
ScrollBar
ScrollBar 由两个视觉项组成:background and content item
1 | import QtQuick |
ScrollIndicator
ScrollIndicator 由两个视觉项组成:background and content item
1 | import QtQuick |
ScrollView
ScrollView 由三个视觉项组成:background , horizontal and vertical scroll bars.
1 | ScrollView { |
Slider
Slider 由两个视觉项组成:background, and handle.
1 | import QtQuick |
SpinBox
SpinBox 由四个视觉项组成:background, contentItem, up indicator, and down indicator
1 | import QtQuick |
SplitView
SplitView consists of a visual handle delegate.
1 | SplitView { |
StackView
StackView 可以有一个 background,它允许自定义用于推送、弹出和替换操作(push, pop, and replace)的转换。
1 | import QtQuick |
SwipeDelegate
SwipeDelegate 由六个视觉项组成:background, content item, indicator, swipe.left, swipe.right, and swipe.behind.
1 | import QtQuick |
SwipeView
SwipeView 可以有一个 background。 导航(navigation)功能是使用 content item 实现的。
1 | import QtQuick |
Switch
Switch 由三个视觉项组成:background, content item and indicator.
1 | import QtQuick |
SwitchDelegate
SwitchDelegate 由三个视觉项组成:background, contentItem and indicator.
1 | import QtQuick |
TabBar
TabBar 由两个视觉项组成:background, and contentItem.
1 | import QtQuick |
ToolButton
ToolButton 由两个视觉项组成:background and content item
1 | import QtQuick |
ToolSeparator
ToolSeparator 由两个视觉项组成:background and content item.
1 | ToolBar { |
ToolTip
ToolTip 由两个视觉项组成:background and content item.
1 | import QtQuick |
注意:要自定义附加的工具提示(attached ToolTip),必须将其作为自定义样式的一部分提供。要对工具提示进行一次性自定义,请参阅自定义工具提示(Custom Tool Tips)。
Tumbler
Tumbler 由三个视觉项组成:background, contentItem, and delegate.
1 | import QtQuick |
如果想定义自己的 contentItem,请使用 ListView 或 PathView 作为 root item。对于 wrapping Tumbler,请使用 PathView:
1 | Tumbler { |
对于 non-wrapping Tumbler,请使用 ListView:
1 | Tumbler { |