在 Qt Quick 中从 ListView 制作自定义 TableView 的规范方法
Canonical way to make custom TableView from ListView in Qt Quick
从 ListView
制作 table 的最佳方法是什么?
比如说,给定一个二维字符串数组,所有列的 delegate
都是 Label
。仅使用 QML 时如何以及何时计算每列的最大项目宽度?每个 Label
的内容不是恒定的(即 implicitWidth
在生命周期内是 mutable)。
发明 TableView
的实际原因是,到 TreeView
的 1 步将保持不变。
关于在 QML 中创建 tables 的问题似乎发布得相当频繁,但我还没有看到编译所有不同选项的答案。有很多方法可以实现您的要求。我希望在这个答案中提供一些替代方案。
TableView(5.12 及更高版本)
(更新于 2021 年 7 月 16 日)
Qt 5.12 包括一个名为 TableView
的新 Qt Quick 项目,它已经过彻底重新设计,可以为具有任意数量的行或列的数据模型提供良好的性能。它解决了之前 TableView
`Quick Controls 1.
中存在的性能问题
创建此答案时 TableView 不存在,但我在最近的答案中提供了新 TableView 的用法示例:
它为基于委托 implicitWidth
调整列宽大小提供了良好的内置支持,但它仅对视口中的行这样做,这意味着滚动可能会显示不适合的数据在列中,除非您强制使用 forceLayout()
.
如果您使用的是 Qt 5.12,并且您知道您的 table 需要水平滚动和垂直滚动(视图中容纳的行和列多),那么这将似乎是首选解决方案。
Qt 在此处提供了新 TableView 与旧 TableView 的性能比较:http://blog.qt.io/blog/2018/12/20/tableview-performance/
下面总结了适用于 Qt 5.11 及更早版本的替代方法,或者如果出于某种原因您不想使用 Qt 5.12 TableView
(也许这些替代方法中的一种更适合您的数据模型? ).
网格布局
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
ListModel {
id: listModel
ListElement { name: 'item1'; code: "alpha"; language: "english" }
ListElement { name: 'item2'; code: "beta"; language: "french" }
ListElement { name: 'item3'; code: "long-code"; language: "long-language" }
}
GridLayout {
flow: GridLayout.TopToBottom
rows: listModel.count
columnSpacing: 0
rowSpacing: 0
Repeater {
model: listModel
delegate: Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: implicitWidth
background: Rectangle { border.color: "red" }
text: name
}
}
Repeater {
model: listModel
delegate: Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: implicitWidth
background: Rectangle { border.color: "green" }
text: code
}
}
Repeater {
model: listModel
delegate: Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: implicitWidth
background: Rectangle { border.color: "blue" }
text: language
}
}
}
}
垂直列表视图
使用垂直 ListView
创建 table 有其优点和缺点。
优点:
- 可滚动
- 在可视区域之外动态创建代理,这应该意味着更快的加载
- 易于创建固定宽度的列,其中文本被省略或换行
缺点:
- 对于垂直滚动
ListView
(这通常是人们想要的),动态列宽很难实现...即列宽设置为完全适合列中的所有值
必须使用对该列内所有模型数据的循环来计算列宽,这可能很慢并且不是您希望经常执行的操作(例如,如果用户可以修改单元格内容而您想要列调整大小)。
当模型分配给 ListView
并混合使用固定宽度和计算宽度的列时,只需计算一次列宽即可实现合理的折衷。
警告:下面是计算列宽以适合最长文本的示例。如果你有一个大模型,你应该考虑取消 Javascript 循环并求助于固定宽度的列(或相对于视图大小的固定比例)。
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
ListModel {
id: listModel
ListElement { name: 'item1'; code: "alpha"; language: "english" }
ListElement { name: 'item2'; code: "beta"; language: "french" }
ListElement { name: 'item3'; code: "long-code"; language: "long-language" }
}
ListView {
property var columnWidths: ({"name": 100, "code": 50}) // fixed sizes or minimum sizes
property var calculatedColumns: ["code", "language"] // list auto sized columns in here
orientation: Qt.Vertical
anchors.fill: parent
model: listModel
TextMetrics {
id: textMetrics
}
onModelChanged: {
for (var i = 0; i < calculatedColumns.length; i++) {
var role = calculatedColumns[i]
if (!columnWidths[role]) columnWidths[role] = 0
var modelWidth = columnWidths[role]
for(var j = 0; j < model.count; j++){
textMetrics.text = model.get(j)[role]
modelWidth = Math.max(textMetrics.width, modelWidth)
}
columnWidths[role] = modelWidth
}
}
delegate: RowLayout {
property var columnWidths: ListView.view.columnWidths
spacing: 0
Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: columnWidths.name
background: Rectangle { border.color: "red" }
text: name
}
Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: columnWidths.code
background: Rectangle { border.color: "green" }
text: code
}
Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: columnWidths.language
background: Rectangle { border.color: "blue" }
text: language
}
}
}
}
TableView(5.11 及更早版本)
(来自快速控制 1)
QC1 有一个 TableView
组件。 QC2 没有(在 Qt 5.9 中)。有一个正在开发中,但没有保证的时间表。
TableView
由于性能问题一直不受欢迎,但它确实在 Quick Controls 1.0 到 1.4 之间得到了改进,并且它仍然是一个可用的组件。 QC1 和 QC2 可以在同一个应用程序中混合使用。
优点
- 轻松实现电子表格样式的用户可调整大小的列
- 基于
ListView
,因此可以很好地处理大量行。
- 仅类似于 Widgets
QTableView
的内置组件
缺点
- 默认样式是一种桌面灰色。与使用
ListView
. 从头开始相比,您可能会花费更多时间尝试覆盖样式
- 自动调整列的大小以适应最长的内容不太实用/实际上不起作用。
示例:
import QtQuick 2.7
import QtQuick.Controls 1.4 as QC1
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 400
height: 200
ListModel {
id: listModel
ListElement { name: 'item1'; code: "alpha"; language: "english" }
ListElement { name: 'item2'; code: "beta"; language: "french" }
ListElement { name: 'item3'; code: "long-code"; language: "long-language" }
}
QC1.TableView {
id: tableView
width: parent.width
model: listModel
QC1.TableViewColumn {
id: nameColumn
role: "name"
title: "name"
width: 100
}
QC1.TableViewColumn {
id: codeColumn
role: "code"
title: "code"
width: 100
}
QC1.TableViewColumn {
id: languageColumn
role: "language"
title: "language"
width: tableView.viewport.width - nameColumn.width - codeColumn.width
}
}
}
从 ListView
制作 table 的最佳方法是什么?
比如说,给定一个二维字符串数组,所有列的 delegate
都是 Label
。仅使用 QML 时如何以及何时计算每列的最大项目宽度?每个 Label
的内容不是恒定的(即 implicitWidth
在生命周期内是 mutable)。
发明 TableView
的实际原因是,到 TreeView
的 1 步将保持不变。
关于在 QML 中创建 tables 的问题似乎发布得相当频繁,但我还没有看到编译所有不同选项的答案。有很多方法可以实现您的要求。我希望在这个答案中提供一些替代方案。
TableView(5.12 及更高版本)
(更新于 2021 年 7 月 16 日)
Qt 5.12 包括一个名为 TableView
的新 Qt Quick 项目,它已经过彻底重新设计,可以为具有任意数量的行或列的数据模型提供良好的性能。它解决了之前 TableView
`Quick Controls 1.
创建此答案时 TableView 不存在,但我在最近的答案中提供了新 TableView 的用法示例:
它为基于委托 implicitWidth
调整列宽大小提供了良好的内置支持,但它仅对视口中的行这样做,这意味着滚动可能会显示不适合的数据在列中,除非您强制使用 forceLayout()
.
如果您使用的是 Qt 5.12,并且您知道您的 table 需要水平滚动和垂直滚动(视图中容纳的行和列多),那么这将似乎是首选解决方案。
Qt 在此处提供了新 TableView 与旧 TableView 的性能比较:http://blog.qt.io/blog/2018/12/20/tableview-performance/
下面总结了适用于 Qt 5.11 及更早版本的替代方法,或者如果出于某种原因您不想使用 Qt 5.12 TableView
(也许这些替代方法中的一种更适合您的数据模型? ).
网格布局
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
ListModel {
id: listModel
ListElement { name: 'item1'; code: "alpha"; language: "english" }
ListElement { name: 'item2'; code: "beta"; language: "french" }
ListElement { name: 'item3'; code: "long-code"; language: "long-language" }
}
GridLayout {
flow: GridLayout.TopToBottom
rows: listModel.count
columnSpacing: 0
rowSpacing: 0
Repeater {
model: listModel
delegate: Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: implicitWidth
background: Rectangle { border.color: "red" }
text: name
}
}
Repeater {
model: listModel
delegate: Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: implicitWidth
background: Rectangle { border.color: "green" }
text: code
}
}
Repeater {
model: listModel
delegate: Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: implicitWidth
background: Rectangle { border.color: "blue" }
text: language
}
}
}
}
垂直列表视图
使用垂直 ListView
创建 table 有其优点和缺点。
优点:
- 可滚动
- 在可视区域之外动态创建代理,这应该意味着更快的加载
- 易于创建固定宽度的列,其中文本被省略或换行
缺点:
- 对于垂直滚动
ListView
(这通常是人们想要的),动态列宽很难实现...即列宽设置为完全适合列中的所有值
必须使用对该列内所有模型数据的循环来计算列宽,这可能很慢并且不是您希望经常执行的操作(例如,如果用户可以修改单元格内容而您想要列调整大小)。
当模型分配给 ListView
并混合使用固定宽度和计算宽度的列时,只需计算一次列宽即可实现合理的折衷。
警告:下面是计算列宽以适合最长文本的示例。如果你有一个大模型,你应该考虑取消 Javascript 循环并求助于固定宽度的列(或相对于视图大小的固定比例)。
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
ListModel {
id: listModel
ListElement { name: 'item1'; code: "alpha"; language: "english" }
ListElement { name: 'item2'; code: "beta"; language: "french" }
ListElement { name: 'item3'; code: "long-code"; language: "long-language" }
}
ListView {
property var columnWidths: ({"name": 100, "code": 50}) // fixed sizes or minimum sizes
property var calculatedColumns: ["code", "language"] // list auto sized columns in here
orientation: Qt.Vertical
anchors.fill: parent
model: listModel
TextMetrics {
id: textMetrics
}
onModelChanged: {
for (var i = 0; i < calculatedColumns.length; i++) {
var role = calculatedColumns[i]
if (!columnWidths[role]) columnWidths[role] = 0
var modelWidth = columnWidths[role]
for(var j = 0; j < model.count; j++){
textMetrics.text = model.get(j)[role]
modelWidth = Math.max(textMetrics.width, modelWidth)
}
columnWidths[role] = modelWidth
}
}
delegate: RowLayout {
property var columnWidths: ListView.view.columnWidths
spacing: 0
Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: columnWidths.name
background: Rectangle { border.color: "red" }
text: name
}
Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: columnWidths.code
background: Rectangle { border.color: "green" }
text: code
}
Label {
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.preferredWidth: columnWidths.language
background: Rectangle { border.color: "blue" }
text: language
}
}
}
}
TableView(5.11 及更早版本)
(来自快速控制 1)
QC1 有一个 TableView
组件。 QC2 没有(在 Qt 5.9 中)。有一个正在开发中,但没有保证的时间表。
TableView
由于性能问题一直不受欢迎,但它确实在 Quick Controls 1.0 到 1.4 之间得到了改进,并且它仍然是一个可用的组件。 QC1 和 QC2 可以在同一个应用程序中混合使用。
优点
- 轻松实现电子表格样式的用户可调整大小的列
- 基于
ListView
,因此可以很好地处理大量行。 - 仅类似于 Widgets
QTableView
的内置组件
缺点
- 默认样式是一种桌面灰色。与使用
ListView
. 从头开始相比,您可能会花费更多时间尝试覆盖样式
- 自动调整列的大小以适应最长的内容不太实用/实际上不起作用。
示例:
import QtQuick 2.7
import QtQuick.Controls 1.4 as QC1
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 400
height: 200
ListModel {
id: listModel
ListElement { name: 'item1'; code: "alpha"; language: "english" }
ListElement { name: 'item2'; code: "beta"; language: "french" }
ListElement { name: 'item3'; code: "long-code"; language: "long-language" }
}
QC1.TableView {
id: tableView
width: parent.width
model: listModel
QC1.TableViewColumn {
id: nameColumn
role: "name"
title: "name"
width: 100
}
QC1.TableViewColumn {
id: codeColumn
role: "code"
title: "code"
width: 100
}
QC1.TableViewColumn {
id: languageColumn
role: "language"
title: "language"
width: tableView.viewport.width - nameColumn.width - codeColumn.width
}
}
}