Python - Center contents (not window) with Tkinter? - python

When I search Google everything comes back for centering the window when using Tkinter. What I'd like to do is center the contents of the window. I am thinking this might not be possible since you place everything at a specific x,y position. So when the window opens let say it is 500x500 and I place buttons and labels and stuff. When the user expands the window full screen or by dragging the corner can the contents recenter inside the window?

Of course it's possible. You have complete control over what goes in a window, and you don't have to place things at specific x,y positions. Tkinter has three geometry managers - pack, place and grid, plus you can also use the text widget and canvas as containers for other widgets. Except for the text widget, all of the rest make it pretty easy to put something in the middle of something else.
Here's a couple of the most obvious examples:
import Tkinter as tk
class Example1(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.label = tk.Label(self, text="Hello, expample1")
self.label.place(relx=.5, rely=.5, anchor="c")
class Example2(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.label = tk.Label(self, text="Hello, example2")
self.label.grid(row=1, column=1, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(2, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(2, weight=1)
if __name__ == "__main__":
root = tk.Tk()
top = tk.Toplevel()
Example1(root).pack(fill="both", expand=True)
Example2(top).pack(fill="both", expand=True)
root.geometry("300x300+100+100")
top.geometry("300x300+450+100")
root.mainloop()

Okay, I managed to figure this one out. I am posting my answer in hopes it helps others.
import Tkinter as tk
root=tk.Tk()
f1 = tk.Frame(width=800, height=600, background="red")
f2 = tk.Frame(width=775, height=575, background="blue")
f1.pack(fill="both", expand=True, padx=20, pady=20)
f2.place(in_=f1, anchor="c", relx=.5, rely=.5)
#f2.rowconfigure(0, weight=1)
#f2.columnconfigure(0, weight=1)
l1 = tk.Label(f2, text="First")
l1.place(x=0, y=0)
l2 = tk.Label(f2, text="Second")
l2.place(x=100, y=300)
root.mainloop()

You can simply use the grid manager and change the column weight. Changing the weight of a column will change how much it expands when the window expands, so you can do:
from tkinter import *
root = Tk()
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
root.rowconfigure(2, weight=1)
root.columnconfigure(2, weight=1)
contents = Frame(root)
contents.grid(row=1, column=1)
# Insert main contents into frame "contents"
This will result in a simple centring effect as the columns and rows either side of the main content are expanding equally to fill the window.

Related

why isn't my frame stretch completely in tkinter

I tried to stretch the frame using sticky='nsew' but it's not working properly. I have attached the screenshot here.
the white color window is my frame and i want to stick to all corners. i dont wanna mess up with the buttons that i made. its working if i set row=0 but in that case i lost the buttons.. i wanna keep that button too
from tkinter import *
root=Tk()
root.title("manage your cashflow")
#for setting full screen window
screen_width=root.winfo_screenwidth()
screen_height=root.winfo_screenheight()
print(screen_height, screen_width)
root.geometry("%dx%d" % (screen_width,screen_height))
root.configure(bg='grey')
#configure
Grid.rowconfigure(root,0,weight=1)
Grid.columnconfigure(root,0,weight=1)
Grid.columnconfigure(root,1,weight=1)
# creating tabs
up_button1= Button(root, text="the list of month", bg='#ebca87')
up_button1.grid(row=0,column=0, sticky='NEW')
up_button2=Button(root, text="the new month",bg='#ebca87')
up_button2.grid(row=0,column=1,sticky='NEW',pady=0)
#class for frames
class frame:
#method for
def frame2(self):
#for frame sticky
Grid.rowconfigure(root,1,weight=1)
Grid.columnconfigure(root,0,weight=1)
Grid.columnconfigure(root,1,weight=1)
#frame for listbox
frame2=Frame(root)
frame2.grid(row=1,column=0,columnspan=2,sticky="NSEW")
Grid.rowconfigure(frame2,0,weight=1)
Grid.columnconfigure(frame2,0,weight=1)
Grid.columnconfigure(frame2,1,weight=1)
salary_text=Label(frame2,text="salary")
salary_text.pack()
salary_entry=Entry(frame2)
salary_entry.pack()
frame_ob=frame()
frame_ob.frame2()
root.mainloop()
I want to stretch the frame to the buttons. how do I do it?
you guys can see in the screenshot that the white frame doesn't stick to all corners of the 2nd row and 1st and 2 column
Your main issue is Grid.rowconfigure(root,0,weight=1). This is causing the row where the buttons sit to expand at the same rate as the row the frame is in.
Just get rid of that line and it should fix your problem.
That said there are several things I would change with your code.
You use Grid.rowconfigure() and so on and this is not the standard usage. Is there any reason you are doing it this way? Normally I would expect to see frame2.rowconfigure(0, weight=1).
You build your frame as a class but don't actually do anything in it that would require it to be a class. You also do not inherit tk.Frame in your class. I would write it a little different here.
import * is frowned on. It can cause problems when maintaining code over time as it grows and makes it harder to know where the methods are from so trouble shooting can be harder. try import tkinter as tk and use the tk. prefix.
Reasons you might need or want to set a variable name for a widget is if you plan on interacting with it later. Like updating a label or if you want to set a variable name to make it easier to ID what that widget is for. Then you can do that but personally I prefer to avoid setting variables if they will never be modified later.
Your code can be reduced quite a bit.
Here is a non OOP example (I set the frame background to black to make it easier to see if the frame was expanding. You can change this back if you need to):
import tkinter as tk
root = tk.Tk()
root.title("manage your cashflow")
root.geometry("%dx%d" % (root.winfo_screenwidth(), root.winfo_screenheight()))
root.configure(bg='grey')
root.rowconfigure(1, weight=1)
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
tk.Button(root, text="the list of month", bg='#ebca87').grid(row=0, column=0, sticky='NEW')
tk.Button(root, text="the new month", bg='#ebca87').grid(row=0, column=1, sticky='NEW', pady=0)
frame2 = tk.Frame(root, bg='black')
frame2.grid(row=1, column=0, columnspan=2, sticky="NSEW")
frame2.rowconfigure(0, weight=1)
frame2.columnconfigure(0, weight=1)
frame2.columnconfigure(1, weight=1)
tk.Label(frame2, text="salary").pack()
salary_entry = tk.Entry(frame2)
salary_entry.pack()
root.mainloop()
Results:
For a more OOP geared option you can do something like this.
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("manage your cashflow")
self.geometry("%dx%d" % (self.winfo_screenwidth(), self.winfo_screenheight()))
self.configure(bg='grey')
self.rowconfigure(1, weight=1)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
tk.Button(self, text="the list of month", bg='#ebca87').grid(row=0, column=0, sticky='NEW')
tk.Button(self, text="the new month", bg='#ebca87').grid(row=0, column=1, sticky='NEW', pady=0)
frame2 = Frame2()
frame2.grid(row=1, column=0, columnspan=2, sticky="NSEW")
class Frame2(tk.Frame):
def __init__(self):
super().__init__(bg='black')
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
tk.Label(self, text="salary").pack()
salary_entry = tk.Entry(self)
salary_entry.pack()
if __name__ == '__main__':
App().mainloop()

Tkinter: Too much space between label and button frame

I'm pretty new to Tkinter and I build a little window with different widgets.
My Code looks like this:
import tkinter as tk
from tkinter import ttk
class Application(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.master = master
self.master.geometry("800x600")
self.master.title("Tkinter Sandbox")
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(1, weight=1)
self._create_left_frame()
self._create_button_bar()
self._create_label_frame()
def _create_left_frame(self):
frame = tk.Frame(self.master, bg="red")
tree_view = ttk.Treeview(frame)
tree_view.column("#0", stretch=tk.NO)
tree_view.heading("#0", text="Treeview")
tree_view.pack(fill=tk.Y, expand=1)
frame.grid(row=0, column=0, rowspan=2, sticky=tk.N + tk.S)
def _create_button_bar(self):
frame = tk.Frame(self.master, bg="blue")
button_run_single = tk.Button(frame, text="Button 1")
button_run_all = tk.Button(frame, text="Button 2")
button_details = tk.Button(frame, text="Button 3")
button_run_single.grid(row=0, column=0)
button_run_all.grid(row=0, column=1, padx=(35, 35))
button_details.grid(row=0, column=2)
frame.grid(row=0, column=1, sticky=tk.N)
def _create_label_frame(self):
frame = tk.Frame(self.master, bg="blue")
name_label = tk.Label(frame, text="Label 1")
performance_label = tk.Label(frame, text="Label 2")
name_entry = tk.Entry(frame)
performance_entry = tk.Entry(frame)
name_label.grid(row=0, column=0)
name_entry.grid(row=0, column=1)
performance_label.grid(row=1, column=0)
performance_entry.grid(row=1, column=1)
frame.grid(row=1, column=1)
if __name__ == '__main__':
root = tk.Tk()
app = Application(root)
app.mainloop()
Between the three buttons and the label + entry frame is a huge space. I want the button and label + entry frame right under each other, without the huge space but the treeview should also expand vertically over the whole application window.
I think the problem might be my row and column configuration but I don't know how to solve this problem.
The way you've structured your code makes it hard to see the problem. As a good general rule of thumb, all calls to grid or pack for widgets within a single parent should be in one place. Otherwise, you create dependencies between functions that are hard to see and understand.
I recommend having each of your helper functions return the frame rather than calling grid on the frame. That way you give control to Application.__init__ for the layout of the main sections of the window.
For example:
left_frame = self._create_left_frame()
button_bar = self._create_button_bar()
label_frame = self._create_label_frame()
left_frame.pack(side="left", fill="y")
button_bar.pack(side="top", fill="x")
label_frame.pack(side="top", fill="both", expand=True)
I used pack here because it requires less code than grid for this type of layout. However, if you choose to switch to grid, or wish to add more widgets to the root window later, you only have to modify this one function rather than modify the grid calls in multiple functions.
Note: this requires that your functions each do return frame to pass the frame back to the __init__ method. You also need to remove frame.grid from each of your helper functions.
With just that simple change you end up with the button bar and label/entry combinations at the top of the section on the right. In the following screenshot I changed the background of the button_bar to green so you can see that it fills the top of the right side of the UI.
You need to change line
self.master.grid_rowconfigure(0, weight=1)
to
self.master.grid_rowconfigure(1, weight=1)
so that the second row takes all the space. Then you need to stick widgets from the label frame to its top by adding sticky parameter to the grid call in _create_label_frame:
frame.grid(row=1, column=1, sticky=tk.N)
I prefer to use the Pack Function since it gives a more open window - its easy to configure. When you use Pack() you can use labels with no text and just spaces to create a spacer, by doing this you won't run into the problem your facing.

How to get an all sticky grid of Treeview and Scrollbar in Python Tkinter?

What I want in Tkinter in Python 2.7 is the following grid layout:
However once, I start using the grid() functions instead of pack() functions, nothing is showing on running the script. The following is what I am stuck with:
import Tkinter, ttk
class App(Tkinter.Frame):
def __init__(self,parent):
Tkinter.Frame.__init__(self, parent, relief=Tkinter.SUNKEN, bd=2)
self.parent = parent
self.grid(row=0, column=0, sticky="nsew")
self.menubar = Tkinter.Menu(self)
try:
self.parent.config(menu=self.menubar)
except AttributeError:
self.tk.call(self.parent, "config", "-menu", self.menubar)
self.tree = ttk.Treeview(self.parent)
self.tree.grid(row=0, column=0, sticky="nsew")
self.yscrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
self.yscrollbar.grid(row=0, column=1, sticky='nse')
self.tree.configure(yscrollcommand=self.yscrollbar.set)
self.yscrollbar.configure(command=self.tree.yview)
if __name__ == "__main__":
root = Tkinter.Tk()
root.title("MyApp")
app = App(root)
app.pack()
app.mainloop()
Any help will be highly appreciated.
You have several problems that are affecting your layout.
First, some of the widgets inside App use self as the parent, some use self.parent. They should all use self in this particular case. So, the first thing to do is change the parent option of the Treeview to self.
self.tree = ttk.Treeview(self)
Second, since your main code is calling app.pack(), you shouldn't be calling self.grid. Remove the line `self.grid(row=0, column=0, sticky="nsew"). It's redundant.
Third, you are using very unusual code to add the menubar. You need to configure the menu of the root window. There's no need to put this in a try/except block, and there's no reason to use self.tk.call. Simply do this:
self.parent.configure(menu=self.menubar)
This assumes that self.parent is indeed the root window. If you don't want to force that assumption you can use winfo_toplevel() which will always return the top-most window:
self.parent.winfo_toplevel().configure(menu=self.menubar)
Finally, since you are using grid, you need to give at least one row and one column a "weight" so tkinter knows how to allocate extra space (such as when the user resizes a window).
In your case you want to give all of the weight to row and column 0 (zero), since that's where you've placed the widget which needs the most space:
def __init__(self, parent):
...
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
Note: you'll also want to make sure when you call app.pack() that you give it parameters that makes it fill any extra space, too. Otherwise the tree will fill "app", but "app" would not fill the window.
app.pack(fill="both", expand=True)
Here is a fully working example with all of those changes. I grouped the main layout code together since that makes the code easier to visualize and easier to maintain:
import Tkinter, ttk
class App(Tkinter.Frame):
def __init__(self,parent):
Tkinter.Frame.__init__(self, parent, relief=Tkinter.SUNKEN, bd=2)
self.parent = parent
self.menubar = Tkinter.Menu(self)
self.parent.winfo_toplevel().configure(menu=self.menubar)
self.tree = ttk.Treeview(self)
self.yscrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
self.tree.configure(yscrollcommand=self.yscrollbar.set)
self.tree.grid(row=0, column=0, sticky="nsew")
self.yscrollbar.grid(row=0, column=1, sticky='nse')
self.yscrollbar.configure(command=self.tree.yview)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
if __name__ == "__main__":
root = Tkinter.Tk()
root.title("MyApp")
app = App(root)
app.pack(fill="both", expand=True)
app.mainloop()
Your question mentioned grid, but in this case you could save a few lines of code by using pack. pack excels in layouts like this, where your gui is aligned top-to-bottom and/or left-to-right. All you need to do is replace the last five lines (the calls to grid, grid_rowconfigureandgrid_columnconfigure`) with these two lines:
self.yscrollbar.pack(side="right", fill="y")
self.tree.pack(side="left", fill="both", expand=True)

python tkinter, more than one item per row

I'm quite new to python and tkinter so try to keep the answers simple please!
I've got the hang of setting up the window and adding in buttons, labels and photos etc, but I've noticed that whenever I use .pack() it just adds it to the middle of the window underneath the last thing I packed.
I was wondering if there is a way to pack the items in the same row as the last item I packed.
Thanks!
pack has options that let you control which side of the containing widget to use, the padding, and other things. For example, to pack widgets from left to right you would use widget.pack(side='left'). All of these options are well documented in many places.
Here's a quick example showing a row of buttons across the top, and a column of labels on the right:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.toolbar = tk.Frame(self, borderwidth=1, relief="raised")
self.labels = tk.Frame(self, borderwidth=1, relief="raised")
self.text = tk.Text(self, width=80, height=20)
self.toolbar.pack(side="top", fill="x")
self.labels.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
for i in range(5):
button = tk.Button(self.toolbar, text="Button %d" %i)
button.pack(side="left")
for i in range(5):
label = tk.Label(self.labels, text="Label %d" %i)
label.pack(side="top", fill="x", padx=8)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
A nice tutorial of pack, including a list of all of the options, is here: http://effbot.org/tkinterbook/pack.htm
A complete description of how the pack algorithm works is here: http://tcl.tk/man/tcl8.5/TkCmd/pack.htm#M26

scrollable listbox within a grid using tkinter

I'm new to this place and tkinter. I am stuck at making a scrollable listbox or canvas. I have tried both widgets. Within this listbox or canvas, I have several entry and label widgets. The origin point is R0,C0. I used row/columnconfigure to stretch the listbox or canvas.
In the main window, I had 4 buttons on row four to column four (0,4->4,4). I placed the scrollbar on column 5. I attempted to use the grid method. The issue I am having is making the scrollbar functional.
Note: Turning the mainframe into a class is only one of the ways I have tried. Packing the scrollbar on the right has worked, with the listbox/canvas packed on the left. However, the listbox/canvas widget that the scrollbar is commanded to does not scroll the listbox/canvas. Also, adding many entry boxes does not cause the listbox/canvas to scroll. Help please.
from tkinter import *
from tkinter.ttk import *
Style().configure("B.TFrame", relief="flat",
background="blue")
Style().configure("R.TFrame", relief="flat",
background="red")
Style().configure("R.TLabel", background="red")
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master, style="B.TFrame")
self.grid(sticky=N+S+E+W)
self.mainframe()
def mainframe(self):
top=self.winfo_toplevel()
self.menuBar = Menu(top)
top["menu"] = self.menuBar
self.subMenu = Menu(self.menuBar, tearoff=0)
self.subMenu2 = Menu(self.menuBar, tearoff=0)
self.menuBar.add_cascade(label="File", menu=self.subMenu)
self.menuBar.add_cascade(label="About", menu=self.subMenu2)
self.subMenu.add_command(label="Open")
self.subMenu.add_command(label="Save")
self.subMenu.add_command(label="Exit")
self.subMenu2.add_command(label="About")
self.subMenu2.add_command(label="Help")
self.data = Listbox (self, bg='red')
scrollbar = Scrollbar(self.data, orient=VERTICAL)
self.add = Button(self, text="")
self.remove = Button(self, text="")
self.run = Button(self, text="")
self.stop = Button(self, text="")
self.data.grid (row=0, column=0, rowspan=4, columnspan=4, sticky=N+E+S+W)
self.data.columnconfigure(1, weight=1)
self.data.columnconfigure(3, weight=1)
self.add.grid(row=4,column=0,sticky=EW)
self.remove.grid(row=4,column=1,sticky=EW)
self.run.grid(row=4,column=2,sticky=EW)
self.stop.grid(row=4,column=3,sticky=EW)
scrollbar.grid(column=5, sticky=N+S)
Without any content in the listbox, there's nothing to scroll...
This seems to work though (shortened the example a bit). See also the example at the scrollbar documentation.
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.grid(sticky=N+S+E+W)
self.mainframe()
def mainframe(self):
self.data = Listbox(self, bg='red')
self.scrollbar = Scrollbar(self.data, orient=VERTICAL)
self.data.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.data.yview)
for i in range(1000):
self.data.insert(END, str(i))
self.run = Button(self, text="run")
self.stop = Button(self, text="stop")
self.data.grid(row=0, column=0, rowspan=4,
columnspan=2, sticky=N+E+S+W)
self.data.columnconfigure(0, weight=1)
self.run.grid(row=4,column=0,sticky=EW)
self.stop.grid(row=4,column=1,sticky=EW)
self.scrollbar.grid(column=2, sticky=N+S)
a = Application()
a.mainframe()
a.mainloop()
You must define the command attribute to the scrollbar, and you must supply the yscrollcommand attribute to the listbox. These two attributes work together to make something scrollable.
The yscrollcommand option tells the listbox "when you are scrolled in the Y direction, call this command. This is usually the set method of a scrollbar, so that when the user scrolls via arrow keys, the scrollbar gets updated.
The command attribute of a scorllbar says "when the user moves you, call this command". This is usually the yview or xview method of a widget, which causes the widget to change its view parameters in the Y or X direction.
In your case, after creating the widgets you would do this:
self.data.config(yscrollcommand=self.scrollbar.set)
scrollbar.config(command=self.data.yview)
This thread is old but in case somebody else falls across it as I did, it needs a few precisions.
Junuxx's answer doesnt work as is, not only because there is an indentation problem due to difficulties in seizing code here (from "self.run" which is part of the "mainframe" function) but because it seems necessary to put the listbox and the scrollbar in their own frame.
Here is a working code for Python 2 and 3 :
#!/usr/bin/env python2
try:
# for Python2
from Tkinter import *
except ImportError:
# for Python3
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.grid(sticky=N+S+E+W)
self.mainframe()
def mainframe(self):
frame = Frame(self)
scrollbar = Scrollbar(frame, orient=VERTICAL)
data = Listbox(frame, yscrollcommand=scrollbar.set,
bg='red')
scrollbar.config(command=data.yview)
scrollbar.pack(side=RIGHT, fill=Y)
data.pack(side=LEFT, fill=BOTH, expand=1)
for i in range(1000):
data.insert(END, str(i))
self.run = Button(self, text="run")
self.stop = Button(self, text="stop")
frame.grid(row=0, column=0, rowspan=4,
columnspan=2, sticky=N+E+S+W)
frame.columnconfigure(0, weight=1)
self.run.grid(row=4,column=0,sticky=EW)
self.stop.grid(row=4,column=1,sticky=EW)
a = Application()
a.mainframe()
a.mainloop()
You may find further information here : https://www.effbot.org/tkinterbook/listbox.htm.
Hope this helps.

Categories

Resources