I'm starting to get to know Tkinter but i'm stuck at a point as i'm experimenting and practicing; i couldn't figure out how to move tkinter elements such as Frame or Labels from one tab to another in Tkinter Notebook.
A resourceful link or an answer concerning my problem would be very helpful!
P.S: It's my first time asking a question so apologies if i did something wrong.
It's fairly unusual to move widgets around between frames. Usually it's easiest just to delete the old widget and create a new one in the new location. However, it is possible to move widgets, though with some constraints.
Widgets exist in a tree-like structure, with the root window as the start of the tree. Except for the root window, all other widgets have a parent. You cannot move a widget to a different branch of the tree, so to move from one frame to another, both frames plus the label need to have the same parent.
Normally a widget will be placed in it's parent when using pack, place, or grid. You can change that by using the in_ parameter.
The following example illustrates the technique. Notice that the label to be moved (the_label) is a child of the notebook rather than a child of one of the tabs, and we use the in_ parameter to designate which frame should have the label.
import tkinter as tk
from tkinter import ttk
def moveToOne():
the_label.pack(in_=tab1, expand=True, padx=20, pady=20)
def moveToTwo():
the_label.pack(in_=tab2, expand=True, padx=20, pady=20)
root = tk.Tk()
notebook = ttk.Notebook(root)
toolbar = ttk.Frame(root)
toolbar.pack(side="top", fill="x")
notebook.pack(side="top", fill="both", expand=True)
tab1 = ttk.Frame(notebook)
tab2 = ttk.Frame(notebook)
notebook.add(tab1, text="Tab 1")
notebook.add(tab2, text="Tab 2")
the_label = tk.Label(notebook, text="Click a button to move me")
b1 = tk.Button(toolbar, text="Move to tab 1", command=moveToOne)
b2 = tk.Button(toolbar, text="Move to tab 2", command=moveToTwo)
b1.pack(side="left")
b2.pack(side="left")
# initialize it to be on the first tab
moveToOne()
root.mainloop()
Related
I am wanting to create a tkinter window where when I click a button widget it opens a new window, showing all the widgets, exactly the same, from the root/original window. Essentially creating a second instance of the root window, where the application can have multiple users, using the same GUI, in different windows.
Any help is appreciated.
An example of one of my widgets:
summary_output = Text(
master=window,
height=8,
width=78,
bg="gray95",
borderwidth=2,
relief="groove",
font=("Arial", 12))
My window layout
window = Tk()
window.title("Data Viewer")
window.geometry("750x950")
window.configure(bg='white')
window.iconphoto(False, tk.PhotoImage(file='icon.png'))
I have this but cant seem to place the widgets from the root window:
def new_window():
newWindow = Toplevel(window)
newWindow.geometry("750x950")
newWindow.configure(bg='white')
newWindow.iconphoto(False, tk.PhotoImage(file='icon.png'))
upload_button.place(x=20, y=560)
mainloop()
Is their anyway to change the master to be any window?
Edit:
from tkinter import *
class StaticFrame(Frame):
def __init__(self,master,*args,**kwargs):
Frame.__init__(self,master,*args,**kwargs)
# All your widgets
Label(self,text='This is a reusable frame',font=(0,17)).place(x=0, y=0)
Button(self,text='Click me for nothing').pack()
Label(self,text='End of page').pack()
upload_button = Button(
self,
text="Edit Data",
fg="DodgerBlue4",
font=("Graph Type", 15),
height=1, width=12,
borderwidth=2,
relief="groove")
upload_button.place(x=20, y=50)
root = Tk() # First window
top = Toplevel(root) # Second window
root.geometry("750x968")
StaticFrame(root).pack() # Put the frame on the first window
StaticFrame(top).pack() # Put the frame on the second window
root.mainloop()
Result:
The concept used here is simple, create a "custom frame" that we will put onto these new windows, so that it will create the exact same frame, and widgets within it, inside different windows.
from tkinter import *
class StaticFrame(Frame):
def __init__(self,master,*args,**kwargs):
Frame.__init__(self,master,*args,**kwargs)
# All your widgets
Label(self,text='This is a reusable frame',font=(0,17)).pack()
Button(self,text='Click me for nothing').pack()
Label(self,text='End of page').pack()
root = Tk() # First window
top = Toplevel(root) # Second window
StaticFrame(root).pack() # Put the frame on the first window
StaticFrame(top).pack() # Put the frame on the second window
root.mainloop()
Very simple to code and has been explained with comments, if you do not know what classes and inheritance is then first do go through those. There are variety of other methods that come onto mind when I read this question, like even having an option database and storing the widgets in a list and recreating it based on its order, but this seems to be the easiest in a scratch.
I'm trying to configure a text box and below to the text box 3 buttons in a row centered
I don't want to expand them to fill all the area. just be at the center to stay in their original size.
I was trying to do it with pack or grid. but I'm really get confused. I also trying to to put the text box and the buttons on different frame so maybe it will separate the widgets and let me configure it without messing up things (because everything is relative to the other..) ... but I came with nothing that looks good.
I also want to learn how to use the grid in the correct way if I have all kinds of widgets and buttons one below the other without "columnspan" or adjust the text length inside the buttons as well to match the widgets above them...
In this example. How I can center the buttons? I have to use side=tkinter.LEFT in order to put them one after one in a row. but the problem that they also stick to the left...
import tkinter
window = tkinter.Tk()
frame1 = tkinter.Frame(window).pack()
textbox1 = tkinter.Text(frame1, width=70, height=15).pack(side=tkinter.TOP)
button1 = tkinter.Button(frame1, text="button1").pack(side=tkinter.LEFT)
button2 = tkinter.Button(frame1, text="button2").pack(side=tkinter.LEFT)
button3 = tkinter.Button(frame1, text="button3").pack(side=tkinter.LEFT)
window.mainloop()
in this example if I set another frame to do separation between the widgets ...
It's not get to the center either....
import tkinter
window = tkinter.Tk()
frame1 = tkinter.Frame(window).pack(side=tkinter.TOP)
textbox1 = tkinter.Text(frame1, width=70, height=15).pack(side=tkinter.TOP)
frame2 = tkinter.Frame(window).pack(side=tkinter.TOP)
button1 = tkinter.Button(frame2, text="button1").pack(side=tkinter.LEFT)
button2 = tkinter.Button(frame2, text="button2").pack(side=tkinter.LEFT)
button3 = tkinter.Button(frame2, text="button3").pack(side=tkinter.LEFT)
window.mainloop()
And in this example. with grid, if I'm using different frames the button just jump on the text box and messed up everything....
import tkinter
window = tkinter.Tk()
frame0 = tkinter.Frame(window).grid(row=0, column=0)
frame1 = tkinter.Frame(window).grid(row=1, column=0)
textbox = tkinter.Text(frame0, width=70, height=15).grid(row=0, column=0)
button1 = tkinter.Button(frame1, text="button1").grid(row=0, column=0)
button2 = tkinter.Button(frame1, text="button2").grid(row=0, column=1)
button3 = tkinter.Button(frame1, text="button3").grid(row=0, column=2)
window.mainloop()
Can someone explain to me please in which way it's better to use and how to understand it better...?
it's always confusing me...
thanks in advance,
eliran
I was trying to do it with pack or grid. but I'm really get confused.
I also trying to to put the text box and the buttons on different
frame so maybe it will separate the widgets and let me configure it
without messing up things (because everything is relative to the
other..) ... but I came with nothing that looks good.
Your second example is fairly close to working, but it has a fatal flaw. If you add some debugging statements you'll see that frame1 and frame2 are None. Thus, any widgets with those as a parent actually end up in the root window.
This is because foo().bar() always returns the result of .bar(). In tkinter, .grid(...) always returns None, so Frame(...).grid(...) will always return None.
The best practice is to always separate widget creation from widget layout. For example:
frame1 = tkinter.Frame(window)
frame2 = tkinter.Frame(window)
frame1.pack(side="top")
frame2.pack(side="top")
With that, frame1 and frame2 are properly set to the frames. And when that happens, the rest of the code in your second example works as you expect and the buttons are centered.
And in this example. with grid, if I'm using different frames the button just jump on the text box and messed up everything....
That happens for the same reason as mentioned above: you think you're using separate frames, but everything is going in the root window. Because they are all in the root window, and you put the text widget and a button in the same row and column, they overlap.
I also want to learn how to use the grid in the correct way if I have
all kinds of widgets and buttons one below the other without
"columnspan" or adjust the text length inside the buttons as well to
match the widgets above them...
grid is not the right choice in this specific case, since you aren't actually creating a grid. You can use it, but it requires more code than using pack. grid is the right choice if you're creating an actual grid. In this case you aren't.
Using grid in this case requires a little creativity. While it's not the only solution, I would recommend that you divide the bottom frame into five columns - an empty column on the left and right, and three columns in the middle for the buttons. The empty columns can be used to take up all extra space, forcing the middle columns to all be centered.
A best practice for using grid is that every window that uses grid to manage its children needs at least one row and one column with a non-zero weight. That lets tkinter know where to allocate any extra space, such as when the user resizes the window.
Here's a complete solution using grid:
import tkinter
window = tkinter.Tk()
frame0 = tkinter.Frame(window)
frame1 = tkinter.Frame(window)
window.grid_rowconfigure(0, weight=1)
window.grid_columnconfigure(0, weight=1)
frame0.grid(row=0, column=0, sticky="nsew")
frame1.grid(row=1, column=0, sticky="nsew")
frame0.grid_rowconfigure(0, weight=1)
frame0.grid_columnconfigure(0, weight=1)
textbox = tkinter.Text(frame0, width=70, height=15)
textbox.grid(row=0, column=0, sticky="nsew")
button1 = tkinter.Button(frame1, text="button1")
button2 = tkinter.Button(frame1, text="button2")
button3 = tkinter.Button(frame1, text="button3")
frame1.grid_rowconfigure(0, weight=1)
frame1.grid_columnconfigure((0,4), weight=1)
button1.grid(row=0, column=1)
button2.grid(row=0, column=2)
button3.grid(row=0, column=3)
window.mainloop()
Can someone explain to me please in which way it's better to use and how to understand it better...?
In summary, your instinct to use separate frames is the right place to start. You should divide your UI into logical groups, and use separate frames for each group. Then, you are free to pick either grid or pack for each group separately. However, you need to be diligent with grid to make sure that the sticky option is used correctly, and that you've set weights for all of the right columns.
And finally, you have to start with the proper practice of separating widget creation from widget layout.
I have had these kinds of problems before. Even though the .pack() and .grid() systems are excellent, when things are getting hectic you can use the .place() system. .place() allows you to exactly pin-point your tkinter and ttk widgets using x-y axis coordinates.
The coordinates (0,0) are not at the center but at the topmost left corner of your tkinter window.
Eg:
some_widget_name = Button(root, text="Click me!"....)
some_widget_name.place(x=100, y=50)
This will make your widget move right 100 pixels and move down 50 pixels from the topmost left corner.
However, sometimes when you really want to make the location of the widgets precise, you may have to do some trial-and-error to make it visually pleasing.
The column value on my button is set to 500, but the button doesn't seem to move that far to the right.
This is my code:
from tkinter import *
root = Tk()
root.geometry("500x500")
logo = Label(root,text = "New File Finder")
new_folder = Button(text = "browse new folder")
old_folder = Button(text = "browse old folder")
logo.grid(row=0, column=8)
new_folder.grid(row=1,column=1)
old_folder.grid(row=1,column=500)
root.mainloop()
As Novel mentions, empty columns have a width of 0, so your column=8 has the same effect as column=2, and column=500 as column=3. And of course your column 0 has a width of zero. You could use .pack instead of .grid, as Novel suggests, but don't mix .pack and .grid into the same container widget, which is the root window in this case. However, you can achieve what you want via the .grid sticky arg, but you also need to set a column weight for the column containing the old_folder Button. Eg:
import tkinter as tk
root = tk.Tk()
root.geometry("500x500")
root.columnconfigure(2, weight=1)
logo = tk.Label(root, text="New File Finder")
new_folder = tk.Button(root, text="browse new folder")
old_folder = tk.Button(root, text="browse old folder")
logo.grid(row=0, column=1)
new_folder.grid(row=1,column=0)
old_folder.grid(row=1,column=2, sticky="e")
root.mainloop()
I also changed the "star" import, since that dumps 130 Tkinter names into your namespace, which is messy, a waste of space, and can cause name collisions, especially if you do other star imports.
As Bryan mentions, .pack(side=RIGHT) doesn't actually force the widget to the right side of the container, it merely tells .pack to pack the widget towards that side. So if you pack a bunch of widgets with .pack(side=RIGHT) they'll be packed from right to left instead of from the default top to bottom. You can control exactly where inside the widget's allotted space that you want the widget to be placed using the anchor arg. Also take a look at the various pad args. See the Pack docs for details.
I am trying to clear out a frame in the tkinter so that the new contents can be written (refresh information) but i could not manage to do it. I am aware of these
frame.destroy()
frame.pack_forget()
frame.grid_forget()
but frame.destroy() will totally remove the frame. And the other two also could not give me the result i want.What i need is just to clear up every items in the frame but the frame itself will stay. Is there anyway to do it?
https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/universal.html
w.winfo_children()
Returns a list of all w's children, in their
stacking order from lowest (bottom) to highest (top).
for widget in frame.winfo_children():
widget.destroy()
Will destroy all the widget in your frame. No need for a second frame.
pack_forget and grid_forget will only remove widgets from view, it doesn't destroy them. If you don't plan on re-using the widgets, your only real choice is to destroy them with the destroy method.
To do that you have two choices: destroy each one individually, or destroy the frame which will cause all of its children to be destroyed. The latter is generally the easiest and most effective.
Since you claim you don't want to destroy the container frame, create a secondary frame. Have this secondary frame be the container for all the widgets you want to delete, and then put this one frame inside the parent you do not want to destroy. Then, it's just a matter of destroying this one frame and all of the interior widgets will be destroyed along with it.
For clear frame, first need to destroy all widgets inside the frame,. it will clear frame.
import tkinter as tk
from tkinter import *
root = tk.Tk()
frame = Frame(root)
frame.pack(side="top", expand=True, fill="both")
lab = Label(frame, text="hiiii")
lab.grid(row=0, column=0, padx=10, pady=5)
def clearFrame():
# destroy all widgets from frame
for widget in frame.winfo_children():
widget.destroy()
# this will clear frame and frame will be empty
# if you want to hide the empty panel then
frame.pack_forget()
frame.but = Button(frame, text="clear frame", command=clearFrame)
frame.but.grid(row=0, column=1, padx=10, pady=5)
# then whenever you add data in frame then you can show that frame
lab2 = Label(frame, text="hiiii")
lab2.grid(row=1, column=0, padx=10, pady=5)
frame.pack()
root.mainloop()
I'm new to Tkinter, and I've got a Listbox widget that I'd like to automatically-resize when changing the main window's size.
Essentially I would like to have a fluid height/width Listbox. If someone can point me to some documentation or provide a bit a code / insight, I'd appreciate it.
You want to read up on the geometry managers pack and grid, which lets you place widgets in a window and specify whether they grow and shrink or not. There's a third geometry manager, place, but it's not used very often.
Here's a simple example:
import tkinter as tk
root = tk.Tk()
scrollbar = tk.Scrollbar(root, orient="vertical")
lb = tk.Listbox(root, width=50, height=20, yscrollcommand=scrollbar.set)
scrollbar.config(command=lb.yview)
scrollbar.pack(side="right", fill="y")
lb.pack(side="left",fill="both", expand=True)
for i in range(0,100):
lb.insert("end", "item #%s" % i)
root.mainloop()
If you wish to use grid instead of pack, remove the two lines that call pack and replace them with these four lines:
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
scrollbar.grid(row=0, column=1, sticky="ns")
lb.grid(row=0, column=0, sticky="nsew")
Note that with grid you have to take the extra step to configure the weight for the row and column that contains the listbox, otherwise tkinter won't allocate any extra space to the widget.
The two main ways to allow a listbox to stretch when the window is resized are using the .pack() or .grid() methods.
SPECS:
Windows 7, Python 3.8.1, tkinter version: 8.6
.pack()
I found the easiest way to do this is by using the .pack() method, and utilizing the fill= & expand=True options.
import tkinter as tk
root=tk.Tk() #Creates the main window
listbox=tk.Listbox(root) #Create a listbox widget
listbox.pack(padx=10,pady=10,fill=tk.BOTH,expand=True) #fill=tk.BOTH, stretch vertically and horizontally
#fill=tk.Y, stretch vertically
#fill=tk.X, stretch horizontally
If your listbox is placed in a frame, the frame will also need to use the fill= & expand=True options.
import tkinter as tk
root=tk.Tk()
frame1=tk.Frame(root)
frame1.pack(fill=tk.BOTH, expand=True)
listbox=tk.Listbox(frame1)
listbox.pack(padx=10,pady=10,fill=tk.BOTH,expand=True)
.grid()
The alternative technique is to use the .grid() method and utilize thesticky= option. In addition, you will need to configure the row and column that the listbox resides in.
import tkinter as tk
root=tk.Tk() #create window
root.columnconfigure(0,weight=1) #confiugures column 0 to stretch with a scaler of 1.
root.rowconfigure(0,weight=1) #confiugures row 0 to stretch with a scaler of 1.
listbox=tk.Listbox(root)
listbox.grid(row=0,column=0,padx=5,pady=5,sticky='nsew')
The sticky option causes the listbox to stick to the "North" (Top), "South" (Bottom), "East" (Right), and "West" (Left) sides of the cell as it is stretched.
If your listbox is placed within a frame, you will need to configure the column and row that the frame is in, along with configure the column and row that the listbox is in.
import tkinter as tk
root=tk.Tk() #create window
root.columnconfigure(0,weight=1)
root.rowconfigure(0,weight=1)
frame1=tk.Frame(root)
frame1.grid(row=0,column=0,sticky='nsew')
frame1.columnconfigure(0,weight=1)
frame1.rowconfigure(0,weight=1)
listbox=tk.Listbox(frame1)
listbox.grid(row=0,column=0,padx=5,pady=5,sticky='nsew')
.pack() & .grid()
Now there is one other technique, but some people frown on it. The third technique is to utilize the .pack() method and .grid() method in the same script. You can mix different geometry management method in the same script as long as only a one management type is used per container. You can see an example of this below.
import tkinter as tk
root=tk.Tk() #create window
frame1=tk.Frame(root) #container: root
frame1.pack(fill=tk.BOTH,expand=True)
frame1.columnconfigure(0,weight=1)
frame1.rowconfigure(0,weight=1)
frame1.rowconfigure(1,weight=1)
listbox=tk.Listbox(frame1) #container: frame1
listbox.grid(row=0,rowspan=2,column=0,padx=5,pady=5,sticky='nsew')
btn1=tk.Button(frame1,text='Demo1') #container: frame1
btn1.grid(row=0,column=1, padx=5, pady=5)
btn2=tk.Button(frame1,text='Demo2') #container: frame1
btn2.grid(row=1,column=1, padx=5, pady=5)
frame2=tk.Frame(root) #container: root
frame2.pack()
btn3=tk.Button(frame2,text='Demo3') #container: frame2
btn3.grid(row=0,column=0)
You can see above that the frames used .pack() while the listbox and buttons used .grid(). This was possible because the frames resided within the root container, while the listbox and buttons resided within their respective frames.
To check you tkinter version use:
import tkinter as tk
print(tk.TkVersion)
If you would like to learn about the differences between fill and expand, please see the following link.
https://effbot.org/tkinterbook/pack.htm