I'm using python 3.4 and tkinter. I have a notebook with two pages. I have a listbox that needs to be on both pages.
I'm wondering if I can setup the listbox once and use it on both pages, or if I need to setup a separate listbox on each page and manage both as the list box in one page changes?
You cannot pack/grid/place the listbox inside two different frames simultaneously.
However, you can re-pack/grid/place the listbox each time the notebook tab changes. To do so, I used the <<NotebookTabChanged>> event which is triggered each time the notebook tab changes:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
notebook = ttk.Notebook(root)
notebook.pack()
frame1 = tk.Frame(notebook, bg='red', width=400, height=400)
frame1.pack_propagate(False)
frame2 = tk.Frame(notebook, bg='blue', width=400, height=400)
frame2.pack_propagate(False)
notebook.add(frame1, text='frame 1')
notebook.add(frame2, text='frame 2')
var = tk.StringVar(root, 'a b c d e f g')
listbox = tk.Listbox(notebook, listvariable=var)
def display_listbox(event):
tab = notebook.tabs()[notebook.index('current')]
listbox.pack(in_=tab)
notebook.bind('<<NotebookTabChanged>>', display_listbox)
root.mainloop()
Explanations about display_listbox:
notebook.tabs() returns (frame1, frame2) (i.e the tuple of the tabs)
notebook.index('current') returns the index of the currently visible tab
the in_ option can be used to specify the widget in which we want to pack the listbox (it also works with grid and place)
Sharing the same listvariable between the two list boxes is sufficient to keep them synchronized.
import tkinter as tk
from tkinter import ttk
rootWin = tk.Tk()
rootWin.title('Talk Like A Pirate')
listvar = tk.StringVar()
#create a notebook
pirateBook = ttk.Notebook(rootWin)
#create a tab
tab1 = tk.Frame(pirateBook)
#create a listbox in the tab
listbx1 = tk.Listbox(tab1,
listvariable=listvar,
height=21,
width=56,
selectmode='single'
)
listbx1.pack()
#create another tab
tab2 = tk.Frame(pirateBook)
#create a listbox in the second tab
listbx2 = tk.Listbox(tab2,
listvariable=listvar,
height=21,
width=56,
selectmode='single'
)
listbx2.pack()
#add the tabs to the notebook
pirateBook.add(tab1, text="Tab 1")
pirateBook.add(tab2, text="Tab 2")
#pack the notebook
pirateBook.pack()
#you can access the listbox through the listvariable, but that takes
# a list as the argument so you'll need to build a list first:
ls=list() #or you could just ls = []
#build a list for the list box
ls.append("Arr, matey!")
ls.append("Dead men tell no tales!")
ls.append("Heave ho, me hearties!")
ls.append("I'll cleave ye t'yer brisket a'fore sendin' ye to Davey Jones Locker!")
#then just set the list variable with the list
listvar.set(ls)
#or you can manipulate the data using Listbox()'s data manipulation
# methods, such as .insert()
listbx1.insert(1, "Shiver me timbers!")
listbx2.insert(3, "Yo ho ho!")
listbx1.insert(5, "Ye scurvy dog!")
listbx2.insert('end', "Ye'll be walking the plank, Sharkbait!")
#you'll see this if you run this program from the command line
print(listbx1.get(5))
rootWin.mainloop()
You would do this a little bit differently if you had your tabs in their own classes, making the common variable global, for instance, but the same idea still applies: sharing a common listvariable.
Related
I created a main root with two frames.
-One frame is for program toolbar.
-Other frame is for canvas where data will be displayed and a scrollbar widget.
-Inside of the canvas is a third smaller frame which will be used for scrolling trough data.
However, when I try to define new widgets and place them on that third smaller frame, nothing happens. I'm defining new widgets inside of a function call of a button command. I have also tried declaring everything as global variables but without luck.
Hint: I tried placing the code from the function to the top level of the code and it works. Also, if I try to mount these widgets on the toolbar frame it also works. It seems that the only thing I can't do is to mount these new widgets on the small frame that is inside the canvas.
I used a simple for loop to create labels just for testing.
Could anyone tell what I am doing wrong?
from tkinter import *
from tkinter import ttk
#Creating main window
root = Tk()
root.resizable(width=False, height=False)
#Defining Background
toolbar = Frame(root, width=613, height=114)
toolbar.grid(row=0, column=0)
background_frame = Frame(root, width=615, height=560)
background_frame.grid(row=1, column=0)
background = Canvas(background_frame, width=615, height=560)
background.pack(side=LEFT, fill=BOTH, expand=1)
scroll_bar = ttk.Scrollbar(background_frame, orient=VERTICAL, command=background.yview)
scroll_bar.pack(side=RIGHT, fill=Y)
background.configure(yscrollcommand=scroll_bar.set)
background.bind('<Configure>', lambda e:background.configure(scrollregion = background.bbox('all')))
second_frame = Frame(background)
background.create_window(150,100, window=second_frame, anchor='nw')
def confirm1():
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
show_labels = Button(toolbar, text= "Show labels", fg="black", command=confirm1)
show_labels.grid(row=0, column=2)
root.mainloop()
Picture of the app so far
I surely can't reproduce the issue with your current code, but looking at the previous edit it is pretty clear what your problem is.
(taken from your previous edit)
def confirm1():
global background_image1
background.delete('all') # <--- this line of code
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
Here you delete all your items from your canvas:
background.delete('all')
hence no item appears.
You should instead delete only those items that you want to remove by passing the id or tags to delete method. You can delete multiple items together at once by giving the same tags.
Another option would be to recreate the frame item again on canvas using create_window (Do note: your frame is not deleted/destroyed, it's only removed from canvas)
So I have this program written in Python Tkinter, it has two frames on two different tabs. On the first there is a button, which should create a third tab with the same frame as the first tab. However, instead of doing that, its replacing the first frame. How do I fix this?
Here is my code so far:
# Imports
from tkinter import *
from tkinter import ttk
# Screen
root = Tk()
root.geometry("600x600")
root.title("Example")
# Create New Tab Function
def CreateANewTabFunc():
TabControl.add(Frame1, text="Tab 3")
# Tab Control - Used for Placing the Tabs
TabControl = ttk.Notebook(root)
TabControl.pack()
# Frame 1 = Main Frame
Frame1 = Frame(TabControl, width=500, height=500, bg="Orange")
Frame1.pack()
# Frame 2 = Settings or Something Else
Frame2 = Frame(TabControl, width=500, height=500, bg="Green")
Frame2.pack()
TabControl.add(Frame1, text="Tab 1")
TabControl.add(Frame2, text="Tab 2")
ExampleButton = Button(Frame1, text="Click Here to Add New Tab to notebook", command=CreateANewTabFunc)
ExampleButton.pack()
# Mainloop
root.mainloop()
You can't use the same frame on two tabs. If you want to create a third tab, you need to create a third frame.
def CreateANewTabFunc():
another_frame = Frame(TabControl, bg="pink")
TabControl.add(another_frame, text="Tab 3")
I'm making a GUI and I'm stuck trying to resize a listbox. The listbox is supposed to expand to fill the frame but instead, the frame shrinks to fit the listbox.
Thanks, in advance, for any help.
I have tried many variations of the code, but none seem to work, so I simplified the code (it still does't work) to put it on here.
import tkinter as tk
w = tk.Tk() # New window
f = tk.Frame(w, width=300, height=500, bg='red') # New frame with specific size
f.grid_propagate(0)
f.grid(row=0, column=0)
lb = tk.Listbox(f, bg='blue') # New listbox
lb.pack(fill=tk.BOTH, expand=True)
I ran these sequentially in IDLE and the frame appears (in red) at the correct size, however, when I pack the listbox, the whole window shrinks to the size of the listbox (and turns completely blue, which is expected).
In my experience, turning off geometry propagation is almost never the right solution. With a couple of decades of using tk and tkinter I've done that only two or three times for very specific edge cases.
Tkinter is very good at making the widget the best size based on the set of widgets you're using. Turning off propagation means you are responsible for doing all calculations to get the window to look right, and your calculations may not be correct when your program runs on a machine with different fonts or a different resolution. Tkinter can handle all of that for you.
Unfortunately, with such a small code example and without knowing your end goal it's hard to solve your layout problems. If your goal is to have a window that is 300x500, the best solution is to make the window that size and then have the frame fill the window, which is easier to do with pack than grid:
import tkinter as tk
w = tk.Tk() # New window
w.geometry("300x500")
f = tk.Frame(w, bg='red') # New frame with specific size
f.pack(fill="both", expand=True)
lb = tk.Listbox(f, bg='blue') # New listbox
lb.pack(fill=tk.BOTH, expand=True)
w.mainloop()
Thank you so much to Tls Chris for your answer and explanation. What I didn't realize is that grid_propagate() and pack_propagate() also affect a widget's children.
Therefore, with the help of Tls Chris, I have fixed my code and it now expands the listbox and doesn't shrink the frame.
Fixed code:
import tkinter as tk
w = tk.Tk() # New window
f = tk.Frame(w, width=300, height=500, bg='red') # New frame
f.grid_propagate(False) # Stopping things (that use grid) resizing the frame
f.pack_propagate(False) # Stopping things (that use pack) resizing the frame
f.grid(row=0, column=0)
lb = tk.Listbox(f, bg='blue') # New listbox
lb.pack(fill=tk.BOTH, expand=True) # Packing the listbox and making it expand and fill the frame
Edit: I think you actually want the listbox to expand to fit the frame. Amended the code to do that as an option
I haven't used pack much but I suspect that grid_propagate changes the behaviour of the grid geometry manager but not of the pack manager.
The below lets app() run with or without propagate set. It uses the grid geometry manager throughout.
import tkinter as tk
def app(propagate = False, expand = False ):
w = tk.Tk() # New window
tk.Label( w, text = 'Propagate: {} \nExpand: {}'.format(propagate, expand) ).grid()
f = tk.Frame(w, width=300, height=500, bg='red') # New frame with specific size
f.grid_propagate(propagate)
f.grid( row=1, column=0 )
lb = tk.Listbox(f, bg='blue') # New listbox
if expand:
f.columnconfigure(0, weight = 1 )
f.rowconfigure(0, weight = 1 )
# lb.pack(fill=tk.BOTH, expand=True)
lb.grid( row=0, column=0, sticky = 'nsew' )
# My guess is that grid_propagate has changed the behaviour of grid, not of pack.
lb.insert(tk.END, 'Test 1', 'Test 2', 'Test 3')
w.mainloop()
This changes the listbox geometry manager to grid. Run app() as below.
app(True, True) # propagate and Expand
app(False, True) # no propagate but expand
app(True, False) # propagate without expand
app() # no propagate or expand
I'm currently working on an invoice generator in python tkinter for my coursework. I am quite new to programming and I have created a basic login page so when the login button is pressed (haven't setup restraints yet) the GUI moves to the next 'screen.' I am using frames to do this. However, on the next 'page' I can only pack() widgets, if I try and place them or use a grid they simply don't appear and I get an empty GUI window.
#!/usr/bin/python
# -- coding: utf-8 --
import tkinter as tk
root = tk.Tk()
def go_to_login():
f1.pack()
f2.pack_forget()
def go_to_first():
f1.pack_forget()
f2.pack()
root.geometry('1280x800')
root.title('Invoice')
f1 = tk.Frame(root)
label_US = tk.Label(f1, text='Username')
label_US.pack()
label_PW = tk.Label(f1, text='Password')
label_PW.pack()
entry_US = tk.Entry(f1)
entry_US.pack()
entry_PW = tk.Entry(f1, show='*')
entry_PW.pack()
checkbox_LI = tk.Checkbutton(f1, text='Keep me logged in')
checkbox_LI.pack()
but_LI = tk.Button(f1, text='login', command=go_to_first)
but_LI.pack()
but_QT = tk.Button(f1, text='Quit', command=quit)
but_QT.pack()
f2 = tk.Frame(root)
but_LO = tk.Button(f2, text='Logout', command=go_to_login)
but_LO.pack() # try to change pack here
but_HP = tk.Button(f2, text='Help')
but_HP.pack() # try to change pack here
but_NX1 = tk.Button(f2, text='Next', command=quit)
but_NX1.pack() # try to change pack here
f1.pack()
root.mainloop()
What I basically want is to be able to place or use grid to set the locations of my widgets also on the second frame, but unless I use pack I get an empty GUI screen. What have I done wrong or how can I place the widgets instead to packing them?
I'm not sure if this is the best way to do things and I have no experience using classes etc but I'm open to suggestions.
As long as you specify the row and column when using .grid(), the code works fine for both 'screens'. I can think of two possible reasons why you're code wasn't working
1) If you try and use .pack and .grid for two labels in the same frame, you will get an error
2) There may be something else in your code causing your error, if so, add it to your question
Anyway, here's the code that worked for me:
f2 = tk.Frame(root)
but_LO = tk.Button(f2, text='Logout', command=go_to_login)
but_LO.grid(row = 0, column = 0) # specify row and column
but_HP = tk.Button(f2, text='Help')
but_HP.grid(row = 1, column = 0)
but_NX1 = tk.Button(f2, text='Next', command=quit)
but_NX1.grid(row = 2, column = 0)
Im new to python so please forgive my Noob-ness. Im trying to create a status bar at the bottom of my app window, but it seems every time I use the pack() and grid() methods together in the same file, the main app window doesn't open. When I comment out the line that says statusbar.pack(side = BOTTOM, fill = X) my app window opens up fine but if I leave it in it doesn't, and also if I comment out any lines that use the grid method the window opens with the status bar. It seems like I can only use either pack() or grid() but not both. I know I should be able to use both methods. Any suggestions? Here's the code:
from Tkinter import *
import tkMessageBox
def Quit():
answer = tkMessageBox.askokcancel('Quit', 'Are you sure?')
if answer:
app.destroy()
app = Tk()
app.geometry('700x500+400+200')
app.title('Title')
label_1 = Label(text = "Enter number")
label_1.grid(row = 0, column = 0)
text_box1 = DoubleVar()
input1 = Entry(app, textvariable = text_box1)
input1.grid(row = 0, column = 2)
statusbar = Label(app, text = "", bd = 1, relief = SUNKEN, anchor = W)
statusbar.pack(side = BOTTOM, fill = X)
startButton = Button(app, text = "Start", command = StoreValues).grid(row = 9, column = 2, padx = 15, pady = 15)
app.mainloop()
Any help is appreciated! Thanks!
You cannot use both pack and grid on widgets that have the same master. The first one will adjust the size of the widget. The other will see the change, and resize everything to fit it's own constraints. The first will see these changes and resize everything again to fit its constraints. The other will see the changes, and so on ad infinitum. They will be stuck in an eternal struggle for supremacy.
While it is technically possible if you really, really know what you're doing, for all intents and purposes you can't mix them in the same container. You can mix them all you want in your app as a whole, but for a given container (typically, a frame), you can use only one to manage the direct contents of the container.
A very common technique is to divide your GUI into pieces. In your case you have a bottom statusbar, and a top "main" area. So, pack the statusbar along the bottom and create a frame that you pack above it for the main part of the GUI. Then, everything else has the main frame as its parent, and inside that frame you can use grid or pack or whatever you want.
Yeah thats right. In following example, i have divided my program into 2 frames. frame1 caters towards menu/toolbar and uses pack() methods wherein frame2 is used to make login page credentials and uses grid() methods.
from tkinter import *
def donothing():
print ('IT WORKED')
root=Tk()
root.title(string='LOGIN PAGE')
frame1=Frame(root)
frame1.pack(side=TOP,fill=X)
frame2=Frame(root)
frame2.pack(side=TOP, fill=X)
m=Menu(frame1)
root.config(menu=m)
submenu=Menu(m)
m.add_cascade(label='File',menu=submenu)
submenu.add_command(label='New File', command=donothing)
submenu.add_command(label='Open', command=donothing)
submenu.add_separator()
submenu.add_command(label='Exit', command=frame1.quit)
editmenu=Menu(m)
m.add_cascade(label='Edit', menu=editmenu)
editmenu.add_command(label='Cut',command=donothing)
editmenu.add_command(label='Copy',command=donothing)
editmenu.add_command(label='Paste',command=donothing)
editmenu.add_separator()
editmenu.add_command(label='Exit', command=frame1.quit)
# **** ToolBar *******
toolbar=Frame(frame1,bg='grey')
toolbar.pack(side=TOP,fill=X)
btn1=Button(toolbar, text='Print', command=donothing)
btn2=Button(toolbar, text='Paste', command=donothing)
btn3=Button(toolbar, text='Cut', command=donothing)
btn4=Button(toolbar, text='Copy', command=donothing)
btn1.pack(side=LEFT,padx=2)
btn2.pack(side=LEFT,padx=2)
btn3.pack(side=LEFT,padx=2)
btn4.pack(side=LEFT,padx=2)
# ***** LOGIN CREDENTIALS ******
label=Label(frame2,text='WELCOME TO MY PAGE',fg='red',bg='white')
label.grid(row=3,column=1)
label1=Label(frame2,text='Name')
label2=Label(frame2,text='Password')
label1.grid(row=4,column=0,sticky=E)
label2.grid(row=5,column=0,sticky=E)
entry1=Entry(frame2)
entry2=Entry(frame2)
entry1.grid(row=4,column=1)
entry2.grid(row=5,column=1)
chk=Checkbutton(frame2,text='KEEP ME LOGGED IN')
chk.grid(row=6,column=1)
btn=Button(frame2,text='SUBMIT')
btn.grid(row=7,column=1)
# **** StatusBar ******************
status= Label(root,text='Loading',bd=1,relief=SUNKEN,anchor=W)
status.pack(side=BOTTOM, fill=X)
For single widgets, the answer is no: you cannot use both pack() and grid() inside one widget. But you can use both for different widgets, even if they are all inside the same widget. For instance:
my_canvas=tk.Canvas(window1,width=540,height=420,bd=0)
my_canvas.create_image(270,210,image=bg,anchor="center")
my_canvas.pack(fill="both",expand=True)
originallbl = tk.Label(my_canvas, text="Original", width=15).grid(row=1,column=1)
original = tk.Entry(my_canvas, width=15).grid(row=1,column=2)
I used pack() in order to set canvas, I used grid() in order to set labels, buttons, etc. in the same canvas