Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. How to link custom widgets to model data
Forum Updated to NodeBB v4.3 + New Features

How to link custom widgets to model data

Scheduled Pinned Locked Moved Solved Qt for Python
19 Posts 3 Posters 3.9k Views 2 Watching
  • 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.
  • superagaS superaga

    Hi @SGaist,

    thanks for your answer! I updated my widget class accordingly.
    ... But I discovered that I have at least another issue first.

    Here where I am:

    • I made a simple Model/View example that plots in a QTableView the data:

    customTable.gif

    • I created a custom widget LED:

    customLEDs.gif

    I then redesigned my view in order to "remap" the table cells to my widgets.
    To start easy I just added few QLabel widgets.

    Here my view.py

    class MyView(QWidget):
        def __init__(
            self,
            model_table: QAbstractTableModel,
            parent: QObject = None,
        ) -> None:
            super().__init__(parent=parent)
    
            # create layout
            self.initUI(model_table=model_table)
    
        def initUI(self, model_table: QAbstractTableModel) -> None:
            # make the GUI
            vbox = QVBoxLayout(self)
            # make widgets
            self.form_layout = QFormLayout()
            self.lbl_txt_1 = QLabel("Cell 1 value:")
            self.lbl_txt_2 = QLabel("Cell 2 value:")
            self.lbl_txt_3 = QLabel("Cell 3 value:")
            self.lbl_val_1 = QLabel()
            self.lbl_val_2 = QLabel()
            self.lbl_val_3 = QLabel()
            # add widgets to layout
            self.form_layout.setWidget(0, QFormLayout.ItemRole.LabelRole, self.lbl_txt_1)
            self.form_layout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.lbl_val_1)
            self.form_layout.setWidget(1, QFormLayout.ItemRole.LabelRole, self.lbl_txt_2)
            self.form_layout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.lbl_val_2)
            self.form_layout.setWidget(2, QFormLayout.ItemRole.LabelRole, self.lbl_txt_3)
            self.form_layout.setWidget(2, QFormLayout.ItemRole.FieldRole, self.lbl_val_3)
            vbox.addLayout(self.form_layout)
            # add the mapper
            self.mapper = QDataWidgetMapper()
            self.mapper.setModel(model_table)
            self.mapper.addMapping(self.lbl_val_1, 1)
            self.mapper.addMapping(self.lbl_val_2, 2)
            self.mapper.addMapping(self.lbl_val_3, 3)
            self.mapper.toFirst()
    

    But I've got just this "empty" dialog which doesn't show my data values:

    11461a7b-5d68-4f0d-8013-1938fa0bc9d1-image.png

    I then tried to use both the QLineEdit and QSpinBox as in the documentation example, but I still have the very same behavior:

    customWidget.gif

    • What I'm still missing?
    • One more: How my mapped value is passed to the widget?
      • I think through the Q_PROPERTY, correct?
        • I'm wondering then: ... what if I want/need to cast (string/float) my data before it would arrive at the widget?
        • It might be this the reason why my label were empties?, But what about then the QSpinBox which uses instead int rather than string?

    So I still have these open questions before move to use my custom widget...

    Many thanks!

    superagaS Offline
    superagaS Offline
    superaga
    wrote on last edited by
    #4

    Hi experts!

    anyone that can try to help me to understand what I'm missing?

    Many thanks!

    JonBJ 1 Reply Last reply
    0
    • superagaS superaga

      Hi experts!

      anyone that can try to help me to understand what I'm missing?

      Many thanks!

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by
      #5

      @superaga
      The behaviour you show --- empty/defualt value mapped widgets --- would be the case if there is no data in your model. Your code shows no evidence there is any data, or suitable data in the specified columns.

      superagaS 1 Reply Last reply
      0
      • JonBJ JonB

        @superaga
        The behaviour you show --- empty/defualt value mapped widgets --- would be the case if there is no data in your model. Your code shows no evidence there is any data, or suitable data in the specified columns.

        superagaS Offline
        superagaS Offline
        superaga
        wrote on last edited by superaga
        #6

        Hi @JonB,

        The model contains the same (random generated) data that I showed in the first message.
        I just updated the view to contain both the current QFormLayout and the QTableView of the "standard version", to provide the evidence that the Model/View architecture is working as expected.

        customWidgetTableview.gif

        The updated view code basically adds the table which is linked to the same model as well.

        ...
        ...
        self.form_layout.setWidget(2, QFormLayout.ItemRole.LabelRole, self.lbl_txt_3)
        self.form_layout.setWidget(2, QFormLayout.ItemRole.FieldRole, self.lbl_val_3)
        v_splitter = QSplitter(QtCore.Qt.Orientation.Vertical)
        self.widget = QWidget()
        self.widget.setLayout(self.form_layout)
        v_splitter.addWidget(self.widget)
        # insert table
        self.table_view = QTableView()
        v_splitter.addWidget(self.table_view)
        vbox.addWidget(v_splitter)
        # add the mapper
        self.mapper = QDataWidgetMapper()
        # connect dictionaries to views
        self.mapper.setModel(model_table)
        self.table_view.setModel(model_table)
        self.mapper.addMapping(self.lbl_val_1, 1)
        ...
        ...
        

        Where could be the error?

        I still have no clear how can I manipulated the data type of the model data before passing it to the widget.

        Many thanks!

        JonBJ 1 Reply Last reply
        0
        • superagaS superaga

          Hi @JonB,

          The model contains the same (random generated) data that I showed in the first message.
          I just updated the view to contain both the current QFormLayout and the QTableView of the "standard version", to provide the evidence that the Model/View architecture is working as expected.

          customWidgetTableview.gif

          The updated view code basically adds the table which is linked to the same model as well.

          ...
          ...
          self.form_layout.setWidget(2, QFormLayout.ItemRole.LabelRole, self.lbl_txt_3)
          self.form_layout.setWidget(2, QFormLayout.ItemRole.FieldRole, self.lbl_val_3)
          v_splitter = QSplitter(QtCore.Qt.Orientation.Vertical)
          self.widget = QWidget()
          self.widget.setLayout(self.form_layout)
          v_splitter.addWidget(self.widget)
          # insert table
          self.table_view = QTableView()
          v_splitter.addWidget(self.table_view)
          vbox.addWidget(v_splitter)
          # add the mapper
          self.mapper = QDataWidgetMapper()
          # connect dictionaries to views
          self.mapper.setModel(model_table)
          self.table_view.setModel(model_table)
          self.mapper.addMapping(self.lbl_val_1, 1)
          ...
          ...
          

          Where could be the error?

          I still have no clear how can I manipulated the data type of the model data before passing it to the widget.

          Many thanks!

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by
          #7

          @superaga
          I don't know, I never had any trouble getting a QDataWidgetMapper to work. Could you start by:

          • Stop all the values changing. Get it working first with values in the table which do not change all the time; and
          • Use the widget which is suitable for your values. Since they look like all floating point QSpinBox is not right, use a QDoubleSpinBox.
          superagaS 1 Reply Last reply
          0
          • JonBJ JonB

            @superaga
            I don't know, I never had any trouble getting a QDataWidgetMapper to work. Could you start by:

            • Stop all the values changing. Get it working first with values in the table which do not change all the time; and
            • Use the widget which is suitable for your values. Since they look like all floating point QSpinBox is not right, use a QDoubleSpinBox.
            superagaS Offline
            superagaS Offline
            superaga
            wrote on last edited by
            #8

            Hi @JonB,

            I update the model and the view accordingly, but it is still not working.

            staticWidgetTable.png

            Now model just contains these values:

            ...
            self._data = [[0,1,2,3], [3,2,1,0]]
            self._rows = len(self._data)
            self._columns = len(self._data[0])
            ...
            ...
            # reimplement the model methods
            def rowCount(self, parent: QModelIndex) -> int:
                if parent.isValid():
                    return 0
                return self._rows
            
            def columnCount(self, parent: QModelIndex) -> int:
                if parent.isValid():
                    return 0
                return self._columns
            
            def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any:
                if role != Qt.ItemDataRole.DisplayRole:
                    return QtCore.QVariant()
                # in all other cases return data
                return str(self._data[index.row()][index.column()])
            ...
            

            Data are now just integers, so using QSpinBox or QDoubleSpinBox didn't make any difference.

            Are there other tests that I can perform?

            Thanks!

            SGaistS JonBJ 2 Replies Last reply
            0
            • superagaS superaga

              Hi @JonB,

              I update the model and the view accordingly, but it is still not working.

              staticWidgetTable.png

              Now model just contains these values:

              ...
              self._data = [[0,1,2,3], [3,2,1,0]]
              self._rows = len(self._data)
              self._columns = len(self._data[0])
              ...
              ...
              # reimplement the model methods
              def rowCount(self, parent: QModelIndex) -> int:
                  if parent.isValid():
                      return 0
                  return self._rows
              
              def columnCount(self, parent: QModelIndex) -> int:
                  if parent.isValid():
                      return 0
                  return self._columns
              
              def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any:
                  if role != Qt.ItemDataRole.DisplayRole:
                      return QtCore.QVariant()
                  # in all other cases return data
                  return str(self._data[index.row()][index.column()])
              ...
              

              Data are now just integers, so using QSpinBox or QDoubleSpinBox didn't make any difference.

              Are there other tests that I can perform?

              Thanks!

              SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #9

              @superaga your model returns nothing except for DisplayRole (that comment is highly misleading). QDataWidgetMapper uses Qt.EditRole to get the value in the editor.

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              1 Reply Last reply
              1
              • superagaS superaga

                Hi @JonB,

                I update the model and the view accordingly, but it is still not working.

                staticWidgetTable.png

                Now model just contains these values:

                ...
                self._data = [[0,1,2,3], [3,2,1,0]]
                self._rows = len(self._data)
                self._columns = len(self._data[0])
                ...
                ...
                # reimplement the model methods
                def rowCount(self, parent: QModelIndex) -> int:
                    if parent.isValid():
                        return 0
                    return self._rows
                
                def columnCount(self, parent: QModelIndex) -> int:
                    if parent.isValid():
                        return 0
                    return self._columns
                
                def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any:
                    if role != Qt.ItemDataRole.DisplayRole:
                        return QtCore.QVariant()
                    # in all other cases return data
                    return str(self._data[index.row()][index.column()])
                ...
                

                Data are now just integers, so using QSpinBox or QDoubleSpinBox didn't make any difference.

                Are there other tests that I can perform?

                Thanks!

                JonBJ Offline
                JonBJ Offline
                JonB
                wrote on last edited by
                #10

                @superaga
                @SGaist is of course correct now that you show your model code. That's why we needed to see that from the start! :)

                1 Reply Last reply
                0
                • superagaS Offline
                  superagaS Offline
                  superaga
                  wrote on last edited by
                  #11

                  Hi @JonB and @SGaist,

                  😱
                  That's a very bad mistake! I'm so sorry... 😔

                  I just fixed the model data method to be:

                  def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any:
                      if not index.isValid():
                          return None
                      if role == Qt.ItemDataRole.DisplayRole:
                          return str(self._data[index.row()][index.column()])
                  

                  Now it is correct... But it is still not working (I still have the very same picture of my previous message).

                  Why the QTableView shows the values instead?

                  I read again the ItemDataRole description and looks good now.

                  I tried to understand where the issue is but I couldn't find any wrong.

                  JonBJ 1 Reply Last reply
                  0
                  • superagaS superaga

                    Hi @JonB and @SGaist,

                    😱
                    That's a very bad mistake! I'm so sorry... 😔

                    I just fixed the model data method to be:

                    def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any:
                        if not index.isValid():
                            return None
                        if role == Qt.ItemDataRole.DisplayRole:
                            return str(self._data[index.row()][index.column()])
                    

                    Now it is correct... But it is still not working (I still have the very same picture of my previous message).

                    Why the QTableView shows the values instead?

                    I read again the ItemDataRole description and looks good now.

                    I tried to understand where the issue is but I couldn't find any wrong.

                    JonBJ Offline
                    JonBJ Offline
                    JonB
                    wrote on last edited by JonB
                    #12

                    @superaga
                    I don't see how what you have is right or that you have heeded what @SGaist said. Your method still returns nothing for the edit role? The usual code would be:

                        if role == Qt.ItemDataRole.DisplayRole || role == Qt.ItemDataRole.EditRole:
                            return str(self._data[index.row()][index.column()])
                    

                    It's also not good to return str(...) from data() when the underlying data has a perfectly reasonable type of its own, such as a number. Forcing to str will stop all kinds of things working correctly, e.g. sorting. It may also stop a QDoubleSpinBox working.

                    superagaS 1 Reply Last reply
                    1
                    • JonBJ JonB

                      @superaga
                      I don't see how what you have is right or that you have heeded what @SGaist said. Your method still returns nothing for the edit role? The usual code would be:

                          if role == Qt.ItemDataRole.DisplayRole || role == Qt.ItemDataRole.EditRole:
                              return str(self._data[index.row()][index.column()])
                      

                      It's also not good to return str(...) from data() when the underlying data has a perfectly reasonable type of its own, such as a number. Forcing to str will stop all kinds of things working correctly, e.g. sorting. It may also stop a QDoubleSpinBox working.

                      superagaS Offline
                      superagaS Offline
                      superaga
                      wrote on last edited by superaga
                      #13

                      Hi @JonB,

                      Ok, now it works.

                      I understood that: I didn't have a clear picture about the ItemDataRole since I "deliberately overlooked" the EditRole.
                      I was convinced that to show read only data the DisplayRole would have been enough, but I was wrong. Could you please suggest me a link where this is well explained?
                      I thought that EditRole was necessary only to let the data be editable.

                      Regarding the data casting on the data method return I completely agree: this is completely wrong and must not be done.

                      One more (if I can) could you also explain me (or point me to) how could I cast data (i.e. from float to str)? I still miss this "hidden" step between the model data return and the mapper fetch.

                      staticWidgetTable_2.png

                      Super thanks!

                      JonBJ 1 Reply Last reply
                      0
                      • superagaS superaga

                        Hi @JonB,

                        Ok, now it works.

                        I understood that: I didn't have a clear picture about the ItemDataRole since I "deliberately overlooked" the EditRole.
                        I was convinced that to show read only data the DisplayRole would have been enough, but I was wrong. Could you please suggest me a link where this is well explained?
                        I thought that EditRole was necessary only to let the data be editable.

                        Regarding the data casting on the data method return I completely agree: this is completely wrong and must not be done.

                        One more (if I can) could you also explain me (or point me to) how could I cast data (i.e. from float to str)? I still miss this "hidden" step between the model data return and the mapper fetch.

                        staticWidgetTable_2.png

                        Super thanks!

                        JonBJ Offline
                        JonBJ Offline
                        JonB
                        wrote on last edited by JonB
                        #14

                        @superaga
                        QDataWidgetMapper expects to allow editing I think, and doesn't particularly know/care that your model might be read-only. If @SGaist says it always uses EditRole then that's what it does.

                        I have never used a QDataWidgetMapper with a QLabel, so not yet sure where the str() needs doing. If you temporarily go back to data() doing the str() (for the edit case, and for the column for Cell 1 value at least) does that then show the value on the label? I would test that first, else it's maybe a QLabel rather than a str issue?

                        superagaS 1 Reply Last reply
                        0
                        • JonBJ JonB

                          @superaga
                          QDataWidgetMapper expects to allow editing I think, and doesn't particularly know/care that your model might be read-only. If @SGaist says it always uses EditRole then that's what it does.

                          I have never used a QDataWidgetMapper with a QLabel, so not yet sure where the str() needs doing. If you temporarily go back to data() doing the str() (for the edit case, and for the column for Cell 1 value at least) does that then show the value on the label? I would test that first, else it's maybe a QLabel rather than a str issue?

                          superagaS Offline
                          superagaS Offline
                          superaga
                          wrote on last edited by
                          #15

                          Hi @JonB, and @SGaist,

                          I somehow missed that QDataWidgetMapper needs the EditRole, I apology.

                          I modified the data method for the model as you described but the QLabel is still empty. I assume than that, for some reasons, the QLabel is not suited to be used within the QDataWidgetMapper...
                          I'll try now to progress in the view to replace the standard widget with mine.

                          Many thanks to both of you

                          JonBJ 1 Reply Last reply
                          0
                          • superagaS superaga

                            Hi @JonB, and @SGaist,

                            I somehow missed that QDataWidgetMapper needs the EditRole, I apology.

                            I modified the data method for the model as you described but the QLabel is still empty. I assume than that, for some reasons, the QLabel is not suited to be used within the QDataWidgetMapper...
                            I'll try now to progress in the view to replace the standard widget with mine.

                            Many thanks to both of you

                            JonBJ Offline
                            JonBJ Offline
                            JonB
                            wrote on last edited by JonB
                            #16

                            @superaga said in How to link custom widgets to model data:

                            I modified the data method for the model as you described but the QLabel is still empty. I assume than that, for some reasons, the QLabel is not suited to be used within the QDataWidgetMapper...

                            That would suggest QDataWidgetMapper is only interested in providing editing facilities and therefore utilizing an "editing" control. So it's not set up [by default] for working with a QLabel. Doubtless it can be done....

                            Ah, yes, now this is all to do with that "user" property you were asking about :) A straightforward example showing how easy it is to change for you is QDataWidgetMapper not working with QLabels. You just need to change your addMapping() lines to write their text correctly. And after that you might understand how the property stuff works, if it's relevant to your widget....

                            superagaS 1 Reply Last reply
                            0
                            • SGaistS Offline
                              SGaistS Offline
                              SGaist
                              Lifetime Qt Champion
                              wrote on last edited by
                              #17

                              Since python does not enforce types, I would wrap all returned values in QVariant since that's the type used in C++ and especially when setting properties like QDataWidgetMapper.

                              Interested in AI ? www.idiap.ch
                              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                              1 Reply Last reply
                              0
                              • JonBJ JonB

                                @superaga said in How to link custom widgets to model data:

                                I modified the data method for the model as you described but the QLabel is still empty. I assume than that, for some reasons, the QLabel is not suited to be used within the QDataWidgetMapper...

                                That would suggest QDataWidgetMapper is only interested in providing editing facilities and therefore utilizing an "editing" control. So it's not set up [by default] for working with a QLabel. Doubtless it can be done....

                                Ah, yes, now this is all to do with that "user" property you were asking about :) A straightforward example showing how easy it is to change for you is QDataWidgetMapper not working with QLabels. You just need to change your addMapping() lines to write their text correctly. And after that you might understand how the property stuff works, if it's relevant to your widget....

                                superagaS Offline
                                superagaS Offline
                                superaga
                                wrote on last edited by superaga
                                #18

                                Hi @JonB,

                                @JonB said in How to link custom widgets to model data:

                                That would suggest QDataWidgetMapper is only interested in providing editing facilities and therefore utilizing an "editing" control. So it's not set up [by default] for working with a QLabel.

                                That's makes a lot of sense now...
                                ... And yes, using the addMapping method as from your linked page worked as expected...

                                staticWidgetTable_3.png

                                Here the code that solved also this point:

                                ...
                                ...
                                self.mapper.addMapping(self.lbl_val_1, 1, b'text')
                                self.mapper.addMapping(self.lbl_val_2, 2)
                                ...
                                ...
                                

                                I also updated as @SGaist told me my custom Led class (I'm using PyQt not PySide) as:

                                ...
                                ...
                                @pyqtProperty(str)
                                def value(self):
                                    return self._value
                                
                                @value.setter
                                def value(self, value):
                                    self._value = value
                                    
                                    if self._value == 1:
                                        self.on()
                                    else:
                                        self.off()
                                ...
                                ...
                                

                                But didn't work. I spend basically yesterday afternoon trying to understand what could cause the issue and I think that despite I made a mistake, I think that I found a "bug".

                                To make the long story short the type of the Q_PROPERTY in my decorator and the data type were different.

                                At the begin I (wrongly) casted my returned data method (within the model) to be str:

                                return str(self._data[index.row()][index.column()])
                                

                                Then I removed the str() cast, so data were back to their original float form, but my decorator was still set to receive string: @pyqtProperty(str).

                                Running this never returned any runtime error or warning, but didn't work.
                                Of course, since we are talking about Python (which is also dynamically typed) I'm not sure if define this behavior wrong (here the double quotes around the bug word).

                                Changing the decorator type to int solved the issue.

                                mvc_custom_widget.gif

                                I really wanted to attach my working code in order to provide help to anyone, but looks that I can't (Error: You don't have enough privileges for this action).

                                Please let me know how to fix this.
                                Thanks!

                                Kind reagrds,
                                AGA

                                superagaS 1 Reply Last reply
                                1
                                • superagaS superaga has marked this topic as solved on
                                • superagaS superaga

                                  Hi @JonB,

                                  @JonB said in How to link custom widgets to model data:

                                  That would suggest QDataWidgetMapper is only interested in providing editing facilities and therefore utilizing an "editing" control. So it's not set up [by default] for working with a QLabel.

                                  That's makes a lot of sense now...
                                  ... And yes, using the addMapping method as from your linked page worked as expected...

                                  staticWidgetTable_3.png

                                  Here the code that solved also this point:

                                  ...
                                  ...
                                  self.mapper.addMapping(self.lbl_val_1, 1, b'text')
                                  self.mapper.addMapping(self.lbl_val_2, 2)
                                  ...
                                  ...
                                  

                                  I also updated as @SGaist told me my custom Led class (I'm using PyQt not PySide) as:

                                  ...
                                  ...
                                  @pyqtProperty(str)
                                  def value(self):
                                      return self._value
                                  
                                  @value.setter
                                  def value(self, value):
                                      self._value = value
                                      
                                      if self._value == 1:
                                          self.on()
                                      else:
                                          self.off()
                                  ...
                                  ...
                                  

                                  But didn't work. I spend basically yesterday afternoon trying to understand what could cause the issue and I think that despite I made a mistake, I think that I found a "bug".

                                  To make the long story short the type of the Q_PROPERTY in my decorator and the data type were different.

                                  At the begin I (wrongly) casted my returned data method (within the model) to be str:

                                  return str(self._data[index.row()][index.column()])
                                  

                                  Then I removed the str() cast, so data were back to their original float form, but my decorator was still set to receive string: @pyqtProperty(str).

                                  Running this never returned any runtime error or warning, but didn't work.
                                  Of course, since we are talking about Python (which is also dynamically typed) I'm not sure if define this behavior wrong (here the double quotes around the bug word).

                                  Changing the decorator type to int solved the issue.

                                  mvc_custom_widget.gif

                                  I really wanted to attach my working code in order to provide help to anyone, but looks that I can't (Error: You don't have enough privileges for this action).

                                  Please let me know how to fix this.
                                  Thanks!

                                  Kind reagrds,
                                  AGA

                                  superagaS Offline
                                  superagaS Offline
                                  superaga
                                  wrote on last edited by
                                  #19

                                  It is not possible attach files, so here the code (from an external hosting):
                                  https://mega.nz/file/FDIThArB#wf8nzrveUSlaj76gx_Qmtu7yv_1K4Ivj_BRfkdIB6mw

                                  I hope it helps!
                                  AGA

                                  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