Partage
  • Partager sur Facebook
  • Partager sur Twitter

[PYQT4] QTableWidget et QItemDelegate

Sujet résolu
    4 octobre 2012 à 12:42:04

    Bonjour,

    Les exemples de la lib PyQt4 donnent, entre autre, celui d'un QItemDelegate afin de mettre une QSpinBox dans un QTableWidget.

    class SpinBoxDelegate(QtGui.QItemDelegate):
        
        def createEditor(self, parent, option, index):
            editor = QtGui.QSpinBox(parent)
            editor.setMinimum(0)
            editor.setMaximum(100)
    
            return editor
    
        def setEditorData(self, spinBox, index):
            value = index.model().data(index, QtCore.Qt.EditRole)
    
            spinBox.setValue(value.toInt()[0])
    
        def setModelData(self, spinBox, model, index):
            spinBox.interpretText()
            value = spinBox.value()
    
            model.setData(index, value, QtCore.Qt.EditRole)
    
        def updateEditorGeometry(self, editor, option, index):
            editor.setGeometry(option.rect)
    


    Ma question est, comment peux-t'on modifier setMinimum et setMaximum de la QSpinBox une fois l'instance de la classe SpinBoxDelegate créée ?

    Merci d'avance.
    • Partager sur Facebook
    • Partager sur Twitter
    Anonyme
      4 octobre 2012 à 14:45:44

      Bonjour,

      Je n'ai pas de réponse pour une QItemDelegate et je ne sais pas à quoi il sert pour un QTableWidget.

      En effet, on peut placer un QSpinBox dans une case d'un QTableWidget et l'utiliser sans problème comme le montre le petit code suivant.

      Comme on place le QSpinBox dans une case avec self.tablewidget.setCellWidget(i,j,spinbox), on peut accéder à tous ses attributs avec cellWidget(i,j), par exemple: self.tablewidget.cellWidget(i,j).setMinimum(50).

      Cependant, si le spinbox a été instancié avec self, self.spinbox.setMinimim(50) fonctionne toujours et pointe sur le spinbox du QTableWidget.

      # Python 2.7
      
      import sys
      from PyQt4 import QtCore, QtGui
       
      #############################################################################
      class Fenetre(QtGui.QWidget):
          
          def __init__(self, parent=None):
              super(Fenetre, self).__init__(parent)
              
              self.resize(800,600)
              
              self.tableWidget = QtGui.QTableWidget(self)
              self.nbrow, self.nbcol = 7, 7
              self.tableWidget.setRowCount(self.nbrow)
              self.tableWidget.setColumnCount(self.nbcol)
              
              spinbox = QtGui.QSpinBox()
              self.tableWidget.setCellWidget(2, 3, spinbox)
              
              self.tableWidget.cellWidget(2, 3).setMinimum(50)
              self.tableWidget.cellWidget(2, 3).setMaximum(100)
              
              posit = QtGui.QGridLayout()
              posit.addWidget(self.tableWidget, 0, 0)
              self.setLayout(posit)
      
              self.tableWidget.setFocus()
              self.tableWidget.setCurrentCell(0, 0)
          
      #############################################################################
      if __name__ == "__main__":
          app = QtGui.QApplication(sys.argv)
          fen = Fenetre()
          fen.show()
          sys.exit(app.exec_())
      
      • Partager sur Facebook
      • Partager sur Twitter
        4 octobre 2012 à 17:29:52

        Bonjour,

        C'est une autre approche en effet. Beaucoup plus simple.

        Par contre, au niveau des connections les spinBox ne sont pas liées à la QTableWidget.

        Je veux dire, la modification d'un spinBox ne déclenchera pas :

        self.connect(self.tableWidget,QtCore.SIGNAL("cellChanged(int,int)"),self.eventTableWidget)
        
        • Partager sur Facebook
        • Partager sur Twitter
        Anonyme
          4 octobre 2012 à 20:29:31

          Puisque plusieurs signaux peuvent arriver au même slot, rien n'empêche de déclarer que chaque modification de valeur du spinbox enverra son signal à eventTableWidget.

          Il suffit d'ajouter au code précédent la ligne suivante:

          self.tableWidget.cellWidget(2, 3).valueChanged.connect(partial(self.eventTableWidget, *(2, 3)))
          


          On trouve partial dans le module functools (à importer) et il permet d'envoyer des données au slot, ici l'adresse du spinbox.

          Voilà mon code adapté:

          #!/usr/bin/python
          # -*- coding: utf-8 -*-
          from __future__ import division
          # Python 2.7
          
          import sys
          from functools import partial 
          from PyQt4 import QtCore, QtGui
           
          #############################################################################
          class Fenetre(QtGui.QWidget):
              
              # =======================================================================
              def __init__(self, parent=None):
                  super(Fenetre, self).__init__(parent)
                  
                  self.resize(800,600)
                  
                  self.tableWidget = QtGui.QTableWidget(self)
                  self.nbrow, self.nbcol = 7, 7
                  self.tableWidget.setRowCount(self.nbrow)
                  self.tableWidget.setColumnCount(self.nbcol)
                  
                  spinbox = QtGui.QSpinBox()
                  self.tableWidget.setCellWidget(2, 3, spinbox)
                  
                  self.tableWidget.cellWidget(2, 3).setMinimum(50)
                  self.tableWidget.cellWidget(2, 3).setMaximum(100)
                  
                  self.tableWidget.cellWidget(2, 3).valueChanged.connect(partial(self.eventTableWidget, *(2, 3)))             
                  self.tableWidget.cellChanged.connect(self.eventTableWidget)
                  
                  posit = QtGui.QGridLayout()
                  posit.addWidget(self.tableWidget, 0, 0)
                  self.setLayout(posit)
          
                  self.tableWidget.setFocus()
                  self.tableWidget.setCurrentCell(0, 0)
              
              # =======================================================================
              def eventTableWidget(self, i, j):
                  if isinstance(self.tableWidget.cellWidget(i,j), QtGui.QSpinBox):
                      valeur = self.tableWidget.cellWidget(i,j).value()
                      print "changement du spinbox adresse:", i, j, "valeur:", valeur
                  else:
                      print "changement de valeur de la case", i, j    
          
          #############################################################################
          if __name__ == "__main__":
              app = QtGui.QApplication(sys.argv)
              fen = Fenetre()
              fen.show()
              sys.exit(app.exec_())
          


          A chaque changement de valeur du spinbox, le signal est reçu par eventTableWidget, qui teste que le changement concerne un spinbox, et qui affiche:

          changement du spinbox adresse: 2 3 valeur: 51
          changement du spinbox adresse: 2 3 valeur: 52
          changement du spinbox adresse: 2 3 valeur: 53
          changement du spinbox adresse: 2 3 valeur: 54
          etc...

          Et désolé si je ne te réponds toujours pas sur le QItemDelegate...
          • Partager sur Facebook
          • Partager sur Twitter
          Anonyme
            6 octobre 2012 à 9:25:36

            Bonjour,

            Je viens de regarder l'exemple dont tu parles (spinboxdelegate.pyw), je m'aperçois qu'il utilise un QTableView avec un "model" et non un QTableWidget. Là, je comprends mieux le delegate: c'est indispensable pour éditer les cases dans ce cas, alors que ce n'est pas nécessaire avec un QTableWidget.

            Ma question: Ton problème est bien avec un QTableView ou un QTableWidget?

            En fonction de ta réponse, je peux creuser un peu.
            • Partager sur Facebook
            • Partager sur Twitter
            Anonyme
              6 octobre 2012 à 9:41:28

              Citation

              Ma question est, comment peux-t'on modifier setMinimum et setMaximum de la QSpinBox une fois l'instance de la classe SpinBoxDelegate créée ?



              J'ai pas testé, mais je pense qu'un simple rajout de valeurs à l'initialisation de ton instance doit suffire.

              Pas testé, mais l'idée est là

              class SpinBoxDelegate(QtGui.QItemDelegate):
                  
                  def __init__(self, mini, maxi):
                      QtGui.QItemDelegate.__init__(self)
                      self.mini = mini
                      self.maxi = maxi
              
                  def createEditor(self, parent, option, index):
                      editor = QtGui.QSpinBox(parent)
                      editor.setMinimum(self.mini)
                      editor.setMaximum(self.maxi)
              
                      return editor
              
                  def setEditorData(self, spinBox, index):
                      value = index.model().data(index, QtCore.Qt.EditRole)
              
                      spinBox.setValue(value.toInt()[0])
              
                  def setModelData(self, spinBox, model, index):
                      spinBox.interpretText()
                      value = spinBox.value()
              
                      model.setData(index, value, QtCore.Qt.EditRole)
              
                  def updateEditorGeometry(self, editor, option, index):
                      editor.setGeometry(option.rect)
              
              spin = SpinBoxDelegate(0, 100)
              spin.mini = 50
              spin.maxi = 75 # par exemple
              


              • Partager sur Facebook
              • Partager sur Twitter
                7 octobre 2012 à 17:18:13

                Bonjour,
                Merci pour vos réponses.

                Citation : tyrtamos

                Bonjour,
                Je viens de regarder l'exemple dont tu parles (spinboxdelegate.pyw), je m'aperçois qu'il utilise un QTableView avec un "model" et non un QTableWidget. Là, je comprends mieux le delegate: c'est indispensable pour éditer les cases dans ce cas, alors que ce n'est pas nécessaire avec un QTableWidget.
                Ma question: Ton problème est bien avec un QTableView ou un QTableWidget?
                En fonction de ta réponse, je peux creuser un peu.


                Alors, effectivement, sur spinboxdelegate.pyw c'est bien un QTableView.
                Dans mon cas c'est un QTableWidget. Le fait d'utiliser le delegate permet d'avoir un rendu plus "embarqué" que j'apprécie bien.

                Ta solution proposé plus haut marche bien et est plus... "accessible"


                Citation : fred1599

                Citation

                Ma question est, comment peux-t'on modifier setMinimum et setMaximum de la QSpinBox une fois l'instance de la classe SpinBoxDelegate créée ?



                J'ai pas testé, mais je pense qu'un simple rajout de valeurs à l'initialisation de ton instance doit suffire.

                Pas testé, mais l'idée est là



                Ta proposition marche bien, je l'ai testé.

                Mais les valeurs limites étaient que la partie visible de l'iceberg (l'iceberg étant mon problème).

                J'ai besoin d'approfondir le delegate afin de comprendre comment ça fonctionne exactement.

                Car là du coup, je n'ai plus accès à mon QSpinBox... A moins de réécrire toute la classe via le délégate peut-être.
                • Partager sur Facebook
                • Partager sur Twitter
                Anonyme
                  8 octobre 2012 à 6:59:58

                  Bonjour,

                  Comme j'étais intrigué par ton delegate sur un QTableWidget, j'en ai fait un!

                  Ça me semblait dommage qu'il n'y ait que des spinbox, aussi j'ai fait un delegate général qui ne fait des spinbox que sur la colonne 2. On voit bien que tout est possible ici, et qu'on pourrait mettre n'importe quel widget dans n'importe quelle case: des spinbox, des checkbox, des combobox, etc... et le mode "normal" dans toutes les autres cases.

                  Sur ces spinbox, j'ai introduit 2 particularités:
                  - en début d'édition, la case devient jaune. En fin d'édition, la case redevient à la couleur normale du QTableWidget (bleu)
                  - en début d'édition, on définit le maxi et le mini en fonction de 2 variables modifiables appartenant au delegate (self.mini, self.maxi)

                  Je récupère les frappes clavier dans un keyPressEvent afin de créer 3 raccourcis clavier:
                  - Alt-P ('P' pour 'Plus'): augmente de 10 les minis et maxis des spinbox
                  - Alt-M ('M' pour 'Moins'): diminue de 10 les minis et maxis des spinbox
                  - Alt-V ('V' pour 'Valeur'): affiche la valeur de n'importe quelle case hors édition

                  Voilà mon code d'essai: j'espère que ça t'aidera un peu.

                  #!/usr/bin/python
                  # -*- coding: utf-8 -*-
                  from __future__ import division
                  # Python 2.7
                  
                  import sys
                  from PyQt4 import QtCore, QtGui
                   
                  
                  #############################################################################
                  class MonDelegate(QtGui.QItemDelegate):
                      
                      #========================================================================
                      def __init__(self, parent=None):
                          super(MonDelegate, self).__init__(parent)
                          
                          self.mini = 100
                          self.maxi = 200
                  
                      #========================================================================
                      def createEditor(self, parent, option, index):
                          """crée les widgets utilisés pour l'édition"""
                          if index.column()==2:
                              editor = QtGui.QSpinBox(parent)
                              editor.setMinimum(self.mini)
                              editor.setMaximum(self.maxi)
                              return editor
                          else:
                              return QtGui.QItemDelegate.createEditor(self, parent, option, index)
                  
                      #========================================================================
                      def setEditorData(self, editor, index):
                          """exécuté lors de l'entrée en édition"""
                          if index.column()==2:
                              spinbox = editor
                              spinbox.setStyleSheet(u"background-color:yellow")
                              editor.setMinimum(self.mini)
                              editor.setMaximum(self.maxi)
                              value = index.model().data(index, QtCore.Qt.EditRole).toInt()[0]
                              if value<self.mini:
                                  value = self.mini
                              if value>self.maxi:
                                  value = self.maxi
                              spinbox.setValue(value)
                          else:
                              QtGui.QItemDelegate.setEditorData(self, editor, index)
                      
                      #========================================================================
                      def setModelData(self, editor, model, index):
                          """exécuté lors de la sortie d'édition (conservé ici pour mémoire)"""
                          QtGui.QItemDelegate.setModelData(self, editor, model, index)
                          
                  #############################################################################
                  class Fenetre(QtGui.QWidget):
                      
                      # =======================================================================
                      def __init__(self, parent=None):
                          super(Fenetre, self).__init__(parent)
                          
                          self.resize(800,600)
                          
                          self.tableWidget = QtGui.QTableWidget(self)
                          self.nbrow, self.nbcol = 7, 7
                          self.tableWidget.setRowCount(self.nbrow)
                          self.tableWidget.setColumnCount(self.nbcol)
                          
                          posit = QtGui.QGridLayout()
                          posit.addWidget(self.tableWidget, 0, 0)
                          self.setLayout(posit)
                  
                          # branchement du delegate
                          self.mondelegate = MonDelegate()
                          self.tableWidget.setItemDelegate(self.mondelegate)
                      
                          self.tableWidget.setFocus()
                          self.tableWidget.setCurrentCell(0, 0)
                          
                      # =======================================================================
                      def keyPressEvent(self, event):
                  
                          if self.tableWidget.hasFocus():
                  
                              #--- Alt-P: augmente les mini-maxi des spinbox de 10 ------------
                              if event.key() == QtCore.Qt.Key_P and \
                                                     (event.modifiers() & QtCore.Qt.AltModifier):
                                  self.tableWidget.itemDelegate().mini += 10
                                  self.tableWidget.itemDelegate().maxi += 10
                                  event.accept()
                  
                              #--- Alt-M: diminue les mini-maxi des spinbox de 10 -------------
                              elif event.key() == QtCore.Qt.Key_M and \
                                                     (event.modifiers() & QtCore.Qt.AltModifier):
                                  self.tableWidget.itemDelegate().mini -= 10
                                  self.tableWidget.itemDelegate().maxi -= 10
                                  event.accept()
                  
                              #--- Alt-V: affiche la valeur du spinbox courant hors édition ---
                              elif event.key() == QtCore.Qt.Key_V and \
                                                     (event.modifiers() & QtCore.Qt.AltModifier):
                                  row = self.tableWidget.currentRow()
                                  col = self.tableWidget.currentColumn()
                                  item = self.tableWidget.item(row, col)
                                  if item == None:
                                      print u"==>", u""
                                  else:
                                      print u"==>", unicode(item.text()).strip()
                  
                                  event.accept()
                  
                              else:
                                  event.ignore()
                          
                          else:
                              event.ignore()
                                      
                  #############################################################################
                  if __name__ == "__main__":
                      app = QtGui.QApplication(sys.argv)
                      fen = Fenetre()
                      fen.show()
                      sys.exit(app.exec_())
                  
                  • Partager sur Facebook
                  • Partager sur Twitter
                    8 octobre 2012 à 22:01:40

                    Bonjour,

                    C'est cool.

                    Je me permets de te poser quelques petites questions :
                    pourquoi
                    return QtGui.QItemDelegate.createEditor(self, parent, option, index)
                    
                    est nécessaire dans le createEditor ?

                    Dans le setEditorData, peux-t'on avoir accès à la cellule de la QTableWidget pour par exemple la colorier en jaune également ?

                    Dans l'affichage de la valeur du spinBox, pourquoi fait-on appelle à item.text() au lieu d'un .value() ?

                    Pourquoi dois t'on s'assurer que item == None:

                    Merci ;)
                    • Partager sur Facebook
                    • Partager sur Twitter
                    Anonyme
                      9 octobre 2012 à 6:55:12

                      Bonjour,

                      return QtGui.QItemDelegate.createEditor(self, parent, option, index)
                      


                      Cette ligne vient du fait que j'ai crée un delegate "général" qui permet de placer n'importe quel widget à n'importe quel endroit. J'ai choisi un spinbox sur la colonne 2 (index.column()==2), mais j'aurai pu ajouter un combobox aux coordonnées: index.row()==3 and index.column()==4. Cependant, dans les cases "normales", je laisse passer l'édition normale d'une case QTableWidget sans delegate grâce à cette ligne.

                      Citation : ced73

                      Dans le setEditorData, peux-t'on avoir accès à la cellule de la QTableWidget pour par exemple la colorier en jaune également ?



                      Je suppose que tu souhaiterais mettre la colonne des spinbox en jaune, même hors édition. Mais dans setEditorData ça ne marche pas puisqu'il n'est exécuté qu'en cas d'édition. Par contre, tu peux colorier les cases du QTableWidget hors du delegate. Je te donne la fonction que j'utilise pour ça:

                      def setcouleur(table, row, col, couleur=u"white"):
                          """met la couleur au fond de la case [row, col]"""
                          if isinstance(couleur, (str, unicode)):
                              coul = QtGui.QColor(couleur)
                          elif isinstance(couleur, (list, tuple)):
                              if len(couleur) == 3:
                                  r, g, b = couleur
                                  t = 255
                              else:
                                  r, g, b, t = couleur
                              coul = QtGui.QColor(r, g, b, t)
                          item = table.item(row, col)
                          if item == None:
                              # la case n'a jamais été initialisée
                              item = QtGui.QTableWidgetItem()
                              item.setBackgroundColor(coul)
                              table.setItem(row, col, item)
                          else:
                              # la case a déjà été initialisée
                              item.setBackgroundColor(coul)
                      


                      Utilisation: on met la colonne 2 en vert:

                      for row in xrange(0, self.tableWidget.rowCount()):
                                  setcouleur(self.tableWidget, row, 2, couleur=(0,255,0))
                      


                      La fonction "setcouleur" est intéressante, parce qu'elle supporte les couleurs sous forme de chaine (u"green") et sous forme rgb (tuple (0,255,0)).

                      Une remarque importante: quand on crée un QTableWidget, les cases sont complètement vides, c'est à dire que self.tableWidget.item(row, col) renvoie None: Dans ce cas, il faut commencer par mettre "quelque chose", en occurrence un QtGui.QTableWidgetItem(), pour mettre la couleur!

                      Le fait de mettre ces 2 couleurs montre d'ailleurs la différence entre le mode "hors édition" (ici case verte) et le mode "édition" (ici case jaune). On n'est dans un spinbox que dans le mode édition (sous delegate, case jaune). Une fois l'édition terminée, le résultat du spinbox est reporté dans un QtGui.QTableWidgetItem(), case verte. C'est pour ça qu'on récupère le résultat par un item.text().

                      D'ailleurs, tu pourrais initialiser toutes les cases dès le départ, ce qui te permettrait d'afficher des valeurs dès le départ. Voici les fonctions que j'utilise pour ça:



                      #############################################################################
                      def setcase(table, row, col, val):
                          """met la valeur val dans la case [row, col] de la table (QTableWidget)"""
                          item = table.item(row, col)
                          if item == None:
                              # la case n'a jamais été initialisée
                              item = QtGui.QTableWidgetItem()
                              table.setItem(row, col, item)
                          item.setText(val)
                          
                      #############################################################################
                      def getcase(table, row, col):
                          """Retourne la valeur de la case [row, col] de la table"""
                          item = table.item(row, col)
                          if item == None:
                              return u""
                          else:
                              return unicode(item.text())
                      


                      L'initialisation de toutes les cases se ferait ainsi:

                      for row in xrange(0, self.tableWidget.rowCount()):
                                  for col in xrange(0, self.tableWidget.columnCount()):
                                      setcase(self.tableWidget, row, col, u"")
                      


                      Et tu pourrais initialiser la colonne 2 (celle avec les spinbox sous delegate) avec la valeur 100, tout en mettant les cases en vert:

                      for row in xrange(0, self.tableWidget.rowCount()):
                                  col = 2
                                  setcouleur(self.tableWidget, row, col, couleur=(0,255,0))
                                  setcase(self.tableWidget, row, col, unicode(100))
                      


                      Ok?
                      • Partager sur Facebook
                      • Partager sur Twitter
                        9 octobre 2012 à 23:18:05

                        Merci tyrtamos, je vais travailler sur ton dernier exemple
                        • Partager sur Facebook
                        • Partager sur Twitter

                        [PYQT4] QTableWidget et QItemDelegate

                        × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                        × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                        • Editeur
                        • Markdown