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 16/07/2021)
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.
At the time of creating this answer TableView did not exist, but I have provided a usage example for the new TableView in a more recent answer here: https://mcmap.net/q/1324914/-tableview-columns-autofit
It provides good built-in support for sizing the column widths based on the delegate implicitWidth
, but it does so only for the rows in the viewport, which means that scrolling could reveal data which does not fit in the column, unless you force a forceLayout()
.
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.
Qt provided a performance comparison of the new TableView vs the old one here: http://blog.qt.io/blog/2018/12/20/tableview-performance/
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
}
}
}
QAbstractItemModel
-descendent or aListModel
. – Karalynndelegate
unless you created it. Usually you will want to create thedelegate
s lazily, so you won't determine the length. From a design point of view it is better to set the sizes in dependence of the window size, anyway. Otherwise, the next time you use the view, the texts will be longer, and break your precious design. Useelide
for cases where the content creator is unable to restrict himself. If you don't want to be lazy, use aGridLayout
and someRepeater
s to fill it. – KaralynnTableView
or aTreeView
now? – KaralynnListView
delegate
. I need to resizehighlight
for full width ofdelegate
. Otherwise it looks bad. – SexagenaryComponent.onCompleted
-handler and a propertymaxWidth
in theListView
that you update. – KaralynnonWidthChanged
and update themaxWidth
. It won't go down with this though, but as hopefully the view's widht should not change all the time something tiny will change, it should be ok? – KaralynnonModelChanged
, and use a javascript loop and aTextMetrics
component to calculate the width for each text string in the model.onModelChanged
will only get called once. other signal handlers likeonWidthChanged
can cause recursion. – PeacefulGreedView
? – SexagenaryGridLayout
.Layout.row: 123
may help to make them non-1-dimensional. – SexagenaryGridLayout
with aRepeater
, but this won't be a canonical way for aTableView
- neither will @MarkCh's solution with aJS
-loop be, for that would work only for finite models, and there only for reasonably small ones.onModelChanged
is useless, as the model will (usually) not change, if you use a properQAbstractItemModel
descendent, and not just aJSArray
, for which you invoke theonModelChanged
-signal every time some data changes. You might connect to the (proper) modelsonDataChanged
though, but this might be invoked when something else changes, too. – KaralynnItem
s won't broadcast their new width themselves. If you writeonImplicitWidthChanged: maxImplicitWidth = Math.max(maxImplicitWidth, implicitWidth)
, this will automatically called for the right element. He is right that theonWidthChanged
-handler is not the best, as it will be invoked for every instantiated element after the width of one changed. Use theimplicitWidthChanged()
-signal instead. – Karalynn