Search code examples
pythonpyqtqwizard

PyQt obtaining collection of all registered fields in QWizard


I am working on a simple QWizard that displays some radio buttons on its pages. The buttons on a given page are all part of the same QButtonGroup. The page is registered as a custom field in itself, based on the selection in the button group:

class Page1(QWizardPage):
     selectionChanged = pyqtSignal('QString')

     def __init__(self, name):
         self.group = QButtonGroup()
         self.group.addButton(QRadioButton("a"))
         self.group.addButton(QRadioButton("b"))
         self.group.addButton(QRadioButton("c"))
         self.registerField(name, self, 'selection', self.selectionChanged)

    @pyqtProperty('QString')
    def selection(self):
        checkedButton = self.group.checkedButton()
        return checkedButton.text() if checkedButton else None

    def nextId(self): return -1

I end up registering self as the widget containing the field property simply because QButtonGroup is not a QWidget. All of the other pages look pretty much exactly like this (I am actually using base class to do all the common work, and this is just a minimal example).

I would like to be able to get a list of all the registered fields in the QWizard. I have not found any methods provided by Qt to allow me to do this so I made a workaround by overriding the behavior of each page's registerField method as well as the wizard's addPage:

def registerField(self, name, *args, **kwargs):
    self.field_names.add(name)
    if self.wizard() is not None:
        self.wizard().field_names.add(name)
    super().registerField(name, *args, **kwargs)

def addPage(self, page, *args, **kwargs):
    self.field_names.union(page.field_names)
    return super().addPage(page, *args, **kwargs)

I can then use the field_set attribute of the parent QWizard combined with QWizard.field to access all the values. This seems a bit redundant and therefore unnecessary. Is there a method in Qt to access the complete collection of fields? The relevant section in the documentation does not mention anything, but there are a lot of other details it omits, so that's not very telling.

My assumption is that the functionality, if it exists, would be the same for PyQt4 as for PyQt5. If it is not, I would prefer an answer for PyQt5 since that is what I am using at the moment.


Solution

  • You said that if the answer is negative it would have to be "pretty convincing." You admit that the documentation contains no mention of the function you want, and I will point out that no such function appears in the list of public functions for QWizard. Therefore the desired function, if it exists at all, is undocumented. To me, that consideration alone would be a "pretty convincing" reason not to use it. The next release of Qt might not have that function, or it might not work the same way.

    Meanwhile you have an acceptable solution with eight lines of straightforward python code. Given the choice between that and calling an undocumented function (if you can find it), the python solution is vastly superior in all practical respects.

    There is a potential problem with your Python code, however. You override the function QWizard.addPage, but there is another function QWizard.removePage that should probably be overridden as well. An alternative approach, which I would prefer, is not to store the field_names in QWizard at all but only in the individual pages. Add a method to QWizard to dynamically build a set of all the current field_names:

    def all_field_names(self):
        return {s for page_id in self.pageIds() for s in self.page(page_id).field_names}
    

    [I didn't have a good way of testing this function, but I think you get the idea.] Now you remove the overridden method QWizard.addPage, remove the variable field_names from QWizard, and remove the middle two lines of register_field. Now you have only five lines of Python code, which will work regardless of how pages are added or removed. And you no longer store the field names in two places.

    For what it's worth, whenever I'm confronted with a choice between using Qt's functionality or basic Python functionality, I always lean toward Python. I use threads instead of QThreads, threading locks and timers instead of Qt equivalents, Python method objects and callbacks instead of custom Slots and Signals. Qt was written for C++ programmers and that often means that it's not as "pythonic" as I would like.