In our previous article, we showed how to derive a new class from QRadioButton that keeps the index value of that button and keeps the click event callback right in the class itself.
In this simple article we are going to look at an easy program that fills an entry field with a text string based on which radio button you select. We also introduce you to the Mediator Design Pattern to help communication between widgets.
We came up with this little trick while writing a program to create a cast list for our operetta company. Leads play named roles, but for chorus member we just store their voice part. The radio buttons are labeled S, A, T and B, and when you click on one of them, the entry field is filled with the full name of that voice part: Soprano, Alto, Tenor and Bass. Since the entry field is editable, you can add more text like “2nd Tenor” where this might be useful, and that name is stored as part of the last list.
Using a Mediator
When you click on any of the RadioButtons, the long name is copied into the entry field. The question is, how does it get there? In effect, each of the four button instances need to communicate with the entry field. And, while that is not difficult to achieve, it doesn’t scale very well as your program grows to include more visual widgets. To simplify this problem, you use the Mediator Design Pattern and the Mediator class.
Rather than the various controls all sending information to each other, the Mediator class becomes the traffic cop that receives the various button clicks and other widget actions. Then, it knows about the other controls you might want to communicate with and passes the click information on to them. So, when we create instances of the derived vcRadioButton, you pass it the label it displays, the long name it is to send on to the entry field and a reference to the Mediator class:
You first create the Mediator and give it a reference to the entry field (which is called QLineEdit in PyQt5):
# create the entry field self.entry = QLineEdit() grid.addWidget(self.entry,0,0) # Create Mediator and # pass it the entry reference self.med = Mediator() self.med.setEntry(self.entry)
# Create a GroupBox for the four radio buttons self.voiceBox = QGroupBox("Voice part") vcGrid = QGridLayout()
Then you create the buttons and pass each of them a reference to the Mediator
# create the four radio buttons and labels
vs = VcRadioButton("S", "Soprano", self.med)
va = VcRadioButton("A", "Alto", self.med)
vt = VcRadioButton("T", "Tenor", self.med)
vb = VcRadioButton("B", "Bass", self.med)
# and add them to the 2 x 2 grid
vcGrid.addWidget(vs, 0, 0)
vcGrid.addWidget(va, 0, 1)
vcGrid.addWidget(vt, 1, 0)
vcGrid.addWidget(vb, 1, 1)
Then we add the vcGrid to the voiceBox layout, and the voiceBox to the outer grid.
self.voiceBox.setLayout(vcGrid) grid.addWidget(self.voiceBox, 1, 0) self.setLayout(grid)
The VcRadioButton class
Our actual VcRadioButton class is even simpler than the one we wrote for the six radio button example, because we don’t have to store anything in a class variable: we just tell the Mediator that the button has been clicked.
class VcRadioButton(QRadioButton): def __init__(self, label, title, med): super().__init__(label) self.title = title #save the title self.med = med # and copy the Mediator reference # connect the button clicks to the comd method self.toggled.connect(self.comd) # returns title stored in this class instance def getTitle(self): return self.title # gets the title and puts it in the character entry field # using the Mediator def comd(self): radio = self.sender() #get the button you clicked if radio.isChecked(): # if checked copy the title self.med.setVoice(radio.getTitle())
Note that the VcRadioButton connects the click event (called toggled in Qt5) to the comd method right there in the same class. And that comd method tells the Mediator to set the voice part into the entry field.
Then finally our Mediator is simple, since we are only mediating connections between radio buttons and the entry field. When the radio button is clicked, it calls the setVoice method in the Mediator, which copies the text into the entry field.
# The Mediator saves a reference to the entry field # and copies text into the field when setVoice is called class Mediator(): # save the entry field reference def setEntry(self, entry): self.entry = entry # copy the text into the entry field def setVoice(self, text): self.entry.clear() self.entry.insert(text)
This may seem like a lot of running around to copy text into an entry field, but the Mediator quickly becomes quite important when your program needs to handle interactions among a number of widgets, such as list boxes, push buttons and check boxes. It is probably the most useful and significant of the 23 Design Patterns when you are writing user interfaces.