python, tkinter - Frame height and width parameter values have no effect - python

I am trying to create 16 buttons which will completely occupy the bottom 3/4 th of the window. But, the frame height and width values don't seem to have any effect. How to get the behaviour I want?
from tkinter import *
class Application(object):
def __init__(self):
# Creating the main window
self.window = Tk()
self.window.geometry('{}x{}'.format(400, 400))
# frame where numbered buttons will be
frame = Frame(self.window)
frame.configure(height=300, width=300)
frame.pack(side=BOTTOM)
# add buttons
for i in range(4):
for j in range(4):
Button(frame, text=str(4*i + j + 1)).grid(row=i, column=j, sticky=N+E+S+W)
self.window.mainloop()
def main():
app = Application()
main()

The reason for the behavior you are seeing is that tkinter widgets are designed to shrink or expand to exactly fit around their children when using grid or pack. 99.99% of the time this is the exact right behavior, because it results in GUIs that are responsive to changes in font size, screen resolution, and window size.
If your goal is to divide the screen into two parts, where one part takes up 1/4 of the screen and one part takes up 3/4, the best solution is to use grid or place since those both make it easy to set relative sizes.
I don't normally recommend place, so here's a solution using grid. Note the use of grid.rowconfigure and grid.columnconfigure
from tkinter import *
class Application(object):
def __init__(self):
self.window = Tk()
self.window.geometry('{}x{}'.format(400, 400))
self.window.grid_rowconfigure(0, weight=1)
self.window.grid_rowconfigure(1, weight=3)
self.window.grid_columnconfigure(0, weight=1)
frame = Frame(self.window, background="pink")
frame.grid(row=1, column=0, sticky="nsew")
for row in range(4):
frame.grid_rowconfigure(row, weight=1)
for column in range(4):
frame.grid_columnconfigure(column, weight=1)
# add buttons
for i in range(4):
for j in range(4):
button = Button(frame, text=str(4*i + j + 1))
button.grid(row=i, column=j, sticky=N+E+S+W)
self.window.mainloop()
def main():
app = Application()
main()
With this example, row 0 (zero) in the root window can be used for anything you want. Tkinter will try its best to always make that part of the GUI 1/4 the height of the window. I recommend putting a frame in that row, and then any other widgets inside that frame.

Related

Tkinter Text Widget to be inside Notebook as resizable/streched/font changable

Why I cannot stretch Text widget inside Notebook widget(Tab) with sticky?
How to get fixed Text widget size while changing font, while grid_propagate doesn't give results.
How that same window can again be resizable (weight) altogether?
Thanks
import tkinter as tk
from tkinter import *
from tkinter import ttk, font
class TextInsideNotebook:
def __init__(self, main):
self.main = main
self.fontSizePx = -20
# Font
self.fontspecs = font.Font(family="consolas", size=self.fontSizePx)
# Notebook
self.tab = ttk.Notebook(main, width=800, height=600)
self.tab.grid(row=0, column=0, sticky="nsew")
# Tab
self.tab_frame = Frame(self.tab)
self.tab.grid(row=0, column=0, sticky="nsew")
self.tab.add(self.tab_frame, text=' NEW FILE ')
# Text Area
self.textarea = tk.Text(self.tab_frame, font=self.fontspecs)
self.textarea.grid(row=0, column=0, sticky="nsew")
self.tab_frame.grid_propagate(False)
# weights
main.columnconfigure(0, weight=1)
main.rowconfigure(0, weight=1)
# Bind
self.main.bind('<Control-MouseWheel>', self.new_font_size)
def new_font_size(self, event):
if event.delta > 0:
self.fontSizePx = self.fontSizePx - 2
else:
self.fontSizePx = self.fontSizePx + 2
self.fontspecs.config(size=self.fontSizePx)
if __name__ == "__main__":
main = tk.Tk()
agc = TextInsideNotebook(main)
main.mainloop()
Why I cannot stretch Text widget inside Notebook widget(Tab) with sticky?
The text does stick to the edges of the area you've allocated to it. However, you haven't given any rows or columns inside self.tab_frame a weight so that row and column is only as wide and tall as the text widget.
If you're only putting the text widget in the frame, it's much easier to use pack than grid since it takes only one line of code rather than three:
self.textarea.pack(fill="both", expand=True)
If you wish to stick to using grid for some reason, you must give the row and column that contains the text widget a non-zero weight
self.tab_frame.grid_rowconfigure(0, weight=1)
self.tab_frame.grid_columnconfigure(0, weight=1)
self.textarea.grid(row=0, column=0, sticky="nsew")
As a rule of thumb you should always give at least one row and one column a positive non-zero weight in any widget that has children managed by grid.
How to get fixed Text widget size while changing font
What I recommend is to give the widget a small size, such as 1x1, and then let the geometry manager (pack, place, or grid) stretch it to fit the space allocated to it.
self.textarea = tk.Text(..., width=1, height=1)
How that same window can again be resizable (weight) altogether?
I don't understand that question. It's never not resizable.

TKinter scrollbar for middle of screen, supporting resizeable screen with top bar and bottom bar

I am trying to make an application that displays a grid in the middle of the screen surrounded by two bars, a top bar and a bottom bar, which contain buttons for the user to press. These buttons should be able to display no matter where the user scrolls to on the grid and should not be cut off if the window is resized. I am struggling to configure the scrollbar to track the right area and to have the grid fall off the screen when the window is resized. Here is my code so far:
from tkinter import *
def add_row(event):
input_row = Entry(grid_frame, bd=1, text="", bg="white", relief="solid")
input_row.grid(row=grid_frame.rows, sticky=N+S+E+W)
Grid.rowconfigure(grid_frame, grid_frame.rows, weight=1)
grid_frame.rows = grid_frame.rows + 1
class GridFrame(Frame):
rows = 0
def __init__(self, root):
Frame.__init__(self, root, bd=1)
root = Tk(className="Main screen")
root.minsize(408, 80)
# size to quarter of screen
w, h = root.winfo_screenwidth() / 2, root.winfo_screenheight() / 2
root.geometry("%dx%d+0+0" % (w, h))
# grid_frame will resize and bars will not
Grid.rowconfigure(root, 1, weight=1)
Grid.columnconfigure(root, 0, weight=1)
myframe = Frame(root, bd=4, relief="groove")
myframe.grid(row=1, sticky=N + W + S + E)
canvas = Canvas(myframe)
grid_frame = GridFrame(canvas)
grid_frame.pack(fill=BOTH, expand=True)
grid_frame.bind("<Button-1>", add_row)
scrollbar = Scrollbar(myframe, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=RIGHT, fill=Y)
canvas.pack(side=LEFT, fill=BOTH, expand=True)
topBar = Frame(root, grid_frame)
label = Label(topBar, text="Top Text")
label.pack()
topBar.grid(row=0, sticky=W+N+E+S)
bottomFrame = Frame(root, grid_frame)
label = Label(bottomFrame, text="Bottom Text")
label.pack()
bottomFrame.grid(row=2, sticky=E+S+W)
mainloop()
The scrollregion I want to track is the myframe/canvas/grid_frame combination I read to use from this post. The current functionality is that the scrollbar is never in an "active" state and rows added to the grid merely shrink the grid for it to fit within the display. To add a new row, click within the grid_frame region. Any help would be greatly appreciated! Here are some images of the current UI:
UI display with only a few rows
UI display with many more rows
There are two major problems with your code.
First, for the canvas to be able to scroll the inner frame, the inner frame must be a canvas object created with create_window. You're adding it to the canvas with pack, which means the canvas cannot scroll it.
To fix that, use create_window instead of pack:
canvas.create_window(0, 0, anchor="nw", window=grid_frame)
Second, you must reset the scrollregion attribute whenever the contents inside the canvas change. Normally this is done in a <Configure> event handler on the frame, but you can just as easily call it in your add_row function.
For example, add the following line to the end of add_row:
canvas.configure(scrollregion=canvas.bbox("all"))
With those two changes, the scrollbars will start to work as soon as the inner frame is taller than the canvas.
The above solves the problem of the inner window being able to scroll when you add items. In the specific example of this test program, you also have the problem that your binding is on the frame. At startup the frame has a size of 1x1 so it's a bit hard to click on. Moving the binding to the canvas will make this specific demo program work better.

Disable tree view from filling the window after update

I have very simple grid layout with two columns, where first column should display some text, and the second to show tree view:
#! python3
from random import randint
import tkinter as tk
from tkinter import ttk
from tkinter.constants import *
class Application(ttk.Frame):
def __init__(self, root):
self.root = root
self.root.resizable(0, 0)
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_columnconfigure(1, weight=3)
self.init_widgets()
self.arrange_grid()
def init_widgets(self):
self.text_frame = ttk.Labelframe(self.root, text='Info')
self.button = ttk.Button(self.root, text='Process', command=self.on_button)
self.tree = ttk.Treeview(self.root)
self.scroll = ttk.Scrollbar(self.root, orient=HORIZONTAL, command=self.tree.xview)
self.tree.configure(xscrollcommand=self.scroll.set)
def arrange_grid(self):
self.text_frame.grid(row=0, sticky=NSEW)
self.button.grid(row=0, sticky=N, pady=32)
self.tree.grid(row=0, column=1, sticky=NSEW)
self.scroll.grid(row=0, column=1, sticky=(S, W, E))
def on_button(self):
headers = list(range(20))
rows = [[randint(0, 100)] * len(headers) for i in headers]
self.tree["columns"] = headers
for i, row in enumerate(rows):
self.tree.insert("", i, values=row)
if __name__ == '__main__':
root = tk.Tk()
app = Application(root)
root.mainloop()
When I click on a "Process" button, tree view is populated with data, but at the same time it resizes the root window and fills whole space.
How can I instruct ttk tree view, to remain it's size after populating with data?
The treeview will grow to fit all of its columns, unless constrained by the window. The window will grow to fit all of it children unless you give it a fixed size. What is happening is that you're giving the treeview many columns, causing it to grow. Because it grows, the window grows because you haven't constraint its growth.
There are several solutions. Perhaps the simplest solution is to put the tree in a frame so that you can give it an explicit width and height. The key to this is to make the frame control the size of its children rather than the other way around. This is done by turning geometry propagation off.
First, start by creating a frame, and then putting the tree in the frame. We can also put the scrollbar in the frame so that we can treat the tree and scrollbar as a single unit.
self.tree_frame = tk.Frame(self.root, width=400, height=200)
self.tree = ttk.Treeview(self.treeframe)
self.scroll = ttk.Scrollbar(self.tree_frame, orient=HORIZONTAL, command=self.tree.xview)
self.tree.configure(xscrollcommand=self.scroll.set)
Next, add the treeview and scrollbar to the frame. You can use any of pack, place or grid; I find pack superior for a top-to-bottom layout. We also use pack_propagate to turn off geometry propagation (meaning: the frame width and height are honored):
self.tree_frame.pack_propagate(0)
self.scroll.pack(side="bottom", fill="x")
self.tree.pack(side="top", fill="both", expand=True)
With that, you need to modify your arrange_grid to put the frame in the root window, and then ignore the scrollbar since it's already packed in the frame:
def arrange_grid(self):
self.text_frame.grid(row=0, sticky=NSEW)
self.button.grid(row=0, sticky=N, pady=32)
self.tree_frame.grid(row=0, column=1, sticky=NSEW)
Note: you've turned off the ability for the user to resize the window. I recommend avoiding this -- the user usually knows better what size they want the window. Instead, you should configure your GUI to properly resize when the user resizes the window.
Since you're using grid, all you have to do is tell tkinter which rows and columns get any extra space caused by the user resizing the window. Since everything is in a single row, you merely need to give that row a weight:
root.grid_rowconfigure(0, weight=1)

Python tkinter: issues resizing frames

I'm trying to resize a window in my GUI but one of my frames is getting left out and I'm not sure why. The window resizes fine horizontally, but when I try to resize vertically the frame with the button disappears. This is my first GUI so I'm sure there is something I'm missing...
from Tkinter import *
from ttk import *
class GUI(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.root = root
lbFrame = Frame(self.root)
nbFrame = Frame(self.root)
self.note = Notebook(nbFrame)
self.note.pack(fill=BOTH, expand=YES)
lbFrame.pack(side=LEFT, fill=BOTH, expand=YES)
nbFrame.pack(side=RIGHT, fill=BOTH, expand=YES)
self.make_file_viewer()
# Label
lblabel = Label(lbFrame, text='Files', background='#E8E8E8')
lblabel.pack(side=TOP, expand=YES, padx=10, pady=10)
# Listbox
self.lb = Listbox(lbFrame, height=49, borderwidth=0, font=('Purisa', 11), selectmode=EXTENDED)
self.lb.pack(side=BOTTOM, expand=YES, padx=10, pady=10)
def make_file_viewer(self):
fvwr = Frame(self.note)
dataFrm = Frame(fvwr)
btnFrm = Frame(fvwr)
dataFrm.pack(side=TOP, fill=BOTH, expand=YES)
btnFrm.pack(side=BOTTOM, fill=BOTH, expand=YES)
fvwr.config(borderwidth=2)
self.note.add(fvwr, text='File View')
# Label
self.lbl_fvwr_search = Label(dataFrm, text='Search Hits\t0', justify=LEFT)
self.lbl_fvwr_search.pack(side=TOP, anchor=W, expand=YES)
# Scrollbar
scrollbar_fvwr = Scrollbar(dataFrm)
scrollbar_fvwr.pack(side=RIGHT, fill=Y, expand=YES)
# Textbox
self.outputPanel_fvwr_text = Text(dataFrm, wrap='word', height=40, width=115, yscrollcommand=scrollbar_fvwr.set)
self.outputPanel_fvwr_text.pack(side=LEFT, fill=BOTH, expand=YES)
scrollbar_fvwr.config(command=self.outputPanel_fvwr_text.yview)
# Start button
viewBtn = Button(btnFrm, text='Start', width=8)
viewBtn.pack(anchor=W, expand=YES)
if __name__ == '__main__':
root = Tk()
app = GUI(root)
root.mainloop()
The absolute best thing you can do is to start over, and do your layout step-by-step. Start by creating the main areas, and make sure they resize properly. In your case, create the left and right sides. Again, get those two sides resizing properly with respect to each other.
Once you are done, focus on one section. Since you know the main section resizes properly, you only need to focus on the elements within that particular side. Again, break it down into pieces, and get those pieces working before tackling any widgets inside the main pieces.
When you do your layout this way, it's much easier to get the whole GUI working right, because you aren't trying to juggle the behavior of a half dozen widgets at once.
In your specific case, the root of the problem is that you have expand=YES for just about everything. As a general rule of thumb, you only want to set that to YES for one widget in an given parent window. For example, in your main window you want the right to expand but not the left (I'm guessing), and in the right window you want the text widget to expand but not the other widgets.
Set expand=NO for scrollbar_fvwr, self.lbl_fvwr_search, and btnFrm to get the right side to resize properly. For the left side, add fill=BOTH for self.lb, and expand=NONE for lblabel.

Resizing Tkinter Frames with fixed aspect-ratio

I'm looking for a way to make Tkinter Frames behave somewhat like this while resizing:
from Tkinter import *
w = Tk()
w.aspect(1,1,1,1)
w.mainloop()
So I'd like this code here:
import Tkinter as tk
from Tkconstants import *
r=tk.Tk()
content_frame=tk.Frame(r,borderwidth=5,relief=GROOVE)
content_frame.grid(row=0,column=0,sticky=(N,S,E,W))
tk.Label(content_frame,text='content').pack()
pad_frame=tk.Frame(r,borderwidth=5,relief=GROOVE)
pad_frame.grid(row=1,column=0,sticky=(N,S,E,W))
tk.Label(pad_frame,text='-pad-').pack()
r.rowconfigure(0, weight=1)
r.rowconfigure(1, weight=1)
r.columnconfigure(0, weight=1)
r.mainloop()
which basically creates 2 frames, one that's supposed to hold some content (in my application this frame holds a mathplotlib graph that can be resized) and one simply for padding.
To behave on resizing following these rules:
the content frame resizes with a fixed aspect ration (let's say it always needs to be square)
the pad frame takes up the remaining (vertical) space
Any ideas? I've been reading manuals for a while now and can't seem to find something fitting.
-Daniel
There are at least a couple ways to solve this. The simplest, IMO, is to have your padding widget be a container for your content widget, and then you explicitly set the width and height of the content widget using place. This is one of the edge cases where place is preferred over grid or pack.
In the following example I've created a function which lets you pass in a content frame, a padding frame, and an aspect ratio. It then constrains the size of the content frame by the aspect ratio and the size of the container. It will make the content window fill the container in the X dimension and then set the height appropriately. If the resulting window is too tall to be visible, it sets the max height to the height of the container and adjusts the width instead.
I've tried to keep most of the code from the question intact, even though this isn't exactly how I would normally choose to code it. I've given the widgets distinct colors so it's easier to see what is happening.
import Tkinter as tk
from Tkconstants import *
r=tk.Tk()
def set_aspect(content_frame, pad_frame, aspect_ratio):
# a function which places a frame within a containing frame, and
# then forces the inner frame to keep a specific aspect ratio
def enforce_aspect_ratio(event):
# when the pad window resizes, fit the content into it,
# either by fixing the width or the height and then
# adjusting the height or width based on the aspect ratio.
# start by using the width as the controlling dimension
desired_width = event.width
desired_height = int(event.width / aspect_ratio)
# if the window is too tall to fit, use the height as
# the controlling dimension
if desired_height > event.height:
desired_height = event.height
desired_width = int(event.height * aspect_ratio)
# place the window, giving it an explicit size
content_frame.place(in_=pad_frame, x=0, y=0,
width=desired_width, height=desired_height)
pad_frame.bind("<Configure>", enforce_aspect_ratio)
pad_frame = tk.Frame(borderwidth=0, background="bisque", width=200, height=200)
pad_frame.grid(row=0, column=0, sticky="nsew", padx=10, pady=20)
content_frame=tk.Frame(r,borderwidth=5,relief=GROOVE, background="blue")
tk.Label(content_frame,text='content').pack()
set_aspect(content_frame, pad_frame, aspect_ratio=2.0/1.0)
r.rowconfigure(0, weight=1)
r.columnconfigure(0, weight=1)
r.mainloop()
This will work best if the containing widget has contents that easily adjust to the size of the container. If there is a complex layout of widgets within, some widgets could get chopped off if they don't fit when the window is shrunk below its natural size.
You could bind a function to the <Configure> event for a Frame which contains the content and padding frames. The <Configure> event will be fired when you resize a window. Use the event's width and height attributes to fix the size of the content frame by updating the weights of the rows and columns using rowconfigure and columnconfigure
You will need two rows and two columns in the container frame to have a square content frame. With a tall window, you need padding in the second row. And with a wide window you need padding in the second column.
A working example:
import Tkinter as tk
from Tkconstants import *
class Application(tk.Frame):
def __init__(self, master, width, height):
tk.Frame.__init__(self, master)
self.grid(sticky=N + S + E + W)
master.rowconfigure(0, weight=1)
master.columnconfigure(0, weight=1)
self._create_widgets()
self.bind('<Configure>', self._resize)
self.winfo_toplevel().minsize(150, 150)
def _create_widgets(self):
self.content = tk.Frame(self, bg='blue')
self.content.grid(row=0, column=0, sticky=N + S + E + W)
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
def _resize(self, event):
'''Modify padding when window is resized.'''
w, h = event.width, event.height
w1, h1 = self.content.winfo_width(), self.content.winfo_height()
print w1, h1 # should be equal
if w > h:
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=0)
self.columnconfigure(0, weight=h)
self.columnconfigure(1, weight=w - h)
elif w < h:
self.rowconfigure(0, weight=w)
self.rowconfigure(1, weight=h - w)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=0)
else:
# width = height
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=0)
self.rowconfigure(0, weight=1)
self.columnconfigure(1, weight=0)
root = tk.Tk()
app = Application(master=root, width=100, height=100)
app.mainloop()

Categories