Tkinter: Why is one Frame overlaying another?
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, colu开发者_开发百科mn=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)
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()
精彩评论