No. 2
In our previous article, we wrote a simple program with two buttons, OK and Quit. Quit is disabled and is enabled when you click on OK.
In this article, we’ll redesign the code for that program to use an object-oriented approach. For such a simple program, this approach is a bit longer, but it is clearer and easier to extend.
As you can see from the above diagram the buttons are different sizes and crammed into a vertical space, when they should be horizontal.
We’ll start with the button size. We can create a DButton class that has a fixed size of 6 characters and derived classes from that for OK and Quit buttons. Since we are going to enable and disable at least one of the buttons, why not put the enable and disable methods right there in the button, to replace the awkward tkinter syntax?
# derived button
# with simplified enable and disable methods
class DButton(Button):
def __init__(self, master=None, **kwargs):
super().__init__(master, width=6,
command= self.comd, **kwargs)
def enable(self):
self['state'] = NORMAL
def disable(self):
self['state'] = DISABLED
def comd(self): pass
Note that we set the width to 6 and wrapped those awkward functions in enable and disable methods. But we also define a comd method that the button calls when it is clicked. And what does this one do? Nothing yet. We call this an abstract class because this method isn’t implement. Buttons we derive from DButton can have comd methods that do something without our making them part of each button’s super()__init__ method.
The Mediator class
Now an important tenet of OO programming is that objects shouldn’t do each others’ jobs. One button enables another button, but it shouldn’t have to know about other buttons. The other button causes the program to exit, but the button itself shouldn’t have to know how to exit from the program.
Instead, they pass that responsibility to a Mediator class that knows about the buttons and knows about actions they should take. So, when we create our OK and Quit buttons, we derive them from DButton, and have their comd method tell the Mediator class that they have been clicked:
# Button displays OK
class OKButton(DButton):
def __init__(self, master, med, **kwargs):
super().__init__(master, text="OK", **kwargs)
self.med = med
def comd(self):
self.med.OKClicked()
# Button displays Quit
class QuitButton(DButton):
def __init__(self, master,med, **kwargs):
super().__init__(master, text="Quit", **kwargs)
self.med = med
def comd(self):
self.med.quitClicked()
Note that a reference to that Mediator class is passed to each button when we create the button. Here is the whole Mediator class that handles this:
# Mediator handles button interactions
class Mediator():
# save buttons
def setButtons(self, okbut, quitbut):
self.qbutton = quitbut
self.okbut = okbut
def OKClicked(self): # Ok button clicked
self.qbutton.enable()
def quitClicked(self): # Quit button clicked
sys.exit()
This may seem a bit involved for such a simple program, but imagine if there are more buttons, menus and radio buttons. Processing all those clicks is best left to a specialist: the Mediator rather than scattering those event all through the program. This also makes changes and additions simpler.
Creating the Buttons
Now we create the mediator and the buttons all at once. The way we make the buttons look better on the screen is to set the side variable to LEFT or RIGHT to put them side by side and then add a little spacing with padx=10 so they aren’t pressed against the window edges. Here we create the Mediatot class, create the buttons and then pass the Mediator references to both buttons. (In this simple program the Mediator on uses the Quit button, but we include the OK button for future use.)
self.med = Mediator() # create the Mediator
# create the OK button
self.okBut = OKButton(root, self.med)
self.okBut.pack(side=LEFT, padx=10)
# create the Quit button
self.quitBut = QuitButton(root, self.med )
self.quitBut.pack(side=RIGHT, padx=10)
self.quitBut.disable()
# tell the Mediator about the buttons
self.med.setButtons(self.okBut, self.quitBut)
While this program is a bit longer than our first program. This one is a lot easier to extend than the original, and easier to understand as well. And this one looks a lot better, too!
All of the code in this article is available on GitHub under jameswcooper/newsletter