So far we have been stacking widgets in a single column, but in most GUIs the layout is more complicated. For example, Figure 19.1 shows a simplified version of TurtleWorld (see Case study: interface design).
This section presents the code that creates this GUI, broken into a series of steps. You can download the complete example from http://thinkpython.com/code/SimpleTurtleWorld.py.
At the top level, this GUI contains two widgets—a Canvas and a Frame—arranged in a row. So the first step is to create the row.
class SimpleTurtleWorld(TurtleWorld): """This class is identical to TurtleWorld, but the code thatlays out the GUI is simplified for explanatory purposes.""" def setup(self): self.row() ...
setup is the function that creates and arranges the widgets. Arranging widgets in a GUI is called packing.
row creates a row Frame and makes it the “current Frame.” Until this Frame is closed or another Frame is created, all subsequent widgets are packed in a row.
Here is the code that creates the Canvas and the column Frame that hold the other widgets:
self.canvas = self.ca(width=400, height=400, bg='white')self.col()
The first widget in the column is a grid Frame, which contains four buttons arranged two-by-two:
self.gr(cols=2)self.bu(text='Print canvas', command=self.canvas.dump)self.bu(text='Quit', command=self.quit)self.bu(text='Make Turtle', command=self.make_turtle)self.bu(text='Clear', command=self.clear)self.endgr()
gr creates the grid; the argument is the number of columns. Widgets in the grid are laid out left-to-right, top-to-bottom.
The first button uses self.canvas.dump as a callback; the second uses self.quit. These are bound methods, which means they are associated with a particular object. When they are invoked, they are invoked on the object.
The next widget in the column is a row Frame that contains a Button and an Entry:
self.row([0,1], pady=30)self.bu(text='Run file', command=self.run_file)self.en_file = self.en(text='snowflake.py', width=5)self.endrow()
The first argument to row is a list of weights that determines how extra space is allocated between widgets. The list [0,1] means that all extra space is allocated to the second widget, which is the Entry. If you run this code and resize the window, you will see that the Entry grows and the Button doesn’t.
The option pady “pads” this row in the y direction, adding 30 pixels of space above and below.
endrow ends this row of widgets, so subsequent widgets are packed in the column Frame. Gui.py keeps a stack of Frames:
- When you use row, col or gr to create a Frame, it goes on top of the stack and becomes the current Frame.
- When you use endrow, endcol or endgr to close a Frame, it gets popped off the stack and the previous Frame on the stack becomes the current Frame.
The method run_file reads the contents of the Entry, uses it as a filename, reads the contents and passes it to run_code. self.inter is an Interpreter object that knows how to take a string and execute it as Python code.
def run_file(self):filename = self.en_file.get()fp = open(filename)source = fp.read()self.inter.run_code(source, filename)
The last two widgets are a Text widget and a Button:
self.te_code = self.te(width=25, height=10) self.te_code.insert(END, 'world.clear()\n') self.te_code.insert(END, 'bob = Turtle(world)\n') self.bu(text='Run code', command=self.run_text)
run_text is similar to run_file except that it takes the code from the Text widget instead of from a file:
def run_text(self):source = self.te_code.get(1.0, END)self.inter.run_code(source, '<user-provided code>')
Unfortunately, the details of widget layout are different in other languages, and in different Python modules. Tkinter alone provides three different mechanisms for arranging widgets. These mechanisms are called geometry managers. The one I demonstrated in this section is the “grid” geometry manager; the others are called “pack” and “place”.
Fortunately, most of the concepts in this section apply to other GUI modules and other languages.