Minecraft Config Editor: Tkinter Text Widget and Frames

March 6, 2012

Furtively he looks round, then takes from the desk drawer a comic-book entitled ‘Thrills and Adventure’. We see the frames of the comic strip. A Superman-type character and a girl are shrinking from an explosion. She is saying ‘My God, his nose just exploded with enough force to destroy his kleenex’. In the next frame, the Superman character is saying ‘If only I had a kleenex to lend him – or even a linen handkerchief – but these trousers…!! No back pocket!’ In the frame beneath, he flies from side to side attempting to escape; finally he breaks through, bringing the two frames above down on himself. Cut to a picture of a safety curtain.

Last tutorial we covered ‘parsing’. We broke a standard config file up into separate lines and then we broke each line into pairs, each pair having a ‘key’ and a ‘value’. If we’re to edit the file, we need a way to edit the values (and after that we’ll write the edited values back to the config file). We’re going to see how to use Tkinter to do that in this tutorial.

Let’s start thinking about how a single key/value pair will look. I am thinking of having the key on the left hand side with a space to input the value on the right hand side. To do this we will use the Label widget that we’ve met before and the Text widget. Label is used to display static text – ie text that will not be edited, while the Text widget allows the user to edit the text which is displayed in the widget, and for the program to read what is entered in the widget. The Text widget is the GUI equivalent of raw_input() that we met so long ago.

from Tkinter import * root = Tk() labelWidget = Label(root,text="A key:") textWidget = Text(root) textWidget.insert('1.0',"A Value") labelWidget.pack(side=LEFT) textWidget.pack(side=RIGHT) root.mainloop()

Here we first create a Tk() object, then create a label widget and a text widget in that object. We pack the label first and we pack it on the LEFT side (LEFT is actually the name of a constant in Tk which Tk translates to ‘put this on the left’) and pack the text widget on the right side. At location “1.0” we add the text “A Value” to the Text widget. Here the number “1.0” means “row 1, at character position 0”, which is to say, at the very start of the text.

If I run this code I get something like this:

Which is sort of what I wanted – a label on the left, and an editable text box on the right (can you see the cursor in the screen shot?) – click the close window widget in the top right corner to close the window.

Exercise: Type something into the text box. See if you can do it to the label.

However, this isn’t really what I wanted. I wanted a little text box, not the enormous one I’ve got here. Since I didn’t specify a height and width for it, the Text widget used its default size (which is way too big). The user also doesn’t have any way to tell the program to use (or cancel) the edit. Let’s change the code to add an ok and cancel button, and to change the size of the text widget.

Here is a revised version which is nearer to what I was looking for:

from Tkinter import * def okClicked(): '''Get the edited values and write them to the file then quit''' #TODO: get values and write them to the file! exit() def cancelClicked(): '''Cancel edits and quit''' exit() root = Tk() labelWidget = Label(root,text="A key:") textWidget = Text(root, width=60, height = 1) okWidget = Button(root, text= "Ok", command = okClicked) cancelWidget = Button(root, text="Cancel", command = cancelClicked) textWidget.insert('1.0',"A Value") labelWidget.pack(side=LEFT) cancelWidget.pack(side=RIGHT) okWidget.pack(side=RIGHT) textWidget.pack(side=RIGHT) root.mainloop()

This gives:

I have added a couple of functions to be run when the ok and cancel buttons are clicked. The ok button’s function is still a little empty at the moment though… I have specified the width to be 60 characters and the height to be one row. Note these are not pixel measurements. If you change the size of the font the text box will also change.

Notice also the way the geometry is working. The widgets which are pack(side=RIGHT) are added at the right in the order they are packed. If the buttons were packed last they would be between the label and the text window.

Exercise: change the program so that the widgets are packed in a different order. What happens if you try side=TOP or side=BOTTOM?

The value can be edited by the user typing directly into the text box. The text in the text box can also be edited programmatically, which is to say its contents can be changed by the program without the user typing. See the Tkinter documentation for details – or clamour on this site and I’ll add a tute.

One thing that I don’t like about this layout is the fact the buttons are on the same line as the key label and value text. When you did the exercise above, you should have noticed that side=TOP and side=BOTTOM don’t really help, since you can’t position the ok and cancel buttons on the same line. What we need to use is the Frame widget. Frames can be thought of as empty spaces in which you can group widgets together. By treating the widgets within the Frame as a group, additional layouts can be achieved. Frames can be packed inside other frames. We will use two frames, one on top of the other. In the first frame we pack the label and text widgets. In the second frame we pack the two buttons.

Here is the code:

from Tkinter import * def okClicked(): '''Get the edited values and write them to the file then quit''' #TODO: get values and write them to the file! exit() def cancelClicked(): '''Cancel edits and quit''' exit() root = Tk() topFrame = Frame(root) bottomFrame = Frame(root) labelWidget = Label(topFrame,text="A key:") textWidget = Text(topFrame, width=60, height = 1) okWidget = Button(bottomFrame, text= "Ok", command = okClicked) cancelWidget = Button(bottomFrame, text="Cancel", command = cancelClicked) textWidget.insert('1.0',"A Value") labelWidget.pack(side=LEFT) cancelWidget.pack(side=RIGHT) okWidget.pack(side=LEFT) textWidget.pack(side=RIGHT) topFrame.pack(side=TOP) bottomFrame.pack(side=BOTTOM) root.mainloop()

This is more like what I wanted (notice you can’t see the individual frames). One might quibble with the location of the ok and cancel buttons. Maybe they should be offset a little from the centre? Maybe they should be off to one side. In any event they are in the general layout that i was looking for: a key label and an editable value text above the ok and cancel buttons. Notice in the code that the widgets we used before have had their parent changed from root to either topFrame or bottomFrame? However these Frame widgets have root as their parent. So the original widgets we were using have effectively been pushed down one level in the hierarchy.

We still don’t know how to get what the user has typed into the text box, but maybe I can leave that as an exercise for the reader (try textWidget.get(“1.0”,END)).

Homework: change the okClicked() function so that it prints the contents of the text box before exiting. Use the hint in the previous paragraph.