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)
Related
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.
I've been creating an app where there are Clients that I can add to a table, the problem is, I need a scrollbar to scroll through all the clients since the app Height is limited and the clients aren't.
Using tkinter I found a way to create a "table" using Entry and grid, but what if I want to create 100 rows? they would be outside of the view, so that's why the need of a scrollbar.
For those of you who know Java, I wanted to create something similar to Jtable, it has a method to create row, delete row, and it generates automatically that scrollbar as soon as the JTable runs out of space.
I've tried to use TkTable from ttk and mess around with some properties, but I preferred how Entries look.
root = Tk()
root.geometry("1200x900")
for i in range(10):
e = Entry(relief=RIDGE)
e.grid(row=i, column=2, sticky=N)
root.mainloop()
I created a root = Tk() and used root to grid them.
You'll see 10 Entries on top of the other.
When a window contains many widgets, they might not all be visible. However, neither a window (Tk or Toplevel instance) nor a Entry are scrollable.
One solution to make the window content scrollable is to put all the widgets in a Frame, and then, embed this Frame in a Canvas using the create_window method.
from tkinter import *
root = Tk()
canvas = Canvas(root)
scroll_y = Scrollbar(root, orient="vertical", command=canvas.yview)
frame = Frame(canvas)
# group of widgets
for i in range(100):
e = Entry(frame, relief=RIDGE, width = 100)
e.grid(row=i, column=2, sticky=N)
# put the frame in the canvas
canvas.create_window(0, 0, anchor='nw', window=frame)
# make sure everything is displayed before configuring the scrollregion
canvas.update_idletasks()
canvas.configure(scrollregion=canvas.bbox('all'),
yscrollcommand=scroll_y.set)
canvas.pack(fill='both', expand=True, side='left')
scroll_y.pack(fill='y', side='right')
root.mainloop()
output:
I'm making a text game. I used an entry widget for player input, a text widget for the game's output, and put them into frames. I set the root window's geometry and the frame sizes to fit into that geometry. However, the frame sizes are smaller than expected. Specifically, my story_text_frame is shorter than expected. I have done a tutorial, and am not sure what I am missing now.
import tkinter as tk
class Game(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args,**kwargs)
self.geometry('1280x720')
self.player_input_frame = tk.Frame(self, height=20, width=625)
self.player_input_field = tk.Entry(self.player_input_frame, background='black', foreground='white', relief='flat')
self.player_input_field.grid(row=0, column=0)
self.player_input_frame.grid(row=2, column=1)
self.story_text_frame = tk.Frame(self, height=670, width=625)
self.story_text_field = tk.Text(self.story_text_frame, background='grey', foreground='white')
self.story_text_field.grid(row=0, column=0)
self.story_text_frame.grid(row=1, column=1)
To have a widget size follow the size of a master widget or a window you must specify how this is to be done with columnconfigure() and rowconfigure(). Then you must expand the widget to fill the available cell space by instructing grid() to stick to the edges; sticky='nsew'. See my example:
import tkinter as tk
class Game(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args,**kwargs)
self.geometry('800x600')
self.columnconfigure(0, weight=1) # Specify how columns and rows will
self.rowconfigure(0, weight=1) # change when window size changes.
# The game's output Text() widget
self.story_text_field = tk.Text(self)
self.story_text_field.grid(row=0, column=0, sticky='nsew',
pady=10, padx=10) # Sticky expands the widget to fill the cell
# The player input Entry() widget
self.player_input_field = tk.Entry(self)
self.player_input_field.grid(row=1, column=0, pady=(0,10))
Game().mainloop()
I have removed the Frames to make the construction clearer. Also I removed the coloring of the widgets. I introduced some padding to make the result more pleasing to the eye and changed the window size to be easier to handle on my tiny screen.
For further information on grid() I can recommend effbot's The Tkinter Grid Geometry Manager. You can also read Mike - SMT's answer to Find position of another anchor than the anchor already used which elaborates on both grid() and pack().
I'm using tkinter with Python 3.4 in Windows 7.
I'm positioning in a non-absolute way (I'm not using place, I'm using grid), and therefore, the widgets should scale when I resize the window automatically. Nevertheless, that does not happen, and I'm failing to grasp the point. Here's my code:
import tkinter as tk
class App(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.config()
self.grid()
self.create_widgets()
def config(self):
self.master.title("Pykipedia Explorer")
def create_widgets(self):
self.search_label = tk.Label(self, text="Search: ")
self.search_label.grid(row=0, column=0, sticky=tk.N+tk.SW)
self.search_entry = tk.Entry(self)
self.search_entry.grid(row=0, column=0, padx=60, sticky=tk.N+tk.SW)
self.search_button = tk.Button(self, text="Explore!")
self.search_button.grid(row=0, column=0, padx=232, sticky=tk.SW)
self.content_area = tk.Text(self)
self.content_area.grid(row=1, column=0)
self.content_scroll_bar = tk.Scrollbar(self, command=self.content_area.yview)
self.content_scroll_bar.grid(row=1, column=1, sticky=tk.NW+tk.S+tk.W)
self.content_area["yscrollcommand"] = self.content_scroll_bar.set
self.quit_button = tk.Button(self, text="Quit", command=self.quit)
self.quit_button.grid(row=2, column=0, sticky=tk.SW)
def main():
app = App()
app.mainloop()
return 0
if __name__ == '__main__':
main()
Why??
Also, I've tried to use grid_columnconfigure and grid_rowconfigure just like in this answer, and it fails miserably.
You have several problems in your code that are working together to prevent the widgets from scaling (and by "widgets", I assume you mean the text widget).
First, you use grid to put the instance of App in the root window. However, you haven't set the sticky attribute, so the app won't grow and shrink. If it doesn't grow and shrink, neither will its contents.
Also, because you're using grid, you need to give row zero and column zero a positive weight so that tkinter will allocate extra space to it. However, since this is the only widget in the root window, you can use pack and solve the problem by replacing the call to grid with a call to pack:
self.pack(fill="both", expand=True)
Next, you use grid to add the text widget to the canvas. You haven't used the sticky option, so even if the space allocated to it grows, the widget will stay centered in the space. You need to use the sticky attribute to tell it to "stick" to all sides of the area it is given:
self.content_area.grid(row=1, column=0, sticky="nsew")
Finally, you haven't given any columns any "weight", which tells tkinter how to allocate extra space. When a window managed by grid resizes, any extra space is given to the rows and columns according to their weight. By default a row and column has zero weight, so it does not get any extra space.
To get the text area to grow as the window grows, you need to give column zero and row one a positive weight:
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
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.