Tkinter pack Problem

Eric Brunel eric_brunel at despammed.com
Wed Jul 26 07:58:30 EDT 2006


On Wed, 26 Jul 2006 12:46:39 +0200, H J van Rooyen <mail at microcorp.co.za>  
wrote:

> Hi,
>
> I am struggling to get the pack method to do what I intend.
> I am trying to display user input in a seperate window, along with
> a little description of the field, something like this:
>
>              Current entry
> Company :               entered co. name
> First entry :               entered stuff
> The second entry:     more entered stuff
> Entry number three :  Last entered stuff

You won't be able to do that kind of layout with pack. This is - quite  
obviously IMHO - a job for grid, since your widgets are arranged in a grid.

According to my experience, pack should only be used for *very* simple  
layouts, such as all widgets in a single row or in a single column. Trying  
to do anything else will very soon end up being a nightmare, with unneeded  
frames everywhere. Learn to use grid and life will be far easier for you.

[snip]

A few comments on your code:

> #!/usr/bin/python
> #   The original of this module is supposed to do an accounting entry -
>
> #   we get the gui stuff
>
> from Tkinter import *
>
> class Entryscreen(Frame):

Why do you inherit from Frame? Apparently, you're trying to do a window. A  
Frame is not a window; it's a general-purpose container for widgets. If  
you want to do a window, inherit from Tk (for a main window) or from  
Toplevel (for any other).

>     """This screen is used to construct a new Entry."""
>
> #   we define the variables we use
>
>     des_string = ''     # Description of what we want to do
>     req_string = ''     # Prompt string
>     dis_string = ''     # Description of field for display
>
>     Company =  "New Entry Screen"   # we dont have a company yet
>     Entry_1 = ''                    # or any entries
>     Entry_2 = ''
>     Entry_3 = ''
>
>     def start_new(self):
>         """This is the routine that assembles a new record"""
>
>         if self.Company == "New Entry Screen":  # if its the first time,
>
> #   we make a new window
>
>             show = Tk()

No, we don't. We actually create a whole new tcl/tk interpreter  
environment, completely independent from the one we already have, which  
happens to create a new main window. Doing so is bound to cause problems  
later. If you want to create a new window, instantiate Toplevel.

>             show.title('Accounting entry display window')
>
> #   we get an instance to display results in
>
>             self.disp = Showscreen("Current entry",master=show)
>
> #   then we ask for the company:
>
>             des_string = "Getting the Company "
>             req_string = "Enter the Company for this session"
>             dis_string = 'Company:'
>             error = self.GetEntry(des_string, req_string, dis_string,  
> self.disp)
>             self.Company = self.Amount
>
> #   Then or else we ask for entry details
>
>         des_string = "Getting first entry"
>         req_string = "Enter first field"
>         dis_string = "First entry:"
>         error = self.GetEntry(des_string, req_string, dis_string,  
> self.disp)
>
>         des_string = "Getting second entry"
>         req_string = "Enter second field"
>         dis_string = "The second entry:"
>         error = self.GetEntry(des_string, req_string, dis_string,  
> self.disp)
>
>         des_string = "Getting third entry"
>         req_string = "Enter third field"
>         dis_string = "Entry number three:"
>         error = self.GetEntry(des_string, req_string, dis_string,  
> self.disp)

This is not important, but why do you always create variables for your  
strings instead of passing them directly? For example, the previous 4  
lines can be written:

error = self.GetEntry("Getting third entry", "Enter third field", "Entry  
number three:", self.disp)

This would shorten your code a lot. And why do you always pass self.disp  
as the last argument? This is an attribute, so using it directly inside  
the GetEntry method is not a problem.

>     def GetEntry(self, des_string, req_string, dis_string, disp):
>         """Entry routine for one field"""
>
>         line = Entryline(des_string, req_string, dis_string, disp,  
> master=root)
>         error = line.mainloop()

*Never* call mainloop twice in an application! This is not the way to go.  
If you want to do a modal dialog, you have to use the wait_window method,  
which waits for a given window to be closed.

>         self.Amount = line.retstring
>         line.destroy()
>
>     def say_bye(self):
>         print 'Have a nice day, Bye!'
>         self.quit()
>
>     def createWidgets(self):
>
>         self.descr = Label(self)
>         self.descr["text"] = self.Company
>         self.descr["fg"] = 'purple'
>         self.descr['bg'] = 'yellow'
>         self.descr.pack({'expand': 'yes', 'fill': 'both', "side": "top"})

This can be written:

self.descr.pack(side=TOP, fill=BOTH, expand=1)

which is shorter and IMHO clearer. TOP and BOTH are constants defined in  
the Tkinter module, and passing named arguments is exactly the same as  
passing a dictionary.

>         Start = Button(self)
>         Start["text"] = "Start a new entry"
>         Start["fg"]   = "blue"
>         Start['bg']   = 'green'
>         Start["command"] =  self.start_new

Again, replace the 5 last lines with:

Start = Button(self, text="Start a new entry", fg='blue', bg='green',  
command=self.start_new)

>         Start.pack({'expand': 'yes', 'fill': 'both', "side": "left",  
> 'after':
> self.descr})

(see above)

>         QUIT = Button(self)
>         QUIT["text"] = "QUIT"
>         QUIT["fg"]   = "black"
>         QUIT['bg']   = 'red'
>         QUIT["command"] =  self.say_bye
>         QUIT.pack({'expand': 'yes', 'fill': 'both', "side": "left",  
> 'after':
> Start})

(see above)

>     def __init__(self, master=None):
>         Frame.__init__(self, master)
>         self.pack()
>         self.createWidgets()
>
> class Showscreen(Frame):
>     """This is supposed to show the entries as they occur."""
>
>     def CreateWidgets(self, Description):
>
>         self.descr = Label(self)
>         self.descr["text"] = Description
>         self.descr["fg"] = 'purple'
>         self.descr['bg'] = 'yellow'
>         self.descr.pack({'expand': 'yes', 'fill': 'x', "side": "top",  
> "anchor":
> "n"})

(see above)

>     def __init__(self, Description, master=None):
>         Frame.__init__(self,master)
>         self.pack()
>         self.CreateWidgets(Description)
>
> class Entryline(Frame):
>     """This asks for an entry from the user and displays the result"""
>
>     retstring = ''
>
>     def entryend(self,S):
>         """This gets done on carriage return and is where the hassles
> originate"""
>
>         self.retstring = self.estring.get() # get the entered string

You're using retstring both as a class attribute (in "retstring = ''"  
above) and here as an instance attribute. This is not really wrong, but a  
bit weird. If you want retstring to be a class attribute, you should  
access it using Entryline.retstring (and not self.retstring); If you want  
it to be an instance attribute, you should remove the line "retstring =  
''" above and add a line:

self.retstring = ''

in the constructor below.

>
>         self.disp.Amount_des = Label(self.disp)       # and put it into  
> the
> display window
>         self.disp.Amount_des["text"] = self.dis_string
>         self.disp.Amount_des["fg"] = 'purple'
>         self.disp.Amount_des['bg'] = 'yellow'
>         self.disp.Amount_des.pack({'expand': 'yes', 'fill': 'x', "side":
> "left"})

(see above)

>         self.disp.Amount = Label(self.disp)
>         self.disp.Amount["text"] = self.retstring
>         self.disp.Amount["fg"] = 'blue'
>         self.disp.Amount['bg'] = 'green'
>         self.disp.Amount.pack({'expand': 'yes', 'fill': 'x', "after":
> self.disp.Amount_des})

(see above)

>         self.quit()                         # That's it, folks
>
>     def createWidgets(self, des_string, req_string):
>         """makes the stuff to ask the question"""
>
>         descr = Label(self)
>         descr["text"] = des_string
>         descr["fg"] = 'purple'
>         descr['bg'] = 'yellow'
>         descr.pack({'expand': 'yes', 'fill': 'both', "side": "top"})

(see above)

>         req = Label(self)
>         req ["text"] = req_string
>         req ["fg"] = 'yellow'
>         req ['bg'] = 'blue'
>         req.pack({'expand': 'yes', 'fill': 'both', "side" :"top",'after':
> descr})

(see above)

>         self.estring = Entry(self)
>         self.estring['fg'] = 'black'
>         self.estring['bg'] = 'white'
>         self.estring.pack({'expand':'yes','fill': 'both', 'side': 'top',
> 'after': req})
>         self.estring.bind('<Key-Return>', self.entryend)
>         self.estring.focus_set()
>
>     def __init__(self, des_stringP, req_stringP, dis_stringP, dispP,  
> master):
>
>         self.disp = dispP       # keep the passed params in instance vars
>         self.des_string = des_stringP
>         self.req_string = req_stringP
>         self.dis_string = dis_stringP

AFAICS, these attributes are never used. Why did you create them?

>         Frame.__init__(self, master)
>         self.pack()
>         self.createWidgets(self.des_string, self.req_string)
>
> root = Tk()
> root.title('Accounting Entry Window')
> app = Entryscreen(master=root)
> app.mainloop()
> root.destroy()

HTH
-- 
python -c "print ''.join([chr(154 - ord(c)) for c in  
'U(17zX(%,5.zmz5(17l8(%,5.Z*(93-965$l7+-'])"



More information about the Python-list mailing list