Search code examples
pyqtwizardqcombobox

PyQT QWizard - registerField on QComboBox's userData rather that text or index?


I'm creating a QWizard & want a combobox that represents files. The combobox will display the file name, but I need the entire path, so I'm storing the path in the combobox's userData. In order to access the combobox data in another wizard page, I use registerField. A combobox doesn't have a currentData method like it has currentIndex or currentText, so I created my own combobox that has a currentData method (in my case, I will always be returning a QString, so I called it currentStringData). When I pass this new function name as the "property" parameter to registerField, I get no results.

Googling around, I came to someone else's question on QtCentre - they had the same question, but it's in C++, and I'm having a hard time converting from the C++ example to python. http://www.qtcentre.org/threads/13858-QWizard-QComboBox-and-registerField()-issue. I believe I'm missing an emit from my derived class, but I'm not certain if that's the case or how to do it.

Anyone know how to do this in Python?

I've created a very simple python script that demonstrates my problem.

#!/usr/bin/env python

from PyQt4 import QtCore,QtGui

class QIComboBox(QtGui.QComboBox):
    def __init__(self,parent=None):
        super(QIComboBox, self).__init__(parent)

    def currentStringData(self):
        return self.itemData(self.currentIndex()).toString()


class VariantWizard(QtGui.QWizard):
    def __init__(self, parent=None):
        super(VariantWizard, self).__init__(parent)

        self.addPage(Page1())
        self.addPage(Page2())

        self.setWindowTitle("QVariant Test")
        self.resize(640,480)

class Page1(QtGui.QWizardPage):
    def __init__(self, parent=None):
        super(Page1, self).__init__(parent)

        self.version_combo = QIComboBox(self)
        self.version_combo.addItem("foo","/path/to/foo")
        self.version_combo.addItem("bar","/path/to/bar")

        self.registerField("version",self.version_combo,"currentStringData")

        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.version_combo)
        self.setLayout(layout)

class Page2(QtGui.QWizardPage):
    def __init__(self, parent=None):
        super(Page2, self).__init__(parent)

        path = self.field("version")
        label1 = QtGui.QLabel("raw path is '%s'" % path)
        label2 = QtGui.QLabel("string path is '%s'" % path.toString())

        layout = QtGui.QVBoxLayout()
        layout.addWidget(label1)
        layout.addWidget(label2)
        self.setLayout(layout)

if __name__ == '__main__':
    import sys
    app = QtGui.QApplication(sys.argv)
    wizard = VariantWizard()
    wizard.show()
    sys.exit(app.exec_())

In this example, I would expect the first label on the second page to be either "raw path is '/path/to/foo'" or "raw path is '/path/to/bar'", depending on which combobox item was selected on Page1. I would expect the second label to be an error, as the currentStringData should already be returning a QString.

Instead, I'm getting "raw path is '<PyQt5.QtCore.QVariant object....>'" and "string path is ''".


Solution

  • You are missing a couple of important details here. First, if you are not going to read a widget existing property via field() then you must define your own property (currentItemData in this case). You can do it via @pyqtProperty decorator as explained here. In your case the new property should be defined in the QIComboBox class. Second, when a page is about to be shown you have to call its (reimplemented) initializePage() method which will initialize the page contents based on other pages fields (you can see an example here).

    A working version of your code follows:

    #!/usr/bin/env python
    
    from PyQt4 import QtCore
    from PyQt4 import QtGui
    from PyQt4.QtCore import pyqtProperty
    
    class QIComboBox(QtGui.QComboBox):
        def __init__(self,parent=None):
            super(QIComboBox, self).__init__(parent)
    
        @pyqtProperty(str)
        def currentItemData(self):
            return self.itemData(self.currentIndex()).toString()
    
    class VariantWizard(QtGui.QWizard):
        def __init__(self, parent=None):
            super(VariantWizard, self).__init__(parent)
            self.addPage(Page1(self))
            self.addPage(Page2(self))
            self.setWindowTitle("QVariant Test")
            self.resize(640,480)
    
    class Page1(QtGui.QWizardPage):
        def __init__(self, parent=None):
            super(Page1, self).__init__(parent)
            self.version_combo = QIComboBox(self)
            self.version_combo.addItem("filename1","/path/to/filename1")
            self.version_combo.addItem("filename2","/path/to/filename2")
            layout = QtGui.QVBoxLayout()
            layout.addWidget(self.version_combo)
            self.setLayout(layout)
    
            self.registerField("version",self.version_combo, "currentItemData")
    
    class Page2(QtGui.QWizardPage):
        def __init__(self, parent=None):
            super(Page2, self).__init__(parent)
            self.label1 = QtGui.QLabel()
            self.label2 = QtGui.QLabel()
            layout = QtGui.QVBoxLayout()
            layout.addWidget(self.label1)
            layout.addWidget(self.label2)
            self.setLayout(layout)
        
        def initializePage(self):
            path = self.field("version")
            self.label1.setText("raw path is '%s'" % path.toString())
            self.label2.setText("string path is '%s'" % path)
            
    if __name__ == '__main__':
        import sys
        app = QtGui.QApplication(sys.argv)
        wizard = VariantWizard()
        wizard.show()
        sys.exit(app.exec_())