Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Add Button to a Table Cell Under Certain Conditions
Forum Updated to NodeBB v4.3 + New Features

Add Button to a Table Cell Under Certain Conditions

Scheduled Pinned Locked Moved Solved QML and Qt Quick
5 Posts 3 Posters 1.6k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • S Offline
    S Offline
    SCP173
    wrote on last edited by
    #1

    I barely managed to put together a TableView using a TabelModel class that I implemented with Python3. This is a table that would be dynamically instantiated in the future, but for testing purposes I manually added 3 entries in the main method. My goal is to have the cells in the last column be either filled with a button or just be blank depending on the boolean in the last parameter field "Value" So if true, then there would be a button there and if false, just an empty cell. I am not sure how to go about modifying my QML and Python to include this functionality. Any suggestions/thoughts would be appreciated.

    view.qml:

    import QtQuick 2.12
    import QtQuick.Controls.Material 2.15
    import QtQuick.Controls 2.15
    
    Item{
    	width: 500
    	height: 500
    
    	TableView {
    	    id: tableView
    	    anchors.fill: parent
    	    topMargin: columnsHeader.implicitHeight
    	    rowSpacing: 5
    	    columnWidthProvider: function (column) { return 50; }
    	    clip: true
    	    model: tableModel
    		
    	    delegate: Rectangle {
    	        implicitHeight: 40
    	        implicitWidth: 40
    	        color: "green"
    	
    	        Text {
    	            anchors.centerIn: parent
    	            text: display
    	            color: "#ffffff"
    	        }
    	    }
    	}
    	
    	Row {
    		id: columnsHeader
    		y: -(tableView.contentY + implicitHeight)
    		Repeater {
    			model: tableView.columns > 0 ? tableView.columns : 1
    			Label {
    				width: tableView.columnWidthProvider(modelData)
    				height: 35
    				text: tableModel.headerData(modelData, Qt.Horizontal)
    				color: '#000000'
    				font.pixelSize: 15
    				padding: 10
    				verticalAlignment: Text.AlignVCenter
    				background: Rectangle { color: "#ffffff" }
    			}
    		}
    	}
    }
    

    main.py:

    import os
    import sys
    
    from pathlib import Path
    
    from PySide6.QtQuick import QQuickView
    from PySide6.QtCore import QUrl, Qt, QAbstractTableModel, Slot, QModelIndex
    from PySide6.QtGui import QGuiApplication
    
    class TableModel(QAbstractTableModel):
        def __init__(self, numCols):
            super().__init__()
            self.columnHeaders = [''] * numCols
            self._data = []
            
            self.setHeaderData(0, Qt.Horizontal, "Id", Qt.EditRole)
            self.setHeaderData(1, Qt.Horizontal, "Name", Qt.EditRole)
            self.setHeaderData(2, Qt.Horizontal, "Value", Qt.EditRole)
            
        def rowCount(self, index):
            return len(self._data)
    
        def columnCount(self, index):
            # Return length of columnHeaders
            return len(self.columnHeaders)
        
        def data(self, index, role=Qt.DisplayRole):
            if not index.isValid():
                return None
    
            if not 0 <= index.row() < len(self._data):
                return None
            
            if role == Qt.DisplayRole:
                id = self._data[index.row()]["Id"]
                name = self._data[index.row()]["Name"]
                value = self._data[index.row()]["Value"]
                
                if index.column() == 0:
                    return id
                elif index.column() == 1:
                    return name
                elif index.column() == 2:
                    return value
            
            return None
        
        def setData(self, index, value, role=Qt.EditRole):
            if role != Qt.EditRole:
                return False
    
            if index.isValid() and 0 <= index.row() < len(self._data):
                data = self._data[index.row()]
                if index.column() == 0:
                    data["Id"] = value
                elif index.column() == 1:
                    data["Name"] = value
                elif index.column() == 2:
                    # Handle functionality here
                    data["Value"] = value
                else:
                    return False
    
                self.dataChanged.emit(index, index, 0)
                return True
            
            return False
        
        @Slot(int, Qt.Orientation, result="QVariant")
        def headerData(self, section, orientation, role=Qt.DisplayRole):
            if orientation == Qt.Horizontal and role == Qt.DisplayRole:
                return self.columnHeaders[section]
    
            return None
        
        def setHeaderData(self, section, orientation, data, role=Qt.EditRole):
            if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
                try: 
                    self.columnHeaders[section] = data
                    return True
                except:
                    return False
            return super().setHeaderData(section, orientation, data, role)
            
        # Default insert 1 row per call with rows=1
        def insertRows(self, position, rows = 1, index=QModelIndex):
            self.beginInsertRows(QModelIndex(), position, position + rows - 1)
            for row in range(rows):
                self._data.insert(position + row, {"Id": "", "Name": "", "Value": ""})
    
            self.endInsertRows()
            return True
        
        def add_entry(self, id, name, value):
            row = {"Id": id, "Name": name, "Value": value}
            
            self.insertRows(id)
            
            # Use index of the newly created row dict
            entries = ["Id", "Name", "Value"]
            for idx, entry in enumerate(entries):
                index = self.index(id, idx, QModelIndex())
                self.setData(index, row[entry], Qt.EditRole)
        
        def flags(self, index):
            if not index.isValid():
                return Qt.ItemIsEnabled
            return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable)
    
    if __name__ == '__main__':
    
        app = QGuiApplication(sys.argv)
        view = QQuickView()
        view.setResizeMode(QQuickView.SizeRootObjectToView)
    
        tableModel = TableModel(3)
        tableModel.add_entry(0, "Apple", True)
        tableModel.add_entry(1, "Pear", True)
        tableModel.add_entry(2, "Banana", False)
        
        view.rootContext().setContextProperty("tableModel", tableModel)
        
        qml_file = Path(__file__).parent / "view.qml"
        view.setSource(QUrl.fromLocalFile(os.fspath(qml_file.resolve())))
    
        if view.status() == QQuickView.Error:
            sys.exit(-1)
        view.show()
    
        sys.exit(app.exec())
        del view
    
    1 Reply Last reply
    0
    • SeDiS Offline
      SeDiS Offline
      SeDi
      wrote on last edited by SeDi
      #2

      Hi,
      docs state:

      In the delegate you have access to the following special properties:
      [...]
      styleData.column - the index of the column
      

      You can make this a condition and work with a Loader as delegate.

      Untested:

      delegate: Loader {
          sourceComponent: {
              if (styleData.column === 4) {
                  return value ===true ? buttonComponent : emptyComponent
              } else {
                  return normalComponent
              } 
          }
          Component {
              id: normalComponent
              Rectangle {
                  // ...
              }
          }
          Component {
              id: buttonComponent 
              Button{
                  // ...
              }
          }
          Component {
              id: emptyComponent
              Rectangle {
                  // ...(transparent, empty)
              }
          }
      }
      
      S 1 Reply Last reply
      1
      • SeDiS SeDi

        Hi,
        docs state:

        In the delegate you have access to the following special properties:
        [...]
        styleData.column - the index of the column
        

        You can make this a condition and work with a Loader as delegate.

        Untested:

        delegate: Loader {
            sourceComponent: {
                if (styleData.column === 4) {
                    return value ===true ? buttonComponent : emptyComponent
                } else {
                    return normalComponent
                } 
            }
            Component {
                id: normalComponent
                Rectangle {
                    // ...
                }
            }
            Component {
                id: buttonComponent 
                Button{
                    // ...
                }
            }
            Component {
                id: emptyComponent
                Rectangle {
                    // ...(transparent, empty)
                }
            }
        }
        
        S Offline
        S Offline
        SCP173
        wrote on last edited by
        #3

        @SeDi Thank you for the suggestion and sample code; the only issue that I have now when using it is getting an error that 'styleData' is undefined. I did some more research into that, and it seems to be something that is a part of TableView from Qt Quick Controls 1 which uses an itemDelegate instead of delegate, but I am using Quick Controls 2 in my case. Is using Quick Controls 1 the only way? I also saw this post:
        https://stackoverflow.com/a/22924554/16051674
        but it is not very clear to me how I could try to apply 'model' or modelData' in my code because I get an undefined error for both.

        Here is my new TableView component for anyone's reference:

        TableView {
        	    id: tableView
        	    anchors.fill: parent
        	    topMargin: columnsHeader.implicitHeight
        	    rowSpacing: 5
        	    columnWidthProvider: function (column) { return 50; }
        	    clip: true
        	    model: tableModel
        		
        		delegate: Loader {
        		    sourceComponent: {
        		        if (column === 2) {
        		        	console.log("Model value: " + modelData[row])
        		            return styleData.value ===true ? buttonComponent : emptyComponent
        		        } else {
        		            return normalComponent
        		        } 
        		    }
        		    Component {
        		        id: normalComponent
        		        Rectangle {
        		            implicitHeight: 40
        			        implicitWidth: 40
        			        color: "green"
        			
        			        Text {
        			            anchors.centerIn: parent
        			            text: display
        			            color: "#ffffff"
        			        }
        		        }
        		    }
        		    Component {
        		        id: buttonComponent 
        		        Button{
        		            implicitHeight: 10
        			        implicitWidth: 10
        			        
        			        background: Rectangle {
                        		color: "red"
                			}
        		        }
        		    }
        		    Component {
        		        id: emptyComponent
        		        Rectangle {
        		            implicitHeight: 40
        			        implicitWidth: 40
        			        color: "#000000"
        		        }
        		    }
        		}
        	}
        
        1 Reply Last reply
        0
        • IntruderExcluderI Offline
          IntruderExcluderI Offline
          IntruderExcluder
          wrote on last edited by
          #4

          If your model isn't a number, nor an JS Array, then you need to use model keyword, where model represents one of the element of your backend model. In this case you shold implement roleNames method, then you would be able to access data by given role, something like model.name;

          S 1 Reply Last reply
          1
          • IntruderExcluderI IntruderExcluder

            If your model isn't a number, nor an JS Array, then you need to use model keyword, where model represents one of the element of your backend model. In this case you shold implement roleNames method, then you would be able to access data by given role, something like model.name;

            S Offline
            S Offline
            SCP173
            wrote on last edited by SCP173
            #5

            @IntruderExcluder I see, thank you for the tip. It seems that I am able to just access the roles right now like the default 'display' without needing the 'model' keyword, but I guess once I modify the roles, I should probably add the 'model' keyword in.
            Your comment should also technically be part of the solution, but I can only mark 1 post as the answer, so I just want to note that here.

            1 Reply Last reply
            0

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved