tags:

views:

53

answers:

1

I am trying to build a GUI in Python using Tkinter. My idea is to build several Frames inside the root Frame. I can get 2 Frames to appear, but the third Frame overlays the second Frame. I've tried both pack() and grid() as the layout manager in the root Frame.

I want to have a "Stuff At The Top" Frame and a "Stuff At The Bottom" Frame with 1 Cat1 Frame and 1 to 8 Cat2 Frames in between. The GUI needs to be able to be dynamically rebuilt as the application discovers how many Cat2 widgets it's controlling.

(One other minor problem. Since I introduced the Cat2Frame class and moved the tkFont variables to global scope, my 12 point font is still only 9 point.)

Here's my sanitized code snippet. (Haven't gotten to the Bottom Frame yet.)

def anchor(widget, rows=0, cols=0):
    """Takes care of anchoring/stretching widgets via grid 'sticky'"""
    for r in range(rows):
        widget.rowconfigure(r, weight=1)
    for c in range(cols):
        widget.columnconfigure(c, weight=1)


font_ = None
bold_ = None
bold_12 = None

class TkinterGui():
    """Tkinter implementation of the GUI"""
    def __init__(self):
        """Create the Tkinter GUI"""
        self._top_level = None
        self._top_row   = None
        self._buildGui(_TITLE)


    def _buildGui(self, title):
        """Build the Tkinter GUI"""
        self._top_level  = Tkinter.Tk()
        font_ = tkFont.Font(family='FreeSans', size=9)
        bold_ = tkFont.Font(family='FreeSans', size=9, weight='bold')
        bold_12 = tkFont.Font(family='FreeSans', size=12, weight='bold')
        anchor(self._top_level, 4, 1)
        self._top_row = 0
        self._buildTop()
        self._top_row = 1
        self._buildCat1()
        t1 = Cat2Frame(self._top_level, "Cat2 1")
        self._top_row = 2
        t1.place_frame(self._top_row)
        t5 = Cat2Frame(self._top_level, "Cat2 5")
        self._top_row = 3
        t5.place_frame(self._top_row)
        self._top_level.title(title)


    def _buildTop(self):
        """Private method to build the Top frame of the GUI."""
        top_frame = Tkinter.Frame(self._top_level, name='top_frame')
        anchor(top_frame, 1, 3)
        top_frame.columnconfigure(0, weight=2)
        top_frame.columnconfigure(1, weight=5)
        col1_label = Tkinter.Label(top_frame
                                   , name='col1_label'
                                   , text="Col1"
                                   , font=bold_12
                                   , width=20
                                    ).grid(row=0
                                         , column=0
                                         , sticky=N+E+W+S
                                          )
        col2_label = Tkinter.Label(top_frame
                                   , name='col2_label'
                                   , text="Col2"
                                   , font=bold_12
                                   , width=40
                                    ).grid(row=0
                                         , column=1
                                         , sticky=N+E+W+S
                                          )
        top_button = Tkinter.Button(top_frame
                                    , name='top_button'
                                    , text='Top Button'
                                    , font=bold_
                                     ).grid(row=0
                                          , column=2
                                          , sticky=E
                                           )
        top_frame.grid(row=self._top_row, column=0, sticky=N+W)


    def _buildCat1(self):
        """Private method to build the Cat1 frame of the GUI"""
        cat1_frame = Tkinter.Frame(self._top_level, name='cat1_frame')
        anchor(cat1_frame, 3, 3)
        cur_row = 0
        cat1_frame.columnconfigure(2, weight=6)
        Tkinter.Label(cat1_frame
                    , name='cat1_label'
                    , text='Cat1'
                    , font=bold_
                     ).grid(row=cur_row, column=0, sticky=N+E+W+S)
        cat1_size = Tkinter.Text(cat1_frame
                                 , name='cat1_size'
                                 , state=DISABLED
                                 , font=font_
                                 , height=1
                                 , width=10
                                  ).grid(row=cur_row
                                       , column=1
                                       , sticky=E
                                        )
        cat1_status = Tkinter.Text(cat1_frame
                                   , name='cat1_status'
                                   , state=DISABLED
                                   , font=font_
                                   , height=3
                                   , width=72
                                    ).grid(row=cur_row
                                         , column=2
                                         , rowspan=3
                                         , sticky=N+E+W+S
                                          )
        cur_row += 1
        cat1_model = Tkinter.Text(cat1_frame
                                  , name='cat1_model'
                                  , state=DISABLED
                                  , font=font_
                                  , height=1
                                  , width=30
                                   ).grid(row=cur_row
                                        , column=0
                                        , columnspan=2
                                        , sticky=N+W
                                         )
        cur_row += 1
        cat1_serial = Tkinter.Text(cat1_frame
                                   , name='cat1_serial'
                                   , state=DISABLED
                                   , font=font_
                                   , height=1
                                   , width=30
                                    ).grid(row=cur_row
                                         , column=0
                                         , columnspan=2
                                         , sticky=N+W
                                          )
        cat1_frame.grid(row=self._top_row, column=0, sticky=N+W)


class Cat2Frame():
    """Class encapsulation for a Cat2 Frame in the GUI"""
    def __init__(self, parent, t_label):
        """Initialize a Cat2 Frame in the GUI"""
        self._frame = Tkinter.Frame(parent, name='cat2_frame')
        anchor(self._frame, 3, 4)
        self.cur_row = 0
        self._frame.columnconfigure(2, weight=5)

        self._label = Tkinter.Label(self._frame
                                  , name='cat2_label'
                                  , text=t_label
                                  , font=bold_
                                   )
        self._size = Tkinter.Text(self._frame
                                , name='cat2_size'
                                , state=DISABLED
                                , font=font_
                                , height=1
                                , width=10
                                 )
        self._status = Tkinter.Text(self._frame
                                  , name='cat2_status'
                                  , state=DISABLED
                                  , font=font_
                                  , height=3
                                  , width=60
                                   )
        self._control = Tkinter.IntVar()
        self._enabled = Tkinter.Radiobutton(self._frame
                                          , name='cat2_enabled'
                                          , variable=self._control
                                          , text='Enabled'
                                          , font=bold_
                                          , value=1
                                           )
        self._model = Tkinter.Text(self._frame
                                 , name='cat2_model'
                                 , state=DISABLED
                                 , font=font_
                                 , height=1
                                 , width=30
                                  )
        self._disabled = Tkinter.Radiobutton(self._frame
                                           , name='cat2_disabled'
                                           , variable=self._control
                                           , text='Disabled'
                                           , font=bold_
                                           , value=0
                                            )
        self._serial = Tkinter.Text(self._frame
                                  , name='cat2_serial'
                                  , state=DISABLED
                                  , font=font_
                                  , height=1
                                  , width=30
                                   )


    def place_frame(self, top_row):
        self._label.grid(row=self.cur_row, column=0, sticky=N+E+S+W)
        self._size.grid(row=self.cur_row, column=1, sticky=E)
        self._status.grid(row=self.cur_row, column=2, rowspan=3, sticky=N+E+W+S)
        self._enabled.grid(row=self.cur_row, column=3, sticky=W)
        self.cur_row += 1
        self._model.grid(row=self.cur_row, column=0, columnspan=2, sticky=N+W)
        self._disabled.grid(row=self.cur_row, column=3, sticky=W)
        self.cur_row += 1
        self._serial.grid(row=self.cur_row, column=0, columnspan=2, sticky=N+W)
        self.cur_row += 1
        self._frame.grid(row=top_row, column=0, sticky=N+W)
+2  A: 

The short answer to your code is that you're giving the same name to both of your Cat2Frame objects. Either give them unique names, or don't use the name attribute for the frame.

Also, might I suggest you reconsider your programming style a bit? The way you have written your code it is very difficult to read. I've found that separating the layout from the widget creation helps immensely, and might have made finding this bug easier (by virtue of being able to see all of the layout at a glance in order to determine it is correct).

Here's a quick hack to show how you might refactor your code to be more readable. I'm not saying this is the best way to refactor the code, I'm just tossing it out as food for thought.

I'm assuming that you want all your rows to be laid out in a single grid, so each "Frame" of yours (Cat1Frame, Cat2Frame) isn't an actual frame since it is difficult to align the cells in separate widgets. If this isn't the case (ie: if each "Frame" is truly a standalone widget) the code could be made even simpler.

I didn't take the time to create the top row, nor did I take time to get all the row and column resizing exactly right. I also gave each of the text widgets a background so you can see how they are laid out. I find that solving layout problems is much simpler when you can see the boundaries of the widgets.

import Tkinter
from Tkinter import DISABLED

_TITLE="This is the title"

class TkinterGui():
    def __init__(self):
        self._buildGui(_TITLE)
        self.last_row = 0

    def _buildGui(self, title):
        self._top_level  = Tkinter.Tk()
        self._top_level.title(title)

        Cat1(self._top_level, label="Cat 1", row=1)
        Cat2(self._top_level, label="Cat 2.1", row=4)
        Cat2(self._top_level, label="Cat 2.2", row=7)

class Cat1:
    def __init__(self, parent, label=None, row=0):
        self._label  = Tkinter.Label(parent, text=label)
        self._size   = Tkinter.Text(parent, state=DISABLED, height=1, width=10, background="bisque")
        self._status = Tkinter.Text(parent, state=DISABLED, height=3, width=72, background="bisque")
        self._model  = Tkinter.Text(parent, state=DISABLED, height=1, width=30, background="bisque")
        self._serial = Tkinter.Text(parent, state=DISABLED, height=1, width=30, background="bisque")

        self._label.grid( row=row,   column=0, sticky="nsew")
        self._size.grid(  row=row,   column=1, sticky="nsew")
        self._status.grid(row=row,   column=2, rowspan=3, sticky="nsew")
        self._model.grid( row=row+1, column=0, columnspan=2, sticky="nsew")
        self._serial.grid(row=row+2, column=0, columnspan=2, sticky="nsew")

class Cat2:        
    def __init__(self, parent, label=None, row=0):
        self._control = Tkinter.IntVar()

        self._label    = Tkinter.Label(parent, text=label)
        self._size     = Tkinter.Text(parent, state=DISABLED, height=1, width=10, background="bisque")
        self._status   = Tkinter.Text(parent, state=DISABLED, height=3, width=72, background="bisque")
        self._model    = Tkinter.Text(parent, state=DISABLED, height=1, width=30, background="bisque")
        self._serial   = Tkinter.Text(parent, state=DISABLED, height=1, width=30, background="bisque")
        self._enabled  = Tkinter.Radiobutton(parent, variable=self._control, text="Enabled", value=1)
        self._disabled = Tkinter.Radiobutton(parent, variable=self._control, text="Disabled", value=0)

        self._label.grid(   row=row,   column=0, sticky="nsew")
        self._size.grid(    row=row,   column=1, sticky="nsew")
        self._status.grid(  row=row,   column=2, rowspan=3, sticky="nsew")
        self._model.grid(   row=row+1, column=0, columnspan=2, sticky="nsew")
        self._serial.grid(  row=row+2, column=0, columnspan=2, sticky="nsew")
        self._enabled.grid( row=row,   column=3, sticky="nsew")
        self._disabled.grid(row=row+1, column=3, sticky="nsew")


if __name__ == "__main__":
    gui=TkinterGui()
    gui._top_level.mainloop()
Bryan Oakley
Thank you. Not using the 'name' attribute allowed all the widgets to show up. I was beginning to refactor my code myself; now I know I was right to do it.It would be nice if there was a central repository for both good examples AND good documentation. The best example I found used both the 'name' attribute and Tkinter.Text().grid(); so I did as well. Looking closer at my main Tkinter reference (tkinter.pdf from New Mexico Tech); it does not even mention the 'name' attribute.Tkinter needs to allow multiple widgets to share one grid; it was just too simple with each thing in its own Frame.
Christopher
I've seen many bits of code that use the Widget().grid(...) style and I can't imagine why they think that is a good idea. Moving layout to a separate block of code makes it so much easier to debug and refactor.
Bryan Oakley
@Christopher: the "multiple widgets share a grid" is possible. The above code illustrates that. I understand what you're asking, though, and what you want is possible, just more work. You can have each logical row truly be a separate entity, you just need to add a little code that reconfigures the column widths so that all rows share the same column widths. I've done it, it just requires a lot of work. It boils down to just how important it is to you that a logical row (ie: a Cat1Frame) is a totally independent entity.
Bryan Oakley