본문 바로가기
James Cooper - Python Programming with D

Visual Programming in Python

by 자동매매 2023. 3. 23.

2
Visual Programming in Python
You can make very nice visual interfaces using the tkinter toolkit provided
with Python. It gives you tools for creating windows, buttons, radio buttons,
check boxes, entry fields, list boxes, combo boxes and a number of other
useful visual widgets.
To use the tkinter library, you must tell your program to import those tools:
import tkinter as tk
from tkinter import *
Then you set up the window like this:
Click here to view code image

set up the window

root = tk.Tk() # get the window
You create a Hello button with:
Click here to view code image

create Hello button

slogan = Button(root,
text="Hello",
command=disp_slogan)
Then you put in the layout:
slogan.pack()
The command argument refers to the function write_slogan that puts up a
message box:
Click here to view code image

write slogan out in a message box

def disp_slogan():
messagebox.showinfo("our message",
"tkinter is easy to use")
Then, our other button is labeled Quit. You create it in the same way as the
Hello button:
Click here to view code image

create exit button with red letters

button = Button(root,
text="QUIT",
fg="red",
command=quit)
button.pack()
The command argument in this button just calls Python’s built-in quit
function. Note that when you place a function name in a command
argument, you omit the parentheses; otherwise, it would be called at once.
The quit function was added for ease of learning Python, but it does not
always exit cleanly. You should use the call sys.exit instead.
The resulting display and message box for Hellobuttons.py are shown in
Figure 2-1.
Figure 2-1 Two buttons and a message box window
The pack layout function can make this window much better looking if we
make the window a bit bigger:
Click here to view code image
root.geometry("100x100+300+300") # x, y window

size and position

To further enhance the window, you can put one button on the left and one
on the right, and add 10 pixels of padding between the buttons:
Click here to view code image
slogan.pack(side=LEFT, padx=10)
button.pack(side=RIGHT, padx=10)
Then the window looks like Figure 2-2.
Figure 2-2 Two buttons side by side using the pack layout
Importing Fewer Names
The import statement imports all the names in the tkinter library.
from tkinter import *
However, your program might need only a few of these names. This could
lead to you trying to create a variable with the same name as some other
tkinter object. The import statement also loads your development
environment with all the functions in the tkinter library. Understandably,
you might want to only import the ones you plan to use:
Click here to view code image
from tkinter import Button, messagebox, LEFT, RIGHT
If you are using PyCharm, it can help you do this. Remove the import *
statement and PyCharm will highlight the names it doesn’t recognize. Click
each of these names and PyCharm will suggest the name to import. This is
very fast, because usually you only have to do this three or four times to
import all the underlined names.
Creating an Object-Oriented Version
The idea of creating these two buttons, one of which calls an external
function, seems inelegant and can be confusing. Instead, it would be better
if the function the button click calls were part of the Button class. We do
this in Derived2Buttons.py below.
In order to do this, we need to derive a new Button class that has the
command method inside it. The easiest way to do this is to create a DButton
class that inherits all the Button behaviors but also has a comd method.
Click here to view code image
#derived class from Button that contains empty comd function
class DButton(Button):
def init (self, root, *kwargs):
super(). init (root, kwargs)
super().config(command=self.comd)
#abstract method to be called by children
def comd(self):
pass
The comd method is empty, but we will then derive the OK and Quit button
classes from it. The pass keyword just means “Go on, but do nothing.”
Note, however that we pass
command=self.comd
on to the parent class. This is essentially an abstract method because it
doesn’t do anything, but the derived classes will fill it in. So, you can
consider DButton as an abstract class.
The other thing that might be confusing is that the init method
contains a reference to *
kwargs. This is syntax borrowed from C and
means a pointer to an array of name-value pairs, in dictionary form. That
array of strings contains all the configuration arguments you can pass to the
parent Button class. You use this exact same syntax in creating derived
classes of any of the tkinter widgets.
Now, the actual OKButton class is derived from DButton and fills out the
actual command method:
Click here to view code image
#derived from DButton with actual OK comd
class OKButton(DButton):
def init (self, root):
super(). init (root, text="OK")
def comd(self):
messagebox.showinfo("our message",
"tkinter is easy to use")
What happens here is rather cool. The base Button class knows to call a
function called comd, but that function is only filled in in the derived
OKButton class; and it is this code that is called, even though OKButton has
not told the parent class about it. The parent class’s abstract comd method is
replaced by the actual comd method in the OKButton class.
In a similar fashion, we can create the QuitButton class, also derived from
DButton, with its own different comd method:
Click here to view code image
#derived from DButton calls Quit function
class QuitButton(DButton):
def init (self, root):
#also sets Quit to Red
super(). init (root, text= "Quit" , fg= "red" )
#calls the quit function and the program exits
def comd(self):
quit()
This simplifies setting up the UI because most of the work is now done
within the derived classes:
Click here to view code image
def buildUI():
root = tk.Tk() # get the window
root.geometry("100x100+300+300") # x, y window

create Hello button

slogan = OKButton(root)
slogan.pack(side=LEFT, padx=10)

create exit button with red letters

button = QuitButton(root)
button.pack(side=RIGHT, padx=10)

start running the tkinter loop

root.mainloop()
Depending on your philosophical preferences, you can also move the two
pack method calls into the derived classes and out of the buildUI function.
Click here to view code image
class OKButton(DButton):
def init (self, root):
super(). init (root, text="OK")
self.pack(side=LEFT, padx= 10 )
Using Message Boxes
There are several calls to the messagebox object that produce slightly
different displays. The functions showwarning and showerror display
distinctive icons of greater severity, as shown in Figure 2-3.
Click here to view code image
messagebox.showwarning("Warning", "file not found")
messagebox.showerror("Error", "Division by zero")
Figure 2-3 Warning and error message boxes
You can also ask questions with askquestion, askyesnocancel,
askretrycancel, askyesno, and askokcancel. These functions return a subset
of True, False, None, OK, Yes, or No, as shown in Figure 2-4.
Click here to view code image
result = messagebox.askokcancel("Continue", "Go on?")
result= messagebox.askyesnocancel("Really", "Want to go on?")
Figure 2-4 Message box askokcancel and askyesnocancel
All of these message boxes and the file dialogs that follow are illustrated in
Messageboxes.py.
Using File Dialogs
If you want to open a file or files within your program, you can use
filedialog.
Click here to view code image
from tkinter import filedialog

open a single file

fname =filedialog.askopenfilename()
print(fname)

select several files -returns a tuple

fnames =
filedialog.askopenfilenames(
defaultextension= "*.py" )
print(fnames)
The first dialog returns either the complete path to the file you select in the
dialog or an empty string if you click Cancel. The second dialog returns a
tuple of the files you select or an empty tuple. The analogous
asksaveasfile opens a Save As dialog.
Understanding Options for the Pack Layout
Manager
Although the pack layout is somewhat limiting, there are an awful lot of
common problems that you can solve really nicely using pack() and a
number of layout options:
fill=X
fill=Y
Stretch widget to fill the frame in the X direction, Y direction, or
both.
fill=B
OTH
side=L
EFT
side=R
IGHT
Position widget at left or right side of frame.
expand
=1
Distribute remaining space in the frame among all widgets with a
nonzero value for expand.
anchorWhere the widget is placed within the packing box. Options are
CENTER (default), N, S, E, W, or contiguous combinations such as
NE.
padX=5
,pady=
5
Add that number of pixels of border space.
The following simple example in packoptions.py illustrates how to use
these layout options. Each of the two rows of widgets is set into its own
frame.
Here is the first row:
Click here to view code image
frame1 = Frame() # first row
frame1.pack(fill=X) # fill all of X
lbl1 = Label(frame1, text= "Name" , width= 7 ) # add a label
lbl1.pack(side=LEFT, padx= 5 , pady= 5 ) # on left
entry1 = Entry(frame1) # add aentry
entry1.pack(fill=X, padx= 5 , expand= True )
and here is the second row:
frame2 = Frame() #second row
frame2.pack(fill=X)
lbl2 = Label(frame2, text= "Address" , width= 7 ) #label
lbl2.pack(side=LEFT, padx= 5 , pady= 5 )
entry2 = Entry(frame2) # and entry
entry2.pack(fill=X, padx= 5 , expand= True )
Figure 2-5 shows the resulting window.
Figure 2-5 Pack layout using expand=TRUE, X filling, and padX=5
Using the ttk Libraries
The tkinter libraries interface directly to the underlying tk windowing
toolkit that has been ported to most platforms. More recently, the tkinter.ttk
toolkit has been added that gives somewhat better-looking widgets in some
cases and has separated the graphics from the logical functions. The ttk
toolkit includes rewritten code for Button, Checkbutton, Entry, Frame,
Label, LabelFrame, Menubutton, PanedWindow, Radiobutton, Scale, and
Scrollbar.
In addition, the ttk toolkit includes the additional widgets Combobox,
Notebook, Progressbar, Separator, Sizegrip, and Treeview.
To use the older tkinter set, add this code to the top of your program:
Click here to view code image
import tkinter as tk
from tkinter import Button, messagebox, LEFT, RIGHT
To use the newer set of widgets, add this code instead:
Click here to view code image
import tkinter as tk
from tkinter import messagebox, LEFT, RIGHT
from tkinter.ttk import Button
This replaces the original tk widgets with the equivalent ttk widgets. If you
want to use Combobox or Treeview, it is worth switching to the ttk toolkit.
(We also found that the earlier pack example aligns slightly better using the
ttk kit.)
Unfortunately, there are a couple of coding changes you have to make to
use the ttk library. Most significant is that the fg or foreground (and bg or
background) options are no longer part of the calling arguments to construct
a new Label or Button class. Instead, you create an entry in the style table.
Click here to view code image
Style().configure("W.TButton", foreground="red")
super(). init (root, text="Quit", style="W.TButton")
The name of the style is not random: For buttons, the suffix must be
TButton; for labels, it must be TLabel. However, you can prefix the name
with anything before the period.
Responding to User Input
In the introductory material in Chapter 31, “Variables and Syntax in
Python,” we use the input statement to get strings that you enter at the
console. You can do much the same thing by using the Entry field in the
tkinter GUI. This section shows those examples in simple tkinter windows.
Our first example was one where you enter your name and it says Hello
back, giving homage to Heinlein in the process. The tkinter program does
the same thing (see Figure 2-6).
Figure 2-6 Hello name example
The code for this program, Yourname.py, creates the two labels, the Entry
field, and the OK button:
Click here to view code image
def build(self):
root = tk.Tk()

top label

Label(root,
text= """What is your name?""" ,
justify=LEFT, fg= 'blue' , pady= 10 , padx= 20 ).pack()

create entry field

self.nmEntry = Entry(root)
self.nmEntry.pack()

OK button calls getName when clicked

self.okButton = Button(root, text= "OK" , command=self.getName )
self.okButton.pack()

This is the label whose text changes

self.cLabel = Label(root, text= 'name' , fg= 'blue' )
self.cLabel.pack()
mainloop()
The OK button calls the getName method, which fetches the text from the
entry field and inserts it into the bottom label text.
Click here to view code image

gets the entry field text

places it on the cLabel text field

def getName(self):
newName = self.nmEntry.get()
self.cLabel.configure(text= "Hi " +newName+ " boy!" )
Adding Two Numbers
Our second example, Simplemath.py, rewritten from the example in
Chapter 31, reads two Entry fields, converts them to floats, and puts the
sum in the lower label (see Figure 2-7).
Figure 2-7 Adding two numbers
The code for this window is much the same, except that you fetch and add
two numbers. Here is the function the OK button calls:
Click here to view code image
xval= float(self.xEntry.get())
yval = float(self.yEntry.get())
self.cLabel.configure(text= "Sum = " +str(xval+yval))
Catching the Error
However, if you enter some illegal non-numeric value, you can catch the
exception and issue a error message:
Click here to view code image
try:
xval= float(self.xEntry.get())
yval = float(self.yEntry.get())
self.cLabel.configure(
text= "Sum = " +str(xval+yval))
except:
messagebox.showerror( "Conversion error" ,
"Not numbers" )
Applying Colors in tkinter
The named colors in tkinter are white, black, red, green, blue, cyan, yellow,
and magenta. You can also create any color you want by using hexadecimal
values, which can be #RGB or #RRGGBB or longer 12- and 16-bit strings.
So red would be #f00 and purple #c0f, for example. Each digit can be
anywhere from 0 to f for the numbers between 0 and 15.
So, in the ttk toolkit, our Quit button with the red lettering is written like
this:
Click here to view code image
class QuitButton(DButton):
def init (self, root):

also sets Quit to Red

Style().configure( "W.TButton" ,
foreground= "red" )
super(). init (root,
text= "Quit" ,style= "W.TButton" )
self.pack(side=RIGHT, padx= 10 )

calls the quit function and the program exits

def comd(self):
quit()
Creating Radio Buttons
The Radiobutton widget is named after the series of buttons on car radios.
When they were actual, solid buttons, those five or six buttons enabled you
to select different stations while you drove. But now that radio controls are
more likely touch screens, the button design persists, with around six
choices per screen, and some way to move on to other sets from other
sources.
The idea of the radiobutton is to allow only one choice, so when you click
on one button, any other button selection turns off. Figure 2-8 shows a
simple example in radiobuttons.py.
When you click the Query button, the code looks to see which button is
selected and goes on with the program. You can also set the argument
indicatoron to 0 if you want to get actual buttons instead of the radio
buttons.
Figure 2-8 Radio buttons with indicatoron=1 (top) and indicatoron=0
(bottom)
When you create a set of radio buttons, you assign them all to the same
group variable. This variable is of the type IntVar, a special type used to
reach down into the Tk graphics toolkit. Each radio button is assigned an
index value (such as 0, 1, or 2) when you create it. When you click one of
the buttons, that index value is copied to the group variable. So, you can
find out which button is selected by examining the value stored in the group
variable. In the following code Radiobuts.py, the ChoiceButton class is
derived from the Radiobutton class.
Click here to view code image
groupv = tk.IntVar()
ChoiceButton(root, 'Red' , 0 , groupv)
ChoiceButton(root, 'Blue' , 1 , groupv)
ChoiceButton(root, 'Green' , 2 , groupv)
You can also set which radio button is selected by setting the value of that
group variable:
Click here to view code image
groupv.set( 0 ) # Red button selected
groupv.set( None ) # No buttons selected
Like the ordinary Button class, the Radiobutton class can receive
commands when clicked. So, just as we did when we created DButton, we
will derive the ChoiceButton class and include the command function inside
it.
Click here to view code image

ChoiceButton is derived from RadioButton

class ChoiceButton(tk.Radiobutton):
def init (self, rt, color, index, gvar,
clabel):
super(). init (rt, text=color,
padx= 20 , command=self.comd,
variable=gvar, value=index)
self.pack(anchor=W)
self.root = rt
self.color = color
self.index = index
self.var = gvar
self.clabel = clabel

clicks are sent here

def comd(self):

change label text and label text color

self.clabel.configure(fg=self.color,
text = self.color)
Figure 2-9 shows the original window and the name and color of the label
text when you click a radio button.
Figure 2-9 Original window (top) and window with a color name and
color (bottom)
The comd method changes both the label text and the label color using the
same color string. In this somewhat inelegant example, a reference to the
group variable gvar is passed to each of the radio buttons, even though it is
the same variable in all three cases. There is a better way to do this well
with a class-level variable.
Using a Class-Level Variable
If all three ChoiceButton classes refer to the same variable, why not put it
inside the class itself? This is a lot better than having it off in some random
place in the main() routine. We’ll declare gvar as a class-level variable,
referring to it a ChoiceButton.gvar. There is one and only one such variable
for all three classes, and all of them can examine its state.
Click here to view code image
class ChoiceButton(tk.Radiobutton):
gvar = None # the group var will go here
def init (self, rt, color, index, cLabel):
super(). init (rt, text=color,
padx= 20 , command=self.comd,
variable=ChoiceButton.gvar,
value=index)
self.pack(anchor=W)
self.color = color #button color name
self.cLabel = cLabel #label to be colored
self.index = index # index of button
Next, we set it to None when we create the user interface:
Click here to view code image

set the group variable inside the class

ChoiceButton.gvar = IntVar()
ChoiceButton.gvar.set(None)
ChoiceButton(root, 'Red' , 0 , cLabel)
ChoiceButton(root, 'Blue' , 1 , cLabel)
ChoiceButton(root, 'Green' , 2 , cLabel)
Everything else is the same; we don’t have to pass the same variable to all
three instances of the ChoiceButton class. This code is in
Radioclassbuttons.py.
Communicating Between Classes
While it is pretty easy to respond to these clicks, there is the question of
how the rest of the program receives these selections and how it handles
them. Because the result is inside one of the ChoiceButton instances, this is
a bit tricky. Likewise, if you click the Query button of the previous
example, how do the button and the program find out what was selected?
The most effective solution to this problem is the Mediator pattern, which
we will cover in Part IV, “Behavioral Patterns.”
Using the Grid Layout
The grid layout is even easier to use than the pack layout, which lets you
arrange your widgets in a grid in your window. Grids are numbered rows
and columns, and you can have any number of either. The grid is not drawn
in your window, and any row or column that doesn’t contain a widget is not
shown.
For a simple illustration, let’s redo the same example used previously for
the pack layout:
Click here to view code image
root=Tk()
root.title("grid")

create the first label and entry field

lbl1 = Label( text="Name")
lbl1.grid(row= 0 , column= 0 , padx= 5 , pady= 5 )
entry1 = Entry()
entry1.grid(row= 0 , column= 1 )

and the second

lbl2 = Label( text="Address")
lbl2.grid(row= 1 , column= 0 , padx= 5 , pady= 5 )
entry2 = Entry()
entry2.grid(row= 1 , column= 1 , padx= 5 )
root.mainloop()
Note that this approach is a lot simpler than the code needed to create the
same window using the pack layout. You don’t have to create any frames:
The grid accomplishes the same thing a bit more simply. The code is in
gridoptions.py.
You can see a slight difference between the top window in Figure 2-10 and
the one in Figure 2-5 (the pack example): the Name and Address labels are
not left justified. This is where you can use the sticky modifier to the grid
layout to indicate where in the grid cell you want to place the widget. By
using these two grid method calls, you can place the label at the West side
of the grid cell:
Click here to view code image
lbl1.grid(row= 0 , column= 0 , padx= 5 , pady= 5 , sticky=W)
lbl2.grid(row= 1 , column= 0 , padx= 5 , pady= 5 , sticky=W)
Figure 2-10 shows the result in the window on the right. (The other
positions are, of course, N, S, and E, and you can combine them.)
Figure 2-10 Grid layout without (top) and with sticky=W (bottom)
Creating Checkbuttons
In Python, the standard windowing UI check box is called a Checkbutton.
Checkbuttons enable you to indicate the selections you want to give users,
where they can select zero or more of the options. Let’s create the ever-
popular pizza topping selection menu using a series of checkbuttons and
position them in a grid layout (see Figure 2-1 1 ).
Figure 2-1 1 Checkbuttons in a grid.
Placing the checkbuttons in a grid is pretty simple: there are six rows and
two columns, with row 4 of the second column containing the Order button.
But how to write the program? Once we create those boxes, how do we find
out which are checked? Checkbuttons operate much as Radiobuttons do,
with an IntVar object associated with each button. Unlike the Radiobutton,
where a single button group refers to the same IntVar, you have to create an
IntVar for each Checkbutton.
So how do we manage these buttons? With a List perhaps? Close, but how
do we make the Order button aware of this list and how do we find out
which ones are checked?
There are two steps to creating this program. The first step is to create a
class derived from Checkbutton, that we’ll just call Checkbox.
Click here to view code image
""" Checkbox class derived from Checkbutton
includes get methods to get the name var state"""
class Checkbox(Checkbutton):
def init (self, root, btext, gvar):
super(). init (root, text=btext,
variable=gvar)
self.text=btext
self.var = gvar
def getVar(self):
return self.var.get() # get value stored
This Checkbox class has two get methods: one to get the value contained in
the associated IntVar and one to get the title string for the Checkbox. So, we
can create the Checkbox classes and ask them whether the box is checked.
But how to create them all?
You can do this most easily by creating a list of check box names and then
looping through it to create the IntVar and Checkbox array. Here is the list of
names:
Click here to view code image
self.names = ["Cheese","Pepperoni","Mushrooms",
"Sausage","Peppers","Pineapple"]
Now, let’s create the array of Checkboxes using these names. It’s pretty
simple, but we need to make sure we create a separate IntVar for each
Checkbox:
Click here to view code image
boxes=[] #list of check boxes stored here
r = 0
for name in self.names:
var=IntVar() # create an IntVar
cb = Checkbox(root, name, var) # create checkbox
boxes.append(cb) # add it to list
cb.grid(column= 0 , row=r, sticky=W) # grid layout
r += 1 # row counter
The second and final step in building this program is to create the Order
button. When we click it, we want to see a list of the toppings ordered. But,
how does this button know about the orders? We subclass Button with an
extra argument that passes that list of boxes to the button:
Click here to view code image

Create the Order button and give it

the list of boxes

OKButton(root, boxes).grid(column= 1 , row= 3 , padx= 20 )
The OK button stores the reference to the boxes list so that it can print the
order when clicked. This derived button class, as with ones we’ve created
earlier, has its own comd method that gets called when the button is clicked.
It prints the check box label and tells whether the box is checked.
Click here to view code image
class OKButton(Button):
def init (self, root, boxes):
super(). init (root, text= "Order" )
super().config(command=self.comd)
self.boxes= boxes # save list

of checkboxes

print out the list of ordered toppings

def comd(self):
for box in self.boxes:
print (box.Text, box.getVar())
And here is the order list:
Cheese 1
Pepperoni 0
Mushrooms 0
Sausage 1
Peppers 1
Pineapple 0
Disabling Check Boxes
Sometimes you may want to keep people from clicking certain check boxes.
Here, we duplicate the Internet joke about pineapple on pizza in the
program checkboxes.py (see Figure 2-12).
Figure 2-12 Check boxes, with one disabled
Note that Pineapple is grayed out in this display, so you can’t click that
check box. The code for doing this is part of the Checkbox class:
Click here to view code image

Internet joke about Pineapple on pizza

if self.text == "Pineapple":
#prevent Pineapple on pizza
self.configure(state=DISABLED)
You can also use this form:
btn['state']=DISABLED
In either case, you can turn a button or other widget back on with either of
the following:
Click here to view code image
btn['state'] = tk.NORMAL
btn.configure(state=NORMAL)
Adding Menus to Windows
As you begin to develop programs that have a number of options, you
probably realize that menus would be just the thing. And menus in Python
are superficially very simple to implement.
Suppose you want to create a couple of menus like these:
File Draw
New Circle
Open Square
Exit
The code for creating these menus can be as simple as this:
Click here to view code image

create the menu bar

menubar = Menu(root)
root.config(menu=menubar)
root.title( "Menu demo" )
root.geometry( "300x200" )
filemenu = Menu(menubar, tearoff= 0 )
menubar.add_cascade(label= "File" , menu=filemenu)
filemenu.add_command(label= "New" , command= None )
filemenu.add_command(label= "Open" , command= None )
filemenu.add_separator()
filemenu.add_command(label= "Exit" , command= None )
drawmenu = Menu(menubar, tearoff= 0 )
menubar.add_cascade(label= "Draw" , menu= drawmenu )
drawmenu.add_command(label= "Circle" , command= None )
drawmenu.add_command(label= "Square" , command= None )
Figure 2-13 shows the resulting window from Menus.py, showing one of
the menus.
Of course, this simple program skips over the commands that the menu
items are meant to execute, and therein lies the complexity. If you have just
two or three menu items, you can simply create the three functions that the
three buttons are meant to call, and it won’t clutter your program too much.
But if you have a dozen or more menu items, you begin to realize that this
is not at all object-oriented programming. There shouldn’t be all those
functions at the top level in your program; they should be part of objects.
And, each class should ideally handle one of those menu commands. This is
the same problem discussed with buttons. Putting the command that the
button executes into the Button class itself is a much better way to organize
your program. These classes are quite general, so you can use them
anywhere you want to make a menu.
Figure 2-13 Menu display
So, what classes would we need here? At the very least we would need
A Menubar class
A Topmenu class that holds the name of that menu group
A way to add menu commands to that menu
Additionally, we create a basic MenuCommand class for each menu item.
Our Menubar class just subclasses Menu and adds one line of code:
Click here to view code image

creates the menu bar

class Menubar(Menu):
def init(self, root):
super().init(root)
root.config(menu=self)
The other main class, Topmenu, represents the top of each menu:
Click here to view code image

this class represents the top menu item in each column

class TopMenu():
def init(self, root, label, menubar):
self.mb = menubar
self.root = root
self.fmenu = Menu(self.mb, tearoff=0)
self.mb.add_cascade(label=label, menu=self.fmenu)
def addMenuitem(self, mcomd):
self.fmenu.add_command(label = mcomd.getLabel(),
command = mcomd.comd)
def addSeparator(self):
self.fmenu.add_separator()
The rest of the code creates the class for each menu item. We’ll begin with a
base class and derive all the others from it:
Click here to view code image

abstract base class for menu items

class Menucommand():
def init(self, root, label):
self.root = root
self.label=label
def getLabel(self):
return self.label
def comd(self): pass
The only thing we have to do is write the comd method into the derived
classes, and most of those are pretty simple. For example:
Click here to view code image

exits from the program

class Quitcommand(Menucommand):
def init(self, root, label):
super().init(root, label)
def comd(self):
sys.exit()
We use the sys.exit() method here because it is recommended in
production code; it ensures that everything is closed before exiting.
The File | Open menu command is a little more complicated. We don’t
really have any need to open any files in this little program, but we strip off
the path and save only the filename, which we display in the title bar:
Click here to view code image

menu item that calls the file open dialog

class Opencommand(Menucommand):
def init(self, root, label):
super().init(root, label)
def comd(self):
fname= filedialog.askopenfilename(
title="Select file")

check for nonzero string length

if len(fname.strip()) > 0:
nameparts = fname.split("/")

find the base file name without the path

k = len(nameparts)
if k>0 :
fname = nameparts[k-1]
self.root.title(fname)
To draw the circle and square, we have to pass the Canvas object to those
menu items:
Click here to view code image

draws a circle

class Drawcircle(Menucommand):
def init(self, root, canvas, label):
super().init(root, label)
self.canvas = canvas
def comd(self):
self.canvas.create_oval(130, 40,
200, 110, fill="red")
That’s really it. We create the menu command items and add them to the
Topmenu and we have the whole program in object-oriented form:
Click here to view code image
menubar = Menubar(root)
#create the File menu and its children
filemenu = TopMenu(root, "File", menubar)
filemenu.addMenuitem(Menucommand(root, "New"))
filemenu.addMenuitem(Opencommand(root, "Open"))
filemenu.addSeparator()
filemenu.addMenuitem(Quitcommand(root, "Quit"))

create the Draw menu and its children

drawmenu= TopMenu(root, "Draw", menubar)
drawmenu.addMenuitem(Drawcircle(root, canvas,
"Circle"))
drawmenu.addMenuitem(Drawsquare(root, canvas,
"Square"))
Figure 2-14 shows the result of ObjMenus.py.
Note that when we created the File|New menu item, we used the base
MenuCommand class, which has an empty comd() method. We did this because
we didn’t plan to execute the New method, as it is beyond the scope of this
example.
Figure 2-14 Menu bar, menu, and two graphics elements drawn in the
canvas
Using the LabelFrame
The LabelFrame widget is just like the Frame widget, except that you can add
a label as part of the frame, as shown in labelframetest.py (see Figure 2-15).
Figure 2-15 The LabelFrame with relief=RAISED and the “alt” theme.
Click here to view code image

style required if used on Windows 10

style = Style()
style.theme_use('alt')

create LabelFrame

labelframe = LabelFrame(root, text= "State data" ,
borderwidth= 7 , relief=RAISED)
labelframe.pack(pady= 5 )

add 4 labels

Label(labelframe, text="State").pack()
Label(labelframe, text="Abbrev").pack()
Label(labelframe, text="Capital").pack()
Label(labelframe, text="Founded").pack()
Because of a bug in Python 3.6, 3.7, and 3.8, the frame is displayed only
faintly in Windows 10 unless you include the “alt” style statement we show
above. For the border, you have the option of GROOVE, FLAT, RAISED, or
RIDGEGROOVE, which is the default; Figure 2-15 shows RAISED.
Moving On
In this chapter, we’ve covered most of the basic visual widgets. In the next
chapter we’ll look at displaying lists of data.
Examples on GitHub
Hellobuttons.py: The first button example
Derived2buttons.py: Subclassed buttons
Messageboxes.py: Message boxes and file window examples
Yourname.py: Entering and displaying your name
Simplemath.py: Entering two numbers and adding them
Packoptions.py: Using the pack method
Radiobuts.py: Radio button examples
Radioclassbuttons.py: Using a class variable
Gridoptions.py: Using the grid
Checkboxes.py: Extension of Checkbuttons
Menus.py: Menu example
Objmenus.py: Object-oriented menu example
LabelFrameTest.py: Labelframe example
Disable.py: Enabling and disabling buttons

'James Cooper - Python Programming with D' 카테고리의 다른 글

The Factory Pattern  (0) 2023.03.23
What Are Design Patterns?  (0) 2023.03.23
Visual Programming of Tables of Data  (0) 2023.03.23
Introduction  (0) 2023.03.23
Index  (0) 2023.03.23

댓글