Monday 2 July 2012

Menus for Rye

Rye is coming along. We need to add a little more to the layout of the program then we can concentrate on making it function well. As the program gets longer and longer it will be tedious to keep reproducing all of it here, so I'm going to highlight additions here from previous versions and, as before, provide the whole code to download.

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
menubar=Menu(self) 1
filemenu=Menu(menubar, tearoff=0) 2

filemenu.add_command(label='Open game',command=self.mnuOpen) 3
filemenu.add_command(label='Exit', command=self.quit)
menubar.add_cascade(label='File', menu=filemenu) 4
   
lvlmenu=Menu(menubar,tearoff=0)
lvlmenu.add_command(label='Restart level', command=self.mnuRestart)
lvlmenu.add_command(label='Goto level', command=self.mnuGoLevel)
menubar.add_cascade(label='Level', menu=lvlmenu)
   
helpmenu=Menu(menubar, tearoff=0)
helpmenu.add_command(label='About', command=self.mnuAbout)
menubar.add_cascade(label='Help', menu=helpmenu)
   
self.config(menu=menubar) 5


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

def mnuRestart(self):
    print 'Restart level'

def mnuGoLevel(self):
    print 'Goto level'

def mnuAbout(self):
    print 'About'
    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:

import tkFileDialog

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

import tkMessageBox

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
        self.columnconfigure(3,weight=1) 15
       
        # window is not resizable
        self.resizable(False,False) 16

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.

=========================
Update:
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.

No comments:

Post a Comment

Thank you for taking the time to add a comment.

I have decided, regrettably, to review comments before they are published to ensure that offensive or inappropriate comments do not get shown.

Genuine comments are always welcome and will be published as quickly as possible.