TableView replacement for TableViewColumn in Qt Quick 2 - qt

In Qt Quick Controls 1 we can style different columns from a model with TableViewColumn:
TableView {
anchors.fill: parent
TableViewColumn {title: "1"; role: "one"; width: 70 }
TableViewColumn {title: "2"; role: "two"; width: 70 }
TableViewColumn {title: "3"; role: "three"; width: 70 }
model: theModel
}
How can we achieve a similar result in Qt Quick 2 if there is no more TableViewColumn for the TableView?

Since Qt 5.12 you can use TableView QML type.
But to have everything you want you need to include Qt.labs.qmlmodels. It all is available in Qt 5.15 (use online installer).
Actual implementation will hardly depend on your requirements, but here is an example of how it could be done.
Let's say you are going to use TableView to show some data you have in JSON format. In this case TableModel will be a perfect fit, since it is designed to work with JavaScript/JSON data, where each row is a simple key-pair object without requiring the creation of a custom QAbstractTableModel subclass in C++.
You need to declare what columns do you need to have in your model by using TableModelColumn, e.g.: TableModelColumn { display: "checked" }.
For loading real data into model use its rows property; data should be in the form of an array of rows, e.g.:
rows: [
// Each property is one cell/column.
{
checked: false,
amount: 1,
type: "Apple",
price: 1.50
},
{
checked: true,
amount: 4,
type: "Orange",
price: 2.50
},
...
]
Most interesting part here is appliance of delegate -- here it be DelegateChooser, since it allows a view to use different delegates for different types of items in the model. So here you can do almost everything to tweak your cells. E.g.: you can use ProgressBar component as your delegate for a cell:
DelegateChoice {
column: 1
delegate: ProgressBar {
enabled: false
width: 100
from: 0.0
to: 10.0
value: model.display
}
}
As result you could easily get this application entirely in QML (whiteout need to use C++ and/or old QtQuick.Controls):
Please refer to this repository to get full application.

Related

QML: Is there an opportunity to create ListModel using <for loop>, and then using loop index on calculating ListModel item coordinates?

I need to create many similar elements, that differ only by coordinates.
I learned how to create this one by one:
Repeater {
model: ListModel {
ListElement { p:100; q:100; w:50; h:50; c:"red"; o:0 }
ListElement { p:200; q:100; w:50; h:50; c:"red"; o:0 }
ListElement { p:300; q:100; w:50; h:50; c:"red"; o:0 }
ListElement { p:400; q:100; w:50; h:50; c:"red"; o:0 }
ListElement { p:500; q:100; w:50; h:50; c:"red"; o:0 }
}
delegate: Rectangle {
x: p
y: q
width: w
height: h
color: c
rotation: o
}
}
I have now come to the conclusion that it would be convenient to do this with a simple for loop, using the loop index when calculating the coordinates:
Repeater {
model: ListModel {
for ( var i = 1; i <= 5; i++ )
{
ListElement { p:100*i; q:100; w:50; h:50; c:"red"; o:0 }
}
}
delegate: Rectangle {
x: p
y: q
width: w
height: h
color: c
rotation: o
}
}
Unfortunately, there is a problem with loop scope, and of course I don't even expect it will work in that shape...
I am trying to show only my idea in the second code.
Is there an opportunity to create ListElements like this or am I confusing two different ways of item creating?
As mentioned above, before you start writing a program, you need to learn the basics. You can't mix QML and Javascript in this way. It also seems to me that ListElement is completely out of place here.
Here's how it can be rewritten:
Repeater {
model: 5
delegate: Rectangle {
x: 100 * index
y: 100
width: 50
height: 50
color: "red"
rotation: 0
}
}
If you need more control over the data, you can change to this:
Repeater {
model: [1, 2, 3, 4, 5]
delegate: Rectangle {
x: 100 * modelData
...
}
}
For your example, I highly recommend #folibis answer as that demonstrates the usage of index and modelData which both are most appropriate for your scenario.
Generally speaking, you can mix QML/Javascript, you just need more practice in knowing the correct syntax/context where you can use QML and where you can use Javascript. Also, there are legimite scenarios where you want to populate a ListModel programmatically.
In the following demo, a ListModel is populated programmatically in JavaScript imperatively. In fact, it is even executed after all the QML components are initialized, so, the Repeater actually initially sees an empty ListModel. Whilst executing the populate function, each append signals changes to the ListModel which your Repeater will acknowledge through incremental updates.
import QtQuick
import QtQuick.Controls
Page {
Repeater {
model: listModel
delegate: Rectangle {
x: p
y: q
width: w
height: h
color: c
rotation: o
}
}
ListModel {
id: listModel
function populate() {
for ( let i = 1; i <= 5; i++ )
{
listModel.append( { p:100*i, q:100, w:50, h:50, c:"red", o:0 } );
}
}
}
Component.onCompleted: listModel.populate()
}
You can Try it Online!
Some time ago, when I was playing around with QML with the kids, we mocked up a "Click Invaders" game. The game has the following features:
ListModel will contain the position of the invaders
Repeater is used to render the invaders
Timer object is used to guarantee to make 10 invaders slowly appear on the screen
When the user clicks, the invader will "die" and be removed - this will trigger more invaders to spawn
Check it out. It has some concepts such as using SVG to render the graphic. It dynamically creates and destroys objects in different positions and orientations:
https://github.com/stephenquan/qmlonline6/wiki/Example-Click-Invaders
A very important section of the documentation to start with when coming to Qt Quick without JavaScript knowledge:
https://doc.qt.io/qt-6/qtqml-javascript-expressions.html
It's a must-see here if you didn't expect that you'll need to learn something besides C++ along with Qt.
The JavaScript environment provided by QML is stricter than that in a web browser. For example, in QML you cannot add to, or modify, members of the JavaScript global object. In regular JavaScript, it is possible to do this accidentally by using a variable without declaring it. In QML this will throw an exception, so all local variables must be explicitly declared. See JavaScript Environment Restrictions for a complete description of the restrictions on JavaScript code executed from QML.
Various parts of QML documents can contain JavaScript code:
The body of property bindings. These JavaScript expressions describe relationships between QML object properties. When dependencies of a property change, the property is automatically updated too, according to the specified relationship.
The body of Signal handlers. These JavaScript statements are automatically evaluated whenever a QML object emits the associated signal.
The definition of custom methods. JavaScript functions that are defined within the body of a QML object become methods of that object.
Standalone JavaScript resource (.js) files. These files are actually separate from QML documents, but they can be imported into QML documents. Functions and variables that are defined within the imported files can be used in property bindings, signal handlers, and custom methods.
Should carefully distinguish between JavaScript and QML elements in Qt Quick. If QML is a language for creating a GUI, its elements are the types described in doc.qt.io. On the other hand, loops, variable definitions, arrays, etc. are JavaScript elements. It's not so obvious at first. By the way, this is a very good lesson if someone did not program in JS, only in C ++. The closeness of the syntax of the two languages ​​may be surprising.
The documentation section above explains these dependencies

QTableView / QTableWidget: Stretch Last Column

How to stretch the last column of a TableView in qml?
In C++ with Qt framework it is possible to use setStretchLastSection from QHeaderView but is there a way to do it in qml?
EDIT: To be more specific: On the screenshot below, I want the three column to fill the entire width of the TableView, in order to get rid that "ghost" 4th column.
Edit2:
I wanted to do something like that:
TableView{
model: ListModel{
ListElement {
name: "Apple"
cost: 2.45
}
ListElement {
name: "Orange"
cost: 3.25
}
ListElement {
name: "Banana"
cost: 1.95
}
}
TableViewColumn{
role: "name"
title: "Name"
}
TableViewColumn{
role: "cost"
title: "Cost"
}
Component.onCompleted: stretchLastSection /*<-----/*
}
But the way to do it for now seems to be by calculating the width of TableViewColumn as #folibis said.
Some related topic with QtDesigner and Qt framework:
QTableView / QTableWidget: Stretch Last Column using Qt Designer
Qt table last column not stretching to fill in parent
How to stretch QTableView last column header

finding click event in Qml TableView Header when Column is clicked

I have 2 static ListModel's in this example, in reality I use LocalStorage to fill the ListModel, but to keep it simple, I added 2 buttons to change the Models, but I want to tie it to the TableView's Header Column click event, and can not figure out how to do that from other examples of trying to sort, I do not know if it is possible to have a sort using ListModel, I could not find any example, so can someone explain this or show an example, of how to replace the buttons with column click events, I can then use this to pass the sort by argument to my LocalStorage sql statement to update the ListModel.
Update: I forgot to mention I was looking for a Qml / Qml JavaScript solution, for some reason I thought if I left off the C++ tag, I would avoid this issue, I will use this method as a last resort, since I decided to write this App using only Qml, with no C++ back end, but I do have that version now, since I had issues with how I was importing JavaScript written for the Web, as opposed to Qml JavaScript, which is not the same.
To be clear, I am trying to change Models and not Sort the rows, those are not the same question, the difference is in how the click event is used, and all I want to do is change the name of a query in the back end, which is Qml JavaScript, the reason I do not want C++ solutions is because I am doing this in Felgo, but this is not a Felgo quesiton, it works fine with C++, but you have to set up Live to work with it, and in reality this is going to be source I open up to github, and want it to be able to work without C++, and it seems there should be a way to hook this, and Mouse did work for me, capturing it in the even keep the header from loading, since it hooks at the beginning and waits for input, but if I have to, I am sure your solution will work, then I will accept it, sorry about that confusion, I get confused about what tags to use, so originally I only included qml, and qt was added to it, which I though was a great idea, because this really is a Qt question, that relates to Qml only, and not C++, that is another tag, this is a trend that Felgo is pushing, and they have good reasons, its easier for JavaScript or C/C++ Programmers to use, and Live Debugging works faster when used without a C++ back end, so now I gave more information, when Originally I though this was a simple question, that only related to Qml, if not then the answer has been given for C++, unless there is a better way, seeing how all I want to do is click on the header column the same way I would like on the button, can I embed the button into the column? If so how? I could not find an example of this, only ones that would effect text properties, and would sort the rows, which is not what I was trying to do, only update the model.
import QtQuick 2.11
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.3
import QtQuick.Window 2.11
Window {
visible: true
width: 640
height: 480
title: qsTr("TableView Sort")
Column {
id: column
spacing: 9
anchors.fill: parent
TableView {
id: tableView
anchors.left: column.left
anchors.leftMargin: 6
anchors.right: column.right
anchors.rightMargin: 273
highlightOnFocus: true
model: myListModel1
sortIndicatorVisible: true
TableViewColumn {
role: "title"
title: "Column 1"
//width: 133
}
TableViewColumn {
role: "description"
title: "Column 2"
//width: 166
}
}
Button {
id: button1
text: qsTr("Model 1")
anchors.left: column.left
anchors.leftMargin: 6
onClicked: {
tableView.model = myListModel1
}
}
Button {
id: button2
text: qsTr("Model 2")
anchors.left: column.left
anchors.leftMargin: 6
onClicked: {
tableView.model = myListModel2
}
}
}
ListModel {
id: myListModel1
ListElement {
title: "Orange"
description: "Orange is Orange"
}
ListElement {
title: "Banana"
description: "Yellow"
}
ListElement {
title: "Apple"
description: "Red"
}
}
ListModel {
id: myListModel2
ListElement {
title: "Apple"
description: "Red"
}
ListElement {
title: "Banana"
description: "Yellow"
}
ListElement {
title: "Orange"
description: "Orange is Orange"
}
}
}
Update: This worked
onSortIndicatorColumnChanged: tableView.model = (sortIndicatorColumn == 0) ? myListModel1 : myListModel2
onSortIndicatorOrderChanged: tableView.model = (sortIndicatorColumn == 0) ? myListModel1 : myListModel2
Thanks for any help.
You could use a proxy model to sort your model. But, there is no QML component and you have to use QSortFilterProxyModel.
It's quite easy to do. But, QSortFilterProxyModel is not made to be used with a QML tableview (your table uses role names to display the columns and the proxy model will attempt to sort on index).
A quick example:
class SortProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
SortProxyModel(): QSortFilterProxyModel ()
{
}
// Define the way you want to sort data
bool lessThan(const QModelIndex& left, const QModelIndex& right) const
{
int role = sourceModel()->roleNames().key(roleName.toLocal8Bit(), 0);
return left.data(role) < right.data(role);
}
Q_INVOKABLE void setSortRole(QString const& roleName) // Used to select the sort role
{
this->roleName = roleName;
}
Q_INVOKABLE virtual void sort(int /*column*/, Qt::SortOrder order = Qt::AscendingOrder)
{
QSortFilterProxyModel::sort(0, order); // Always use the first column.
}
private:
QString roleName; // Role used to sort the model
};
// main.cpp
// Declare your type to use it in QML
qmlRegisterType<SortProxyModel>("SortProxyModel", 0, 1, "SortProxyModel");
// Main.qml
import SortFilterProxyModel 0.1;
TableView {
id: tableView
model: proxy // Use the proxy model rather than the model itself
sortIndicatorVisible: true
onSortIndicatorColumnChanged: { // Called when you click on the header
if (sortIndicatorColumn == 0) // Set the role used to sort data
model.setSortRole("title");
else
model.setSortRole("description");
model.sort(sortIndicatorColumn, sortIndicatorOrder)
}
onSortIndicatorOrderChanged: { // Called when you click on the header
if (sortIndicatorColumn == 0) // Set the role used to sort data
model.setSortRole("title");
else
model.setSortRole("description");
model.sort(sortIndicatorColumn, sortIndicatorOrder)
}
SortProxyModel {
id: proxy
objectName: "proxy"
sourceModel: myListModel1
}
It's just a quick example and you should improve the code. But, I think it will be a good start...

How to set the model's column index of a TableViewColumn in QML using QAbstractTableModel

This question is basically asking the same thing - Is it possible to use QAbstractTableModel with TableView from QtQuick.Controls?
I have a QAbstractTableModel with data in the rows and columns. I want to be able to specify which column in my model the view should be displaying. This is not at all obvious to do, any help would be appreciated.
Basic examples rely on unique role names to ensure the correct data arrives in a given column, eg:
TableView {
TableViewColumn { role: "title"; title: "Title"; width: 100 }
TableViewColumn { role: "author"; title: "Author"; width: 200 }
model: libraryModel
}
The only hack I can think of is to assign each column in my model a unique role name, but this seems like a code smell, is there another simple way to specify the column index of my TableViewColumn?

Use the size of a model in QML

I am looking for a way to access the number of elements that are to be found in a model in QML.
For example:
Item {
id: root
Row {
id: row
height: root.height
width: root.width
spacing: 5
Repeater {
id: rep
width: root.width
height: root.height
model: [5, 3, 3, 1, 12]
delegate: myDelegate
Text {
id: myDelegate
text: "Element " + index + " of " size + ":" + modelData
}
}
}
But I can't figure out how to retrieve the size of the model.
In the documentation I can find a property called count, but no hint how to access it.
It depends on the model you're using. In your case, the model is a plain old JavaScript array, so you'd use model.length. Every other Qt type related to models or views has a count property: ListModel, Repeater, ListView, etc. So, you could also use rep.count, in your case.
Generic way is to request repeater for it's underlying model count - in your case it would be rep.count.

Resources