TableView - Show an optional item covering the whole row of a multi-columns table view
-
In a project, I added a
TableView
component, which is composed by a multiple columns header and several rows, as shown in the below screenshot:Depending on events happening in the application, I need to cover a part of some items with a kind of overlay, which e.g shows an error or ask a confirmation to the user, as shown in the below screenshot:
My idea was to add a special
Item
component in myTableWiew
delegate, which would be optionally visible under certain conditions, and only drawn with the first column (i.evisible: column === 0
) but covering the last part of my row by using the delegate anchoring and the parent view width.This solution seems possible, but has several issues:
- The content of the columns covered by the item are visible above the item, even if they z order is set to 0 whereas the item z order is set to 1. Probably because the
TableView
draws the columns randomly, depending on what it needs. - I tried to draw the item on the last column instead of the first one. This resolve partially the issue mentioned in point 1 but randomly the content of several columns appear above my item, depending on if the tree decide to refresh the content of these particular columns.
- I tried to paint the background of each column covered by my item. This resolve the point 1 and 2, but the components contained in my item are also partially cropped by the background.
So my questions are:
- Is my idea correct or is there another way to achieve that?
- If my idea isn't the good one, what is the best way to create a such item?
- From the qml, is there a signal which notifies that a row was painted, or a way to know that a particular row was painted?
- The content of the columns covered by the item are visible above the item, even if they z order is set to 0 whereas the item z order is set to 1. Probably because the
-
Thanks to all for the replies.
I finally resolved the issue by replacing the
TableView
by aListView
and rewriting the columns handling manually. As theListView
draws the whole item content once, all the above mentioned issues are resolved at the same time.However very interesting concepts were presented here, and they may help me for other similar situations in the future. Once again, thank you very much to took the time to help me and reply to my questions.
-
@jeanmilost Hi:
Do you want something like this?Regards,
Emmanuel -
How did you do the status bars? Did you use a popup?
I imagine you could use another popup and ensure it covers the status bar?
Here is the status bar code I played with:import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.12 import Qt.labs.qmlmodels 1.0 Window { id: root visible: true width: 800 height: 600 title: qsTr("TableView Testing") Row { id: root_row TableView { id: tableview1 width: root.width/2 height: root.height - 50 clip: true model: tablemodel delegate: Item { id: del_item implicitWidth: tableview1.width/3 implicitHeight: del_height + progbar_height property bool column0: column == 0 property int del_height: 25 property int progbar_height: statusTip < 1.0 ? 20 : 0 Rectangle { anchors.fill: parent color: "lightsteelblue" clip: true border.width: 1 border.color: "steelblue" Text { id: del_text height: del_item.del_height text: display } Popup { y: del_item.del_height width: tableview1.width height: del_item.progbar_height visible: del_item.column0 && del_item.progbar_height > 0 modal: false focus: false closePolicy: Popup.NoAutoClose background: Rectangle { color: "transparent" } contentItem: ProgressBar { from: 0 to: 1.0 value: statusTip anchors.fill: parent anchors.margins: 5 } } } } } } Slider { id: progress_slider anchors.top: root_row.bottom value: 1.0 from: 0 to: 1.0 } TableModel { id: tablemodel property real progress: progress_slider.value TableModelColumn { display: "text"; statusTip: "progress" } TableModelColumn { display: "path"; statusTip: "progress" } TableModelColumn { display: "image"; statusTip: "progress" } rows: [ { text: "one", path: "/root/one.txt", image: "/root/one.png", progress: progress }, { text: "two", path: "/root/two.txt", image: "/root/two.png", progress: progress }, { text: "three", path: "/root/three.txt", image: "/root/three.png", progress: progress }, ] } }
-
Like this:
import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.12 import Qt.labs.qmlmodels 1.0 Window { id: root visible: true width: 800 height: 600 title: qsTr("TableView Testing") Row { id: root_row TableView { id: tableview1 width: root.width/2 height: root.height - 50 clip: true model: tablemodel delegate: Item { id: del_item implicitWidth: tableview1.width/3 implicitHeight: del_height + progbar_height property bool column0: column == 0 property bool column1: column == 1 property int del_height: 25 property int progbar_height: statusTip < 1.0 ? 20 : 0 Rectangle { anchors.fill: parent color: "lightsteelblue" clip: true border.width: 1 border.color: "steelblue" Text { id: del_text height: del_item.del_height text: display } Popup { y: del_item.del_height width: tableview1.width height: del_item.progbar_height visible: del_item.column0 && del_item.progbar_height > 0 modal: false focus: false closePolicy: Popup.NoAutoClose background: Rectangle { color: "transparent" } contentItem: ProgressBar { from: 0 to: 1.0 value: statusTip anchors.fill: parent anchors.margins: 5 } } Popup { width: del_item.implicitWidth*2 height: del_item.implicitHeight // del_item.del_height // if you want to show progress bar visible: del_item.column1 && decoration modal: false focus: false closePolicy: Popup.NoAutoClose onHeightChanged: { //console.log(height) } background: Rectangle { color: "darkred" } Text { anchors.centerIn: parent text: "BIG BAD!" } } } } } } Slider { id: progress_slider anchors.top: root_row.bottom value: 1.0 from: 0 to: 1.0 } TableModel { id: tablemodel property real progress: progress_slider.value TableModelColumn { display: "text"; statusTip: "progress"; decoration: "warning"} TableModelColumn { display: "path"; statusTip: "progress"; decoration: "warning"} TableModelColumn { display: "image"; statusTip: "progress"; decoration: "warning"} rows: [ { text: "one", path: "/root/one.txt", image: "/root/one.png", progress: progress, warning: false }, { text: "two", path: "/root/two.txt", image: "/root/two.png", progress: progress_slider.to - progress, warning: true }, { text: "three", path: "/root/three.txt", image: "/root/three.png", progress: progress, warning: false }, ] } }
-
Thank for all the answers.
@Allon Not exactly. In fact I need an area which covers a part of the existing row, showing extra information like a confirmation to user, or an error message. This area should appear on the top of all existing components already contained in the row, and may cover them , partially or completely. The area should also not be dependent of the columns, i.e it may cover several columns, partially or completely. I know that this behavior may be somewhat illogical, but it's a feature required by our client.
@fcarney The status bar is just a
Rectangle
covering the bottom of the row. It is drawn while the first cell of the row is drawn, and as the clipping is ignored it may cover the whole row without problems. The key is that no child component is drawn on the same location covered by the progress, so there is no drawing conflicts here and I can draw it once for all (to be honest I implemented this progress based on another question answered by yourself, here: https://forum.qt.io/topic/117322/how-to-add-a-component-onto-the-whole-row-of-a-multi-column-tableview).But the situation isn't the same here, unfortunately. The overlay I want should cover already existing children components, and they enter in conflict with it. I tried several solutions, among others:
- drawing my overlay while the first column is drawn. This failed because all the components contained in the other cells were overprinted above my overlay, whatever the z value.
- drawing my overlay while the last column is drawn. This worked better, however the view seems to refresh several cells randomly, and they appear overprinted when this scenario happens.
- force some columns to erase their background before painting the overlay (by drawing a rectangle covering the whole cell content after the normal content was painted). This worked, except that the components drawn in the overlay were also cropped when they overlapped other columns.
- using a popup for my overlay. This was the best solution until now, however there is an issue: the popup isn't clipped into the
TableView
bounds.
About the popup, this would be the solution to my issue if I can apply the above mentioned clipping. Do you know how to achieve that?
-
@jeanmilost Hi
An area which covers a part of the existing row, is it not what happens when the cursor goes over the three points and the rectangle appears with the pen and the bin?
Regards -
@Allon First of all, thank you for your help. Yes, your example may match with what I need, however it's not representative of the issue I face. AFAIK, the idea is to put the three dots component and its 2 buttons overlay in a well bounded cell, or in an
Item
placed at the end of the row if the tree doesn't contain columns.But in my case, things are different. I have a multi-columns tree, and I need an overlay which overlap several cells, completely or partially, and remain independent of these cells, breaking thus the logical provided by the tree. And a such overlay is incompatible with the standard painting process, unfortunately.
I'm aware that creating a multi-columns tree to break the rules and show graphical elements which overlap the cells sounds like a conception issue, and I already reported that to my client. But as he really want the tree working this way, I need to find a solution.
I'm also trying another approach, which use a
ListView
instead of aTableView
, but although this may resolve the overlay issue I described above, I need to rewrite manually the whole columns management, and this brings performance issues as described in this post: https://forum.qt.io/topic/118005/listview-how-to-refresh-rows-which-content-changed-without-slowdown -
@jeanmilost said in TableView - Show an optional item covering the whole row of a multi-columns table view:
About the popup, this would be the solution to my issue if I can apply the above mentioned clipping. Do you know how to achieve that?
I didn't, but now I do:
delegate: Item { id: del_item implicitWidth: tableview1.width/3 implicitHeight: del_height + progbar_height property bool column0: column == 0 property int del_height: 25 property int progbar_height: statusTip < 1.0 ? 20 : 0 property bool column1: column == 1 property var coords onCoordsChanged: { badrect.x = coords.x badrect.y = coords.y } onXChanged: { coords = mapToItem(tableview1,0,0) } Rectangle { id: badrect color: "darkred" parent: tableview1 visible: del_item.column1 && decoration width: del_item.implicitWidth*2 height: del_item.implicitHeight } Rectangle { anchors.fill: parent color: "lightsteelblue" clip: true border.width: 1 border.color: "steelblue" Text { id: del_text height: del_item.del_height text: display } Popup { y: del_item.del_height width: tableview1.width height: del_item.progbar_height visible: del_item.column0 && del_item.progbar_height > 0 modal: false focus: false closePolicy: Popup.NoAutoClose background: Rectangle { color: "transparent" } contentItem: ProgressBar { from: 0 to: 1.0 value: statusTip anchors.fill: parent anchors.margins: 5 } } /* Popup { width: del_item.implicitWidth*2 height: del_item.implicitHeight // del_item.del_height // if you want to show progress bar visible: del_item.column1 && decoration modal: false focus: false closePolicy: Popup.NoAutoClose onHeightChanged: { //console.log(height) } background: Rectangle { color: "darkred" } Text { anchors.centerIn: parent text: "BIG BAD!" } } */ } }
The trick is re-parenting the rectangle (badrect) to the tableview1 and capturing when the coordinates of the del_item changes. You cannot get the coordinates during the Component.onCompleted signal. It has to be done later. There might be a more elegant way to get this, but whatever. If x changes more than once you should be okay. Not sure if this will work when a cell is on the left side of a table, or at the top if tracking y. So maybe calling mapToItem with x and y in the call as dummy change triggers would be better.
Edit: Maybe this won't work. It still seems to have clipping issues. I don't understand why. My tableview is set to clip.
Edit 2: This may have more to do with my tableview not resize when the window resizes. Not sure what the issues is there. -
@jeanmilost
Hi,
I would still not use popup.
Here is how I did it.
https://invent.kde.org/education/gcompris/-/blob/server_allon/src/activities/server/components/ManageGroupsBar.qmlThe line that will interest you is the following:
https://invent.kde.org/education/gcompris/-/blob/server_allon/src/activities/server/components/ManageGroupsBar.qml#L179If I get hover the elipses mousearea I set modifyGroupCommandsRectangle to visible, which means I display the rectangle with my pen and my bin.
In your case this would be one rectangle with "download failed and cancel".At the moment I am working with RowLayout and a repeater, but I will change it soon to a Listview + inner rectangles to be able to take advantage of the flickable property.
If you want to cover several cells you can anchor your modifyGroupCommandsRectangle to the anchors.right of your listview element and set the length of it to the addition of several cells length.
You can even using repeater index to set your "download failed and cancel" on any element of you listview.Hope I got it right.
-
Thanks to all for the replies.
I finally resolved the issue by replacing the
TableView
by aListView
and rewriting the columns handling manually. As theListView
draws the whole item content once, all the above mentioned issues are resolved at the same time.However very interesting concepts were presented here, and they may help me for other similar situations in the future. Once again, thank you very much to took the time to help me and reply to my questions.