Written by Bo Thorsen
2015/09/30
If none of the four standard positioners (Column, Row, Flow, Grid) work for you, you can write your own.
I have a pet project that involves a poker table. This is done with a list model that has a list of seats with the name, stack size etc. exposed as custom roles. In this example, I only use the “name” role in a Text item. The real application has a Seat custom item that uses all the roles.
I have a C++ implementation of the table (exported to the QML as PokerTable in the Poker module), which draws the table and calculates the seat positions. My goal now is to create the seat items on the correct positions. There is another C++ class that implements the hand model.
The first step is to just show that the model works. I do this with the standard Row positioner and a Repeater for the model:
import QtQuick 1.0
import Poker 1.0
Item {
PokerTable {
id: table
anchors.fill: parent
Row {
anchors.centerIn: parent
Repeater {
model: handModel
Text { text: name }
}
}
}
}
With this simple QML, I can see my table with the player names placed next to each other in the center of the table. So far so good.
Next step is to implement a simple positioner, that shows how this actually works:
import QtQuick 1.0
import Poker 1.0
Item {
PokerTable {
id: table
anchors.fill: parent
Item {
id: seatView
anchors.fill: parent
onChildrenChanged: updatePositions();
Repeater {
model: handModel
Text { text: name }
}
function updatePositions() {
for (var i = 0; i < seatView.children.length; ++i) {
seatView.children[i].pos = Qt.point(i * 50, i * 5);
}
}
}
}
}
This code calculates the positions for each of the seats, when children are added or removed. The position calculations do not make any sense, they just show an effect where you can be sure that it works.
In my case, I want the table class to calculate the seat positions, because I don’t want to copy those calculations to another place. But it might be that the above code pattern is all you need to implement your own positioner, if you can code it in javascript.
A couple of final touches are also needed. First, I’ll add (but not show here) a signal from the table class that can be emitted when the positions change for other reasons – resizing, for example.
The updatePositions()
function will be called every time a child is added. I can have ten seats, so my function will be called when the first child have been added, then when the second, etc. That means it’s a bad idea to do the real calculations when new seats will be added immediately after. There are several ways to fix this. You could add a zero time timer that is restarted when the signal fires and will run the update function as it’s done. In my case, I already have the seat count exported as a property on my table, so I’ll check that the seat count is the same as the number of children before doing any work.
You might need to handle that the repeater is also a child of the view. I do this by setting an object name on the repeater and skip this child.
For the actual positions of the seats, I add this function in my C++ table class:
Q_INVOKABLE QPointF seatPosition(int seat) const;
Now I can do the proper positioning in the QML slot:
function updatePositions() {
if (seatView.children.length == table.seats + 1) {
var index = 0;
for (var i = 0; i < seatView.children.length; ++i) {
if (seatView.children[i].objectName != "repeater") {
seatView.children[i].pos = table.seatPosition(index++);
}
}
}
So there you have it, a simple custom positioner that you can modify to place the view items in any kind of positioning you want.