Canonical way to make custom TableView from ListVi

2019-03-30 06:23发布

What is the best way to make table from ListView?

Say, given a 2d array of strings and delegate for all the columns are Labels. How and when to calculate maximum item width for each column while using only QML? Content of each Label is not constant (i.e. implicitWidth is mutable during lifetime).

Practical reason to invent the TableView is the fact, that 1 step to TreeView will remain.

1条回答
贼婆χ
2楼-- · 2019-03-30 07:09

Questions about creating tables in QML seem to get posted fairly frequently, but I am yet to see an answer compiling all the different options. There are lots of ways to achieve what you are asking. I hope to provide in this answer a number of alternatives.

TableView (5.12 and later)

(Updated 14 Jan 2019)

Qt 5.12 includes a new Qt Quick item called TableView, which has been redesigned from the ground up to have good performance for a data model with any number of rows or columns. It resolves the performance problems which were present in the previous TableView from`Quick Controls 1.

I cannot provide a usage example for this approach, because I haven't used it yet (I am still using Qt 5.9 for my projects), but there are examples in the Qt documentation for 5.12 and later.

If you are using Qt 5.12, and you know that you will need both horizontal scrolling and vertical scrolling for your table (there are more rows AND columns than can fit in the view), then this would seem to be the first choice solution.

Below are a summary of alternative approaches for Qt 5.11 and earlier, or if for some reason you do not want to use the Qt 5.12 TableView (perhaps one of these alternative approaches better suits your data model?).

GridLayout

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

Vertical ListView

Creating a table with the Vertical ListView has its advantages and disadvantages. Pros:

  • Scrollable
  • Dynamic creation of delegates which are outside the viewable area, which should mean faster loading
  • Easy to create for fixed width columns, in which the text is elided or wrapped

Cons:

  • For a vertical scrolling ListView (which is usually what people want), dynamic column width is difficult to achieve... i.e. column width is set to completely fit all values in the column

Column widths must be calculated using a loop over all the model data inside that column, which could be slow and is not something you would want to perform often (for example if user can modify cell contents and you want the column to resize).

A reasonable compromise can be achieved by only calculating the column widths once, when the model is assigned to the ListView, and having a mixture of fixed-width and calculated-width columns.

Warning: Below is an example of calculating column widths to fit longest text. If you have a large model, you should consider scrapping the Javascript loop and resort to fixed width columns (or fixed proportions relative to the view size).

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 and earlier)

(from Quick Controls 1)

QC1 has a TableView component. QC2 does not (in Qt 5.9). There is one in development, but with no guaranteed timescale.

TableView has been unpopular due to performance issues, but it did receive improvements between Quick Controls 1.0 to 1.4, and it remains a useable component. QC1 and QC2 can be mixed in the same application.

Pros

  • easy to achieve spreadsheet-style user-resizable columns
  • based on a ListView, so handles large numbers of rows well.
  • only built-in component resembling the QTableView from Widgets

Cons

  • default styling is a sort of desktop-grey. You might spend more time trying to override the styling than if you started from scratch using a ListView.
  • auto resizing columns to fit longest contents not really practical / doesn't really work.

Example:

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
        }
    }
}
查看更多
登录 后发表回答