In the beginning, all hell breaks loose, but we will soon fix this apparent mess.
Note that in Eclipse, the PyDev editor hints at coding problems by highlighting them in red on the right-hand side portion of the code editor.
Maybe we should not code in OOP after all, but this is what we do, and for very good reasons:
We just have to prepend all the variables with the self keyword and also bind the functions to the class by using self, which officially and technically turns the functions into methods.
Let's prefix everything with self to get rid of the red so we can run our code again:
Once we do this for all of the errors highlighted in red, we can run our Python code again. The click_Me function is now bound to the class and has officially become a method.
Unfortunately, starting in a procedural way and then translating it into OOP is not as simple as I stated earlier. The code has become a huge mess. This is a very good reason to start programming in Python using the OOP model of coding.
We are translating our procedural code into object-oriented code. Looking at all the troubles we got ourselves into, translating only 200+ lines of Python code into OOP could suggest that we might as well start coding in OOP from the beginning.
We actually did break some of our previously working functionality. Using Tab 2 and clicking the radio buttons no longer works. We have to refactor more.
The procedural code was easy in the sense that it was simply top to bottom coding. Now that we have placed our code into a class, we have to move all the callback functions into methods. This works, but does take some work to translate our original code:
########################################
# Our procedural code looked like this:
########################################
# Button Click Function
def click_me():
action.configure(text='Hello ' + name.get() + ' ' +
number_chosen.get())
# Adding a Textbox Entry widget
name = tk.StringVar()
name_entered = ttk.Entry(mighty, width=12, textvariable=name)
name_entered.grid(column=0, row=1, sticky='W')
# Adding a Button
action = ttk.Button(mighty, text="Click Me!", command=click_me)
action.grid(column=2, row=1)
ttk.Label(mighty, text="Choose a number:").grid(column=1, row=0)
number = tk.StringVar()
number_chosen = ttk.Combobox(mighty, width=12,
textvariable=number, state='readonly')
number_chosen['values'] = (1, 2, 4, 42, 100)
number_chosen.grid(column=1, row=1)
number_chosen.current(0)
********************************************
The new OOP code looks like this:
********************************************
class OOP():
def __init__(self): # Initializer method
# Create instance
self.win = tk.Tk()
# Add a title
self.win.title("Python GUI")
self.create_widgets()
# Button callback
def click_me(self):
self.action.configure(text='Hello ' + self.name.get() + ' '
+self.number_chosen.get())
# ... more callback methods
def create_widgets(self):
# Create Tab Control
tabControl = ttk.Notebook(self.win)
tab1 = ttk.Frame(tabControl) # Create a tab
tabControl.add(tab1, text='Tab 1') # Add the tab
tab2 = ttk.Frame(tabControl) # Create second tab
tabControl.add(tab2, text='Tab 2') # Add second tab
# Pack to make visible
tabControl.pack(expand=1, fill="both")
# Adding a Textbox Entry widget - using self
self.name = tk.StringVar()
name_entered = ttk.Entry(mighty, width=12,
textvariable=self.name)
name_entered.grid(column=0, row=1, sticky='W')
# Adding a Button - using self
self.action = ttk.Button(mighty, text="Click Me!",
command=self.click_me)
self.action.grid(column=2, row=1)
# ...
#======================
# Start GUI
#======================
oop = OOP() # create an instance of the class
# use instance variable to call mainloop via win
oop.win.mainloop()
We moved the callback methods to the top of the module, inside the new OOP class. We moved all the widget-creation code into one rather long method, which we call in the initializer of the class.
Technically, deep underneath the hood of the low-level code, Python does have a constructor, yet Python frees us from any worries about this. It is taken care of for us.
Instead, in addition to a real constructor, Python provides us with an initializer.
We are strongly encouraged to use this initializer. We can use it to pass in parameters to our class, initializing variables we wish to use inside our class instance.
Unlike Java, which has a very rigid naming convention (without which it does not work), Python is much more flexible.
Python truly rocks!
Once our Python GUI gets large, we will break some classes out into their own modules but, unlike Java, we do not have to. In this book and project, we will keep some classes in the same module while, at the same time, we will break out some other classes into their own modules, importing them into what can be considered as a main() function (this is not C, but we can think C-like because Python is very flexible).
What we have achieved so far is adding the ToolTip class to our Python module and refactoring our procedural Python code into OOP Python code.
Here, in this recipe, we can see that more than one class can live in the same Python module.
Cool stuff, indeed!
Both the ToolTip class and the OOP class reside within the same Python module: