We will need a menu bar to allow some interaction outside of the game, such as loading a game layout or restarting the game. Menus are made up of parts and we need to add the parts in the right way to make it work. In the GUI initialisation part of the program we need to define the menus:
# create a menu
filemenu=Menu(menubar, tearoff=0) 2
filemenu.add_command(label='Open game',command=self.mnuOpen) 3
menubar.add_cascade(label='File', menu=filemenu) 4
lvlmenu.add_command(label='Restart level', command=self.mnuRestart)
lvlmenu.add_command(label='Goto level', command=self.mnuGoLevel)
This creates a menubar with three menus on it, File, Level and Help. Line 1 creates an empty menubar. Each of the menu columns is created as in line 2. The tearoff=0 prevents the menu being able to be dragged for later use elsewhere, which I wish was the default state. Each of the items on the dropdown column are added using the example in line 3. This provides a label and points to a function to execute if the option id clicked. This is an event handler. Just like we have seen before, the event handler is called when the mouse is clicked on the menu, not by calling it in code from somewhere. This means we don't need to monitor mouse or keyboard actions, Tkinter does that for us and just calls our code at the right time. We now add the whole column of the menu with line 4, giving it a label of File. This process gets repeated for the Level and Help menu columns. Now the menu structure is created we use config, in line 5, to add it to the window.
We now add the event handlers used above. For now, some will not do much, but it will show the benefit of running these GUI programs from a terminal window, because we can write a message to the terminal to show the event handler has run. Add these event handlers:
def mnuOpen(self): 6
print 'Open' 7
filename = tkFileDialog.askopenfilename(filetypes=[('Rye games','*.rye')]) 8
print filename 9
print 'Restart level'
print 'Goto level'
tkMessageBox.showinfo(parent=self,message='Rye version 0.3',detail='A game to build for yourself',icon='info',title='About')
Each of these functions gets called by name from the menu item if it is clicked. Line 6 defines the function that will be called if the 'Open game' item, defined in line 3, gets clicked. the function uses print 'Open' one line 7 to display the word Open on the terminal, not on the GUI window. It then displays a dialog box to open a file in line 8 and if a file is chosen the name gets printed on the terminal in line 9. The dialog box is a standard part of Tkinter and of GUIs in general. You do need to include an extra import line near the top of your program to make this work:
The dialog box is an askopenfilename type, which means it displays a list of files for you to choose from and returns the name of the selected file or an empty string ('') if you cancel. The dialog does not open the file for you, just helps you choose the file to open. We have added an option for filetypes. This is a list of file types to display - others will not be displayed. In our we want to see any file that has an extension of '.rye', so we use *.rye and name these Rye Games.
The event handlers for Restart Level and Goto Level simply print a message so we know the handler is working. Later we will extend these. The handler for About displays a messagebox. It also needs an import line
This is a rather crude way of displaying an about box, but it works for now.
The remaining parts of the Rye screen are boxes to show how many lives are remaining, the current level of play, how many prizes are still remaining and a hint on how the level might be finished. They are added to the grid for the window to position them. I have included the canvas for the game-play because it is also included in the grid.
# create the canvas for the game
self.cnvs = Canvas(self, height=300, width=300)
self.cnvs.grid(column=0, row=0, columnspan=4) 10
# show the remaining lives
self.lives = Canvas(self, height=22, width=62)
self.lives.grid(column=0, row=1, sticky='W') 11
# show the level number
self.lbllevel = Label(self, text='Level:', width=6)
self.lbllevel.grid(column=1, row=1, sticky='W') 12
# show how many prizes remain
self.lblprizes = Label(self, text='Prizes:',width=6)
self.lblprizes.grid(column=2,row=1, sticky='W') 13
# show the hint for the page
self.lblhint = Label(self, text='hint')
self.lblhint.grid(column=3,row=1, sticky='E') 14
# stretch column 3 on resize
# window is not resizable
In line 10, the canvas is positioned in column 0, row 0. Below the game play there will be 4 items in a single row. We want the game play to be the same width as the total width of the four columns below it, so we add the option columnspan=4, meaning it will span from column 0 to column3 - a total of 4 columns. You can make things span rows too, but we don't need to. We add a canvas to draw the remaining lives on in row 1, column 0. Adding option sticky='W' on line 11 means it will stick to the west end of the box usually known as being left-justified in other programs. The Labels in lines 12 and 13 are where text will be written and both are also stuck west. The label in line 14 has the option sticky='E', to force it to be right-justified. The size of the game play canvas may change with various game layouts, so it would be good to have the hint stretch to fill the space, the other labels have a fixed size (option width=). To do that we make column 3 have weight 1 - others default to 0. This means that when the window is being resized anything in column 3 will get resized because its weight is greater than 0. You can create elaborate weighting to define what proportion of the resize falls into which column, but we don't need to. Line 15 is all we need.
The remaining job is make sure the window cannot be resized with the mouse and line 16 does this. The two options are for vertical and horizontal resizing, both of which are prevented.
Your copy of menuRye is available for download here.
I forgot to point out a simple menu option between line 4 & 5. The File>Exit menu option uses the command command=self.quit. This is how to end the program by closing the top-most window. This is not always the way you might want to close a GUI program, you might want to offer the change to save a file before closing, so the Exit menu option might want to use an event handler with code just like any others, but this quick way out suits out game.