Canonical way to create custom TableView from ListView in Qt Quick
What's the best way to make a table from ListView
?
Let's say given a 2d array of strings and delegate
for all columns Label
s. How and when to calculate the maximum element width for each column when using QML only? The content of each is Label
not permanent (i.e., it implicitWidth
is changeable over a lifetime).
The practical reason for the invention TableView
is that 1 step TreeView
will remain.
source to share
Questions about creating tables in QML seem to be posted quite often, but I have not yet seen an answer compiling all the different options. There are many ways to accomplish what you ask. I hope to provide a number of alternatives in this answer, however this will take me a while.
To get started, I used a suggestion from the comments with GridLayout
.
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 vertical table ListView
has advantages and disadvantages. Pros:
- scrolling
- Dynamically creating delegates that are outside the visible area, which should mean faster loading
- Easily create fixed-width columns where text is stripped or wrapped
Minuses:
- For vertical scrolling
ListView
(which is usually what people want), dynamic column width is difficult to achieve ... that is, the column width will fully match all values ββin the column
Column widths must be calculated using a loop over all the model data within that column, which can be slow and not something you would like to do often (for example, if the user can change the contents of the cells and you want the column to resize).
A reasonable compromise can only be reached by calculating the column widths once, when the model is assigned ListView
and has a mixture of fixed width and calculated width columns.
Warning. Below is an example of calculating column widths to fit long text. If you have a large model, you should consider going through a Javascript loop and using fixed width columns (or fixed proportions relative to the size of the view).
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
(from Quick Controls 1)
QC1 has a component TableView
. QC2 is not (in Qt 5.9). There is one in development, but no guaranteed time.
TableView
was unpopular due to performance issues, but it got improvements between Quick Controls 1.0 to 1.4 and remained a useful component. QC1 and QC2 can be mixed in one application.
Arguments
- easy to get columns with user editable table style tables.
- based
ListView
, therefore handles large numbers of lines well. - only inline component like
QTableView
from widgets
against
- the default style is a kind of desktop gray. You could spend more time trying to override the style than if you started from scratch with
ListView
. - Automatically resizing columns to fit the longest content isn't very practical / doesn't 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
}
}
}
source to share