Intro — What We’re Doing

Today, we’ll start looking at widgets that populate windows (and frames) and what better way to start than with a button or two so we’ve got something to click on besides the window’s close gadget. We’ll also look at:

  • callbacks,
  • rolling our own super-class Buttons, and
  • deriving child classes from that super-class.

A Button with a Callback

Results of this example:
Current example output
Current example output
Terminal image
Current example terminal output (click for enlarged view)


This demo will show—not just a button, but a button with a callback. Because… why create a button that does nothing?

Here’s what we’ve added to the simple code from previous OOP demos:

class HelloButton(ttk.Button):
	def __init__(self, frame):
		super().__init__(frame)
		# object attributes
		self.text = "Say Hello"
		self.message = "Hello, tkinter World!"
		# configure
		self.grid()
		self.config(text = self.text, command = self.say_hello)

	def say_hello(self):
		print(self.message)
	

The first thing you’ll notice about this demo is that it won’t win any awards for design. It does, however, illustrate the basics… plop down a button, hook up a callback, and show a result. (Check the terminal/Command Prompt/shell/what-have-you to see output.)

Breakdown

A new class named HelloButton. It could also have been called simply Button, but it’s specialized to do that ‘Hello, World’ thing we’ve all come to love when exploring a new language or GUI toolkit. Here’s how that specialization breaks down:

  • self.text: text to be slapped onto the button face,
  • self.message: text to be dumped to the terminal when the callback is triggered, and
  • self.config(): configure the button using the previous variables.

Callback

Like all Python callbacks, the first (and in this case, only) argument must be self-referential. I won’t attempt to explain why and it doesn’t matter anyway. It’s just the way things are done in Python.

The only statement here is straightforward: dump some text to the terminal. And this is why we used the self. prefix when we set up the object attribute in the initialization method. If we hadn’t, we’d have to pass the message attribute as an argument. We’ll look at doing that down the road, but for now, we’re keeping it dead simple.

So, that’s what a simple Button looks like, but most of the time, we’ll need more than a simple Button.

Buttons of a Feather

Results of this example:
Current example output
Current example output
Terminal image
Current example terminal output (click for enlarged view)


Buttons all have a few things in common, so much so that we can set up our own super-class Button to configure these things, then override them when we have to in the derived Buttons.

Button Super-class

Things all Buttons have in common:

  • a label (might be text, might be an image, but for now, let’s stick with text),
  • an action to be carried out when the user clicks it, and
  • GUI placement.

And here’s the code for our super-class:

class Button(ttk.Button):
	def __init__(self, parent):
		super().__init__(parent)
		# object attributes
		self.text = "*******"
		self.message = "no message"
		# configure
		self.config(command = self.do_something)
		self.grid()

	def do_something(self):
		print(self.message)

The object attributes are rather generic as is the do_something() method, but they’re fine for a demo. They define:

  • the placeholder button name as a row of asterisks (in case I forget to name one of the derived buttons I write later, this should stand out enough to catch my eye), and
  • the placeholder message as being no message at all.

As for do_something(), all it does is spit the message to the terminal.

Derived Classes

First, another HelloButton:

class HelloButton(Button):
	def __init__(self, parent):
		super().__init__(parent)
		# object attributes
		self.text = "Say Hello"
		self.message = "Hello, tkinter World!"
		# configure
		self.config(text = self.text)

Here we do an override on self.text (the Button label) as well as self.message.

The other thing we do—and we didn’t do this in the super-class—is make a call to self.config() to set the Button’s label text.

In fact, it doesn’t even make sense to make this call in a super-class. If we did do it there instead of in the derived class, all our buttons would have the generic label ******* despite the fact that we change this attribute for each derived class. Since that’s the way things work in Python, we might as well just make the call here in the derived class and be done with it.

Second, we have a GoodbyeButton. The only thing of note here is that we don’t override the value of self.message and so clicking the GoodbyeButton spits out the generic message defined in our Button super-class.

Conclusion

And that’s all for today. Buttons, callbacks, and derivation of classes. Good stuff to keep in mind when building UIs.

Next time, we’ll dig into sizing a Window in a couple of different ways, one of which can be overridden by sizing demands made by child widgets.

Until then, keep your windows clean and stay safe.


Sponsorship


Comments? Questions? Observations?

Did we miss a tidbit of information that would make this post even more informative? Let's talk about it in the comments.

Thank you very much for dropping by!

© Copyright 2021 Ronald V. Tarrant