在 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,因此可以很好地处理大量行。
  • 仅类似于 WidgetsQTableView 的内置组件

缺点

  • 默认样式是一种桌面灰色。与使用 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
        }
    }
}