Solved positioning within a grid...revisited
-
Hi all -
I got some help with this awhile back here, but evidently I didn't understand what I was doing, so I need to take it up again.
The problem: I have a gridlayout in which I want to place rectangles over the columns, and circles in the rectangle, centered on the row and column. That's basically it.
Here's my code (sorry it's so long; I edited it as much as I could:
import QtGraphicalEffects 1.12 import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Controls.Universal 2.2 import QtQuick.Layouts 1.14 import libstyles 1.0 Item { id: root property var wellPlateRowNames: ["A", "B", "C", "D", "E", "F", "G", "H"] property var wellPlateColNames: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] property int nbrPlateRows: wellPlateRowNames.length property int nbrPlateCols: wellPlateColNames.length GridLayout { id: wellsGrid anchors.fill: parent columnSpacing: 0 // the default is non-zero! rowSpacing: 0 anchors.margins: gridMargin_ property double cellHeight: (wellsGrid.height - (gridMargin_ * 2)) / wellsGrid.rows property double cellWidth: (wellsGrid.width - (gridMargin_ * 2)) / wellsGrid.columns columns: root.nbrPlateCols + (hideRowNames ? 0 : 1) rows: root.nbrPlateRows + (hideColNames ? 0 : 1) // column caps Repeater { id: columnCapsRepeater model: issViewModel.columnCapList property var capHeight: parent.height Item { Layout.minimumHeight: wellsGrid.cellHeight Layout.minimumWidth: wellsGrid.cellWidth Layout.maximumHeight: Layout.minimumHeight Layout.maximumWidth: Layout.minimumWidth Layout.row: Math.floor(index / wellPlateColNames.length) + 1 Layout.column: (index) + (hideRowNames ? 0 : 1) Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Rectangle { id: columnStrip function getHeight() { var wgh = wellsGrid.cellHeight var nr = root.nbrPlateRows + 1 // I don't know why this "+1" is needed. var h = wgh * nr var s = wellsGrid.rowSpacing s *= (root.nbrPlateRows - 1) h += s return h } height: getHeight()//wellsGrid.height * root.nbrRows width: wellsGrid.cellWidth radius: 3 color: 'lightgray' // the individual caps on the cap column. ColumnLayout { id: capLayout function calculateSpacing () { var stripHeight = columnStrip.height var capHeight = wellsGrid.cellHeight * root.wellCircleFromEnclosingRect var totalSpacing = stripHeight - (capHeight * root.nbrPlateRows) var unitSpacing = totalSpacing / (root.nbrPlateRows - 1) return unitSpacing } spacing: calculateSpacing() onVisibleChanged: { if (visible) { calculateSpacing() } } Repeater { id: wellCaps model: wellPlateRowNames.length Layout.row: Math.floor(index / root.nbrPlateCols) + 1 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Rectangle { id: wellCap height: wellsGrid.cellHeight * root.wellCircleFromEnclosingRect width: height radius: height Layout.alignment: Qt.AlignHCenter border.color: 'dimgray' color: 'deeppink' } } }
And here's what I'm getting:
Can someone provide me with guidance on how to center within a grid? Thank you.
-
question: why do you even bother with a GridLayout, when you provide columns and rows with their own repeaters ?
import QtGraphicalEffects 1.12 import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Controls.Universal 2.2 import QtQuick.Layouts 1.12 //import libstyles 1.0 Item { id: root property var wellPlateRowNames: ["A", "B", "C", "D", "E", "F", "G", "H"] property var wellPlateColNames: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] property int cellWidth: width / wellPlateRowNames.length property int cellHeight: height / wellPlateColNames.length Column{ id:cols Repeater{ model: wellPlateColNames delegate: Row{ id:row property var colData: modelData Repeater{ model: wellPlateRowNames delegate: Item{ width: cellWidth height: cellHeight Rectangle{ anchors.centerIn: parent width: Math.min(cellWidth, cellHeight) height: width radius: height / 2 color: "pink" } Text { anchors.centerIn: parent text: row.colData + modelData } } } } } } }
-
@J-Hilk to answer your question, I inherited this view from another developer, so I'm not sure why a lot of stuff is in here. But, given how much time I've spent on trying to fix their stuff, it might be better to start from scratch.
So, regarding your example, I have a few questions:
- your rows and columns are interchanged. I tried fixing it like this:
Row { id: rows Repeater { model: wellPlateColNames delegate: Column { id: columns property var colData: modelData Repeater { model: wellPlateRowNames delegate: Item { width: cellWidth height: cellHeight Rectangle { anchors.centerIn: parent width: cellWidth height: width radius: height / 2 color: "pink" } Text { anchors.centerIn: parent text: modelData + columns.colData } } } } } }
But I only get one row to display. What am I doing wrong?
2. Why, in your output, are there gaps between the columns and not between the rows?
3. Why is it necessary to use the intermediate delegate: Item, and not just repeat on a Rectangle? From the docs, it seems as though the only thing the delegate does for you is expose an index, but I don't see that we're using it in this example.Thanks...
EDIT: I found the answer to #2 above:
property int cellWidth: width / wellPlateRowNames.length property int cellHeight: height / wellPlateColNames.length
Should be:
property int cellWidth: width / wellPlateColNames.length property int cellHeight: height / wellPlateRowNames.length
EDIT 2: #1 is answered; I got the vertical repeater working; was missing a delegate...I guess that answers my question #3 as well.
-
So...my "dots" are now displaying properly. (I've got a bunch of changes I need to make to them, but I'll ignore that for now.)
This display is (optionally) supposed to display the row and column headers as well. I've disabled the dots, and tried doing the same thing that J.Hilk showed above for the headers (disabling the dots for now), but I get this:
Can someone tell me why they aren't spreading out? Thanks...
-
I've reduced this as much as possible, but the file's still sort of long; sorry.
This code:
import QtQuick 2.12 import libstyles 1.0 Item { id: root property var wellPlateRowNames: ["A", "B", "C", "D", "E", "F", "G", "H"] property var wellPlateColNames: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] property double cellHeight: height / wellPlateRowNames.length property double cellWidth: width / wellPlateColNames.length width: 720 height: 480 // column names Repeater { model: wellPlateColNames delegate: Item { height: parent.cellHeight * 0.67 width: parent.cellWidth Rectangle { Text { anchors.fill: parent anchors.centerIn: parent text: wellPlateColNames[index] color: 'black' verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } } } Column { Repeater { model: wellPlateRowNames delegate: Row { id: rows property var colData: modelData Repeater { model: wellPlateColNames delegate: Item { width: cellWidth height: cellHeight Rectangle { anchors.centerIn: parent width: cellWidth height: width radius: height / 2 color: "pink" } Text { anchors.centerIn: parent text: rows.colData + modelData } } } } } } }
Produces this:
So, the wells (the pink circles) are displaying correctly, but the column names are not. Can someone please tell me what I'm doing wrong in the first repeater?
Oh: notice the little black rectangle in the very upper left; I think that's all the column names sitting on top of each other.
Thanks...
-
OK, through sheer trial and error, I got my column headings to appear (I removed the wells for the example):
import QtQuick 2.12 Item { id: root property var wellPlateColNames: ["", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] property double cellSize: 60 // nominal; expected to be overridden width: cellSize * wellPlateColNames.length height: cellSize * wellPlateRowNames.length // column names Row { Repeater { model: wellPlateColNames delegate: Item { height: cellSize width: cellSize Rectangle { anchors.centerIn: parent Text { anchors.centerIn: parent text: wellPlateColNames[index] color: 'black' verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } } } } }
So...having both an Item and a Rectangle within my Repeater seems a bit redundant. When I try to eliminate either, though, I get a QML error "QML Row: Cannot specify left, right, horizontalCenter, fill or centerIn anchors for items inside Row. Row will not function." Is the use of both an Item and Rectangle a technique to get around this? If so, is there a preferred method of doing this?
Thanks...
-
@mzimmers hey, sorry for the late reply, long weekend, little bit of sickness, mothers day etc. 😳
Well, first of great that you managed to understand and evolve the (basic) example I provided, not looking to bad !
To answer some of your questions:
its probably best to answer with an other example, you're looking for an outer display of row names and column names, that can be toggled on and off
import QtQuick 2.12 Item { id: root property var wellPlateRowNames: ["A", "B", "C", "D", "E", "F", "G", "H"] property var wellPlateColNames: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] property double cellHeight: height / (wellPlateRowNames.length + (showHeader2 ? 1 : 0)) property double cellWidth: width / (wellPlateColNames.length + (showHeader1 ? 1 : 0)) Behavior on cellHeight { NumberAnimation{duration: 200 }} Behavior on cellWidth { NumberAnimation{duration: 200 }} property bool showHeader1: true property bool showHeader2: true Timer{ running:true interval: 1000 repeat: true onTriggered:{ showHeader1 = Math.round(Math.random() % 2) showHeader2 = Math.round(Math.random() % 4) } } // Row names Column{ id:headerRow anchors{ left: parent.left right: undefined top: headerCol.bottom bottom: parent.bottom } width: showHeader1 ? cellWidth : 0 Behavior on width { NumberAnimation{duration: 200 }} Repeater { model: wellPlateRowNames delegate: Rectangle { // Rectangle if you want white backround, item if you want transparency height: cellHeight width: cellWidth Text { anchors.fill: parent anchors.centerIn: parent text: modelData color: 'black' verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } } } // column names Row{ id:headerCol anchors{ left: headerRow.right right: parent.right top: parent.top bottom: undefined } height: showHeader2 ? cellHeight : 0 Behavior on height { NumberAnimation{duration: 200 }} Repeater { model: wellPlateColNames delegate: Item { height: cellHeight width: cellWidth Text { anchors.fill: parent anchors.centerIn: parent text: modelData color: 'black' verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } } } Column { anchors{ left: headerRow.right right: parent.right top: headerCol.bottom bottom: parent.bottom } Repeater { model: wellPlateRowNames delegate: Row { id: rows property var colData: modelData Repeater { model: wellPlateColNames delegate: Item { width: cellWidth height: cellHeight Rectangle { anchors.centerIn: parent width: cellWidth height: width radius: height / 2 color: "pink" } Text { anchors.centerIn: parent text: rows.colData + modelData } } } } } } }
I added a timer to randomly turn the headers on and off
Can someone please tell me what I'm doing wrong in the first repeater
the Repater doesn't fill a layout or column/row and you don't give the delegates of the repeater width/height or x/y yourself so they all stack. In the example I simply used an extra column/row to manage that
So...having both an Item and a Rectangle within my Repeater seems a bit redundant. When I try to eliminate either, though, I get a QML error "QML Row: Cannot specify left, right, horizontalCenter, fill or centerIn anchors for items inside Row. Row will not function."
In the example I fixed that,
You could potentially get around the double item/rectangle for the main row/col view as well, but than you need to manage the spacing property of row/col to fix spacing issues, that we currently circumvent by filling the extra space with an invisible item :D
hope this helps!
Btw. Row/Col solution is one of many and simply the first one that came to my mind, a grid view or a proper model and tableview may actually better suited for your case here. In case you want to do it right and not fast :D
-
@mzimmers said in positioning within a grid...revisited:
I've reduced this as much as possible, but the file's still sort of long; sorry.
I think you should take time to read Qt documentation about "positionner" and "layout": https://doc.qt.io/qt-5/qtquick-usecase-layouts.html
In your first try, you used only a
repeater
, so you will use "Manual Positioning". In this case, you have to specify yourself X and Y position of each item. Per default, X=0 and Y=0, so everything is on same position!Row
in combination withrepeater
, you are using a "Positionner". The positioner will upate those coodinates for you.Another solution could be to use a Layout, so you could add spacings between each item and handle automatic resizing of the items.
-
@J-Hilk that's a good example...thanks. I tweaked it slightly for reasons I hadn't bothered to share, but it works.
After looking at your example, I realized I was making a mistake by reusing the name of the data model ("wells"). That's fixed, but something's now wrong with the way I access the model.
I need to determine the color of the well (the dot) based on a property in the model. The model is defined as:
Q_PROPERTY(QQmlListProperty<dnas::Well> wells READ GetWells NOTIFY EventWellsUpdated)
This worked before my re-write, but now I don't seem to be able to extract a valid index from the model. Here's a code snippet:
Item { id: root property var wells: null // provided by caller property var wellPlateRowNames: ["A", "B", "C", "D", "E", "F", "G", "H"] property var wellPlateColNames: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] function getColor(index) { var color color = Utils.getColorByWellState(wells[index].seqState, wells[index].yieldState, useSeqColors) return color } Column { Repeater { model: wellPlateRowNames delegate: Row { Repeater { model: wellPlateColNames delegate: Item { Rectangle { color: root.getColor(root.wells.index)
When I go through the getColor() function in the debugger, the argument index is always undefined. I'm almost positive the model is good...what am I doing wrong here?
Thanks.
-
Well, I'm still not sure how I broke the wells.index value, but I've worked around it with this:
Repeater { model: wellPlateRowNames delegate: Row { id: wellRows property var i: index property var colData: modelData Repeater { model: wellPlateColNames delegate: Item { id: colInRow property var i: index Rectangle { property int i: (colInRow.i * 12) + wellRows.i color: root.getColor(i)
Certainly not the most elegant solution, but it works. Still, I'd like to know if anyone sees a problem with my prior effort.
Thanks...
-
I decided that using a Grid was a more natural implementation:
Grid { id: wellsGrid columns: nbrColsInPlate columnSpacing: 0 // the default is non-zero! rowSpacing: 0 anchors { left: columnOfRowNbrs.right right: parent.right top: rowOfColNbrs.bottom bottom: parent.bottom } Repeater { model: wells delegate: Item { width: cellWidth height: cellHeight // the well. Rectangle { anchors.centerIn: parent width: wellWidth height: width radius: height / 2 color: root.getColor(index) }
This works great for the individual cells. Now, I need to add to this view, by (selectively) drawing rectangles over the columns. I've looked at the Grid docs, but I don't see a way to reference a particular column, and therefore no way to center these rectangles. Am I going to have to use manual positioning?
Thanks...
-
I don't know if this is a particularly good way to do it, but I managed to make it work like this:
Repeater { id: columnCapsRepeater model: issViewModel.columnCapList property var capHeight: parent.height Item { x: (cellWidth * index) + (hideColNames ? 0 : (cellWidth * headingSizeRatio)) y: hideRowNames ? 0 : (cellHeight * headingSizeRatio) height: cellHeight * nbrRowsInPlate width: cellWidth Rectangle { id: capStrip height: parent.height width: parent.width anchors.fill: parent anchors.leftMargin: cellWidth * 0.02 anchors.rightMargin: cellWidth * 0.02 radius: width * 0.1 color: 'lightgray' }
I welcome any feedback on this approach. Thanks to everyone for the help!