[Tkinter] Getting Tkinter Grid Sizing Right the first time - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Tutorials (https://python-forum.io/forum-4.html) +---- Forum: GUI tutorials (https://python-forum.io/forum-34.html) +---- Thread: [Tkinter] Getting Tkinter Grid Sizing Right the first time (/thread-755.html) |
Getting Tkinter Grid Sizing Right the first time - Larz60+ - Nov-06-2016 Hello, Introduction: This is a subject that is asked over and over again. Laying out a tkinter GUI and having it resize properly can be a daunting task. It doesn't have to be, if a few simple rules are followed. The basis for the code presented here came from the tutorial at http://www.tkdocs.com/tutorial/grid.html My methods are somewhat different, so although the code presented here will look familiar, it has been modified quite a bit. You may wish to visit the original tutorial as I feel it is superior to mine, however it doesn't hurt to have another view. 1. First it's in a class. I find this simplifies usage and keeps it containerized. 2. I have added a method for displaying all widgets, their attributes, data types, and current values. This is a great help while debugging. A lot of programmers use pack geometry exclusively for their GUI's. This works fine for many applications, but almost always will have you pulling your hair out when the GUI starts to become complicated. I don't even think of place geometry, unless I am building an instrument panel or similar application where the only things I want moving are the values in dials, meters, and other indicators. I often use a combination of pack and grid. Pack for containers, and grid for their contents This can make resizing difficult if not impossible, so if you need to resize don't do it (unless you like pain) The very best method for resizable GUI's that can grow large with ease is the grid geometry. When done properly, it gives a really finished look and feel to your GUI. This tutorial will use the grid geometry. I will also use ttk because it allows for styling themes and other advanced features. I won't be using these too much in this tutorial, but will be expanding the code and using the style capability, as well as option file in future tutorials. <---> Design Layout: The first thing you want to do is layout your window design. I have found that a spreadsheet is ideal for this purpose [attachment=62] Gui Screenshot: Screenshot: [attachment=63] Source Code: # # Laying out a tkinter grid # # Please also find two images: # GridLayout.png and screenshot.png # Credit: Modified by Larz60+ From the original: # 'http://www.tkdocs.com/tutorial/grid.html' # from tkinter import * from tkinter import ttk class ResizableWindow: def __init__(self, parent): self.parent = parent self.f1_style = ttk.Style() self.f1_style.configure('My.TFrame', background='#334353') self.f1 = ttk.Frame(self.parent, style='My.TFrame', padding=(3, 3, 12, 12)) # added padding self.f1.grid(column=0, row=0, sticky=(N, S, E, W)) # added sticky self.f2 = ttk.Frame(self.f1, borderwidth=5, relief="sunken", width=200, height=100) self.namelbl = ttk.Label(self.f1, text="Name") self.name = ttk.Entry(self.f1) self.onevar = BooleanVar() self.twovar = BooleanVar() self.threevar = BooleanVar() self.onevar.set(True) self.twovar.set(False) self.threevar.set(True) self.one = ttk.Checkbutton(self.f1, text="One", variable=self.onevar, onvalue=True) self.two = ttk.Checkbutton(self.f1, text="Two", variable=self.twovar, onvalue=True) self.three = ttk.Checkbutton(self.f1, text="Three", variable=self.threevar, onvalue=True) self.ok = ttk.Button(self.f1, text="Okay") self.cancel = ttk.Button(self.f1, text="Cancel") self.f1.grid(column=0, row=0, sticky=(N, S, E, W)) # added sticky self.f2.grid(column=0, row=0, columnspan=3, rowspan=2, sticky=(N, S, E, W)) # added sticky self.namelbl.grid(column=3, row=0, columnspan=2, sticky=(N, W), padx=5) # added sticky, padx self.name.grid(column=3, row=1, columnspan=2, sticky=(N, E, W), pady=5, padx=5) # added sticky, pady, padx self.one.grid(column=0, row=3) self.two.grid(column=1, row=3) self.three.grid(column=2, row=3) self.ok.grid(column=3, row=3) self.cancel.grid(column=4, row=3) # added resizing configs self.parent.columnconfigure(0, weight=1) self.parent.rowconfigure(0, weight=1) self.f1.columnconfigure(0, weight=3) self.f1.columnconfigure(1, weight=3) self.f1.columnconfigure(2, weight=3) self.f1.columnconfigure(3, weight=1) self.f1.columnconfigure(4, weight=1) self.f1.rowconfigure(1, weight=1) def get_widget_attributes(self): all_widgets = self.f1.winfo_children() for widg in all_widgets: print('\nWidget Name: {}'.format(widg.winfo_class())) keys = widg.keys() for key in keys: print("Attribute: {:<20}".format(key), end=' ') value = widg[key] vtype = type(value) print('Type: {:<30} Value: {}'.format(str(vtype), value)) def main(): root = Tk() rw = ResizableWindow(root) rw.get_widget_attributes() root.mainloop() if __name__ == '__main__': main()Towards the end of the __init__ method, there are several column and row configure statements These, along with the 'sticky' arguments are what tells the resizer how to distribute the weight for each section of the gui. Sticky: (Credit: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/grid.html) The sticky part by itself doesn't do the trick, it is not a resize instruction, but has the effect of resizing within a cell. In other words, It's purpose is to show where in the grid cell you want the widget to stick. N for top, if the widget is smaller than the cell (the usual condition), widget will still be in the middle of the cell. Extra verticle space will be totally on the bottom of the cell. W + E will stretch the widget horizontally to go the entire distance of the cell from left to right (padding excepted - more on this later) N + S will stretch the widget vertically to go the entire distance of the cell from top to bottom (padding excepted) N + S + E + W will stretch in all directions (padding excepted). sticky can be written two different ways. There are the built-in (to tkinter) constants N, S, E, W or place the values n, s, e, w in a string as follows: Widget(..., sticky= N + S) Widget(..., sticky='ns')rowconfigute and columnconfigure: To actually control resizing of the window, you need rowconfigure and columnconfigure which provide scale information. the syntax is widget.columnconfigure(N, option=value, ...) Inside widget, configure column N so that the given option has the given value. and widget.rowconfigure(N, option=value, ...) Inside widget, configure row N so that the given option has the given value. Available options are:
Determining weight: The weight option sets the relative weight for dispensing any extra spaces among rows/columns. A weight of zero indicates that the cellis not to deviate from it's requested size, otherwise the number indicates the overall or relative growth rate in comparison to the weights of all the other rows/columns. If the weight is 3, it grows at three times the rate of a row/column with a weight one one when the window is stretched. The first two instances are: self.parent.columnconfigure(0, weight=1) self.parent.rowconfigure(0, weight=1)These are for the parent directory (which in this case is root, but doesn't have to be, it could also be a portion of a larger GUI. Both the X and the Y scaling remain constant over the entire widget, so only one rowconfigure and one columnconfigure as required for the parent. The first controls the resizing of columns (vertical) for the entire parent widget and the second controls the resizing of rows (horizontal) for the entire parent widget the zero in each refers to column and row respectfully. a weight of 1 will be applied to the full range of both the scaling remains the same for each row and/or column until specifically changed. <---> For this GUI, controling the expansion of self.f1 and only self.f1 will do the trick as all widgets inside f1 expand as a group. This frame will need 1 rowconfigure (row expansion is symmetrical across the entire window) and 5 columnconfigures (column scale changes in five places). self.f1 is set up as a container for all of the widgets in the GUI: self.parent = parent self.f1_style = ttk.Style() self.f1_style.configure('My.TFrame', background='#334353') self.f1 = ttk.Frame(self.parent, style='My.TFrame', padding=(3, 3, 12, 12)) # added padding self.f1.grid(column=0, row=0, sticky=(N, S, E, W)) # added stickyThis widget is not completely visible (on the display), because it 'contains' all of the other widgets, and is displayed underneath the widgets it contains. The Frame (self.f1) is the same size as the parent widget less borders. Note the sticky - contains all four compass directions which indicates fill the current allotted space. The resizing associated with this frame is: self.f1.columnconfigure(0, weight=3) self.f1.columnconfigure(1, weight=3) self.f1.columnconfigure(2, weight=3) self.f1.columnconfigure(3, weight=1) self.f1.columnconfigure(4, weight=1) self.f1.rowconfigure(1, weight=1)Since all of the widgets are contained within this frame, all resizing is associated with this frame(self.f1) Again the first argument associated relates to the column number or row number The following columns of f1 must be weighted Take a look at the design image above (click to expand) to see how these fit.Padding: At this point, the window should be looking pretty good. But some of widgets seem to be running into each other, giving an unfinished look. Here's where padding comes in. When a widget (such as Label) has text, can contain other widgets, and for other reasons has something that may be placed inside the widget, it will have two types of padding, internal as well as external. padx and pady refer to external padding, and ipadx and ipady refer to internal padding. Unfortunately this is not always the the case for padx and pady. Some widgets use these names for internal padding Frame is one of them. These are usually specified in the Frame statement, separate from the grid statement you can use the padx, pady, ipadx and ipady in the grid statement. This also allows for a two element list so different spacing can be placed on the left or right, or on top or bottom. The value is always in pixels. It is also possible to add padding to rowconfigure and columncomfigure. If done here, the padding will be around the entire row or column Extras - get_widget_attributes def get_widget_attributes(self): all_widgets = self.f1.winfo_children() for widg in all_widgets: print('\nWidget Name: {}'.format(widg.winfo_class())) keys = widg.keys() for key in keys: print("Attribute: {:<20}".format(key), end=' ') value = widg[key] vtype = type(value) print('Type: {:<30} Value: {}'.format(str(vtype), value))Output: You can use this to provise list of each widget and it's attributes.Hope you find this useful -- Larz60+ |