The example in this recipe shows how we can define our own variations of the Tkinter objects to generate custom controls and dynamically construct a menu with them. We will also take a quick look at using threads, to allow other tasks to continue to function while a particular task is being executed.
Getting ready
How to do it…
To create a graphical Start menu application, create the following graphicmenu.py script: #!/usr/bin/python3
# graphicmenu.py import tkinter as tk
from subprocess import call import threading
#Define applications ["Display name","command"] leafpad = ["Leafpad","leafpad"]
scratch = ["Scratch","scratch"] pistore = ["Pi Store","pistore"] app_list = [leafpad,scratch,pistore] APP_NAME = 0 APP_CMD = 1 class runApplictionThread(threading.Thread): def __init__(self,app_cmd): threading.Thread.__init__(self) self.cmd = app_cmd def run(self):
#Run the command, if valid try:
call(self.cmd) except:
print ("Unable to run: %s" % self.cmd) class appButtons:
def __init__(self,gui,app_index): #Add the buttons to window
btn = tk.Button(gui, text=app_list[app_index][APP_NAME], width=30, command=self.startApp)
btn.pack()
self.app_cmd=app_list[app_index][APP_CMD] def startApp(self):
print ("APP_CMD: %s" % self.app_cmd)
root = tk.Tk()
root.title("App Menu")
prompt = ' Select an application '
label1 = tk.Label(root, text=prompt, width=len(prompt), bg='green') label1.pack()
#Create menu buttons from app_list for index, app in enumerate(app_list): appButtons(root,index)
#Run the tk window root.mainloop() #End
The previous code produces the following application:
The App Menu GUI
How it works…
We create the Tkinter window as we did before; however, instead of defining all the items separately, we create a special class for the application buttons.
The class we create acts as a blueprint or specification of what we want the appButtons
items to include. Each item will consist of a string value for app_cmd, a function called startApp(), and an __init__() function. The __init__() function is a special function (called a constructor) that is called when we create an appButtons item; it will allow us to create any setup that is required.
In this case, the __init__() function allows us to create a new Tkinter button with the text to be set to an item in app_list and the command to be called in the startApp() function when the button is clicked. The keyword self is used so that the command called will be the one that is part of the item; this means that each button will call a locally defined function that has access to the local data of the item.
We set the value of self.app_cmd to the command from app_list and make it ready for use by the startApp() function. We now create the startApp() function. If we run the application command here directly, the Tkinter window will freeze until the application we have opened is closed again. To avoid this, we can use the Python Threading module, which allows us to perform multiple actions at the same time.
The runApplicationThread() class is created using the threading.Thread class as a template—this inherits all the features of the threading.Thread class into a new class. Just like our previous class, we provide an __init__() function for this as well. We first call the __init__() function of the inherited class to ensure it is set up correctly, and then we store the app_cmd value in self.cmd. After the runApplicationThread() function has been created and initialized, the start() function is called. This function is part of threading.Thread, which our class can use. When the start() function is called, it will create a separate application thread (that is, simulate running two things at the same time), allowing Tkinter to continue monitoring button clicks while executing the run() function within the class.
Therefore, we can place the code in the run() function to run the required application (using call(self.cmd)).
There's more…
One aspect that makes Python particularly powerful is that it supports the programming techniques used in Object Orientated Design (OOD). This is commonly used by modern programming languages to help translate the tasks we want our program to perform into meaningful constructs and structures in code. The principle of OOD lies in the fact that we think of most problems consisting of several objects (a GUI window, a button, and so on) that interact with each other to produce a desired result.
In the previous section, we found that we can use classes to create standardized objects that can be reused multiple times. We created an appButton class, which generated an object with all the features of the class, including its own personal version of app_cmd which will be used by the function startApp(). Another object of the appButton type will have its own unrelated [app_CMD] data that its startApp() function will use.
You can see that classes are useful to keep together a collection of related variables and functions in a single object, and the class will hold its own data in one place. Having multiple objects of the same type (class), each with their own functions, and data inside them results in better program structure. The traditional approach would be to keep all the information in one place and send each item back and forth for various functions to process; however, this may become cumbersome in large systems.
The following diagram shows the organization of related functions and data: Data recordJohn addToRecord(name,age) addToRecord(name,age) Function John 34 Kirsty 32 Class Objects Record recordJohn recordKirsty name age addToRecord() name=John age=34 name=Kirsty age=32 recordKirsty
Related functions and data can be organized into classes and objects
So far, we have used Python modules to separate parts of our programs into different files; this allows us to conceptually separate different parts of the program (an interface, encoder/ decoder, or library of classes, such as Tkinter). Modules can provide code to control a particular bit of hardware, define an interface for the Internet, or provide a library of common functionality; however, its most important function is to control the interface (the collection of functions, variables, and classes that are available when the item is imported). A well- implemented module should have a clear interface that is centered around how it is used rather than how it is implemented. This allows you to create multiple modules that can be swapped and changed easily since they share the same interface. In our previous example, imagine how easy it would be to change the encryptdecrypt module for another one just by supporting encryptText(input_text,key). Complex functionality can be split into smaller, manageable blocks that can be reused in multiple applications.
Python makes use of classes and modules all the time. Each time you import a library, such as sys or Tkinter, or convert a value using value.str() and iterate through a list using for...in, you can use them without worrying about the details. You don't have to use classes or modules in every bit of code you write, but they are useful tools to keep in your programmer's toolbox for times when they fit what you are doing.
We will understand how classes and modules allow us to produce well-structured code that is easier to test and maintain by using them in the examples of this book.