I've been trying to create a scrollbar on a frame by trying to combine two codes written by Bryan Oakley. ( The code is not mine).
The first one is code the creates multiple frame using classes and the other one uses canvas to create a scrollable frame.
import tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, Example):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
The original code for does not use parent nor controller as parameters but uses roots instead. By switching to parent and controller the labels created in the function "inmatning" are moved all the way to the write and the scrollbar doesn't show.
class Example(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.canvas, background="#ffffff")
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((4,4), window=self.frame, anchor="nw",
tags="self.frame")
self.frame.bind("<Configure>", self.onFrameConfigure)
self.inmatning()
def inmatning(self):
allbio = läs_fil()
x = 0
while x < len(allbio):
label = tk.Label(self, text="\n"+allbio[x].namn)
label.pack()
lista =["Barn", "Vuxna", "Penionärer"]
l = 0
while l < len(lista):
label1= tk.Label(self, text="Antal " + lista[l])
label1.pack()
enter1 = tk.Entry(self)
enter1.pack()
l=l+1
x=x+1
def onFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
This here is normal frame that works without any problem ( not using canvas of course )
class Menu(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Här kan du väljer mellan de följande 6 alternativen")
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Example",
command=lambda: controller.show_frame("Example"))
button1.pack()
button2 = tk.Button(self, text="Beläggning",
command=lambda: controller.show_frame("Beläggning"))
button2.pack()
Take a look at this code:
def inmatning(self):
...
while x < len(allbio):
label = tk.Label(self, text="\n"+allbio[x].namn)
What is it doing? It is creating some labels, each with a parent of self. What is self? It's a frame that has an canvas, and inside that canvas is a frame. Widgets that are inside that inner frame will be scrolled when the canvas is scrolled.
The whole point of the scrollable frame is that put put widgets in the inner frame, not in the outer frame. It's the inner frame (the one inside the canvas) that should contain all of the widgets.
Try changing your label creation to this:
label = tk.Label(self.frame, text="\n"+allbio[x].namn)
I don't know if that's the only problem, but it's certainly part of the problem. I can't simply cut and paste your code to test it out since you have code spread among several blocks, and didn't include all of your code.
Related
Just started learning Tkinter and was hoping someone could help me. I've been trying to bind a keyboard character (Enter button) to a tk button following this example and not getting anywhere.
Say I take the button (Enter) and try bind it nothing happens:
Enter.bind('<Return>', lambda:self.retrieve_Input(t))
If I bind to self instead using Lambda nothing happens also. I can get it to trigger if I remove the lambda but that's not the desired outcome
self.bind('<Return>', lambda:self.retrieve_Input(t))
My Code:
import sys
import tkinter as tk
from tkinter import ttk
class windows(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.wm_title("Test Application")
self.lift() #Bringing the GUI to the front of the screen
main_frame = tk.Frame(self, height=400, width=600) #Creating a main Frame for all pages
main_frame.pack(side="top", fill="both", expand=True)
main_frame.grid_rowconfigure(0, weight=1) #Configuring the location of the main frame using grid
main_frame.grid_columnconfigure(0, weight=1)
# We will now create a dictionary of frames
self.frames = {}
for F in (MainPage, CompletionScreen): #Add the page components to the dictionary.
page = F(main_frame, self)
self.frames[F] = page #The windows class acts as the root window for the frames.
page.grid(row=0, column=0, sticky="nsew")
self.show_page(MainPage) #Method to switch Pages
def show_page(self, cont):
frame = self.frames[cont]
frame.tkraise()
##########################################################################
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#switch_window_button = tk.Button(self, text="Go to the Side Page", command=lambda: controller.show_page(SidePage))
#switch_window_button.pack(side="bottom", fill=tk.X)
tk.Label(self, text="Project Python Search Engine", bg='white').pack()
tk.Label(self, text="", bg='white').pack()
tk.Label(self, text="Song", bg='white').pack()
tk.Label(self, text="", bg='white').pack()
t = tk.Entry(self, bg='white', width = 50)
t.pack()
tk.Label(self, text="", bg='white').pack()
Enter = tk.Button(self, text='Search', command= lambda:self.retrieve_Input(t))
Enter.pack()
tk.Button(self, text="Latest Popular Songs", command=lambda:self.Popular_Songs(t)).pack() #Line 210 onwards
Enter.bind('<Return>', lambda:self.retrieve_Input(t))
def retrieve_Input(self, t):
print ("work")
print (t)
class CompletionScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Completion Screen, we did it!")
label.pack(padx=10, pady=10)
switch_window_button = ttk.Button(
self, text="Return to menu", command=lambda: controller.show_page(MainPage)
)
switch_window_button.pack(side="bottom", fill=tk.X)
if __name__ == "__main__":
App = windows()
App.mainloop()
I'm not really sure what I'm missing
Answer: The button probably doesn't have the keyboard focus. When I run your code and then use the keyboard to move the focus to the button, your binding works. You probably want to bind to the entry widget rather than the button since that's what will have the keyboard focus. – Thanks Bryan Oakley
I have some pages in a container, and I want the name box that the user is allowed to type into from one of my pages to update and be displayed on the other page. This is not a variable in all my pages, this is only something I want displayed in 1 of my pages. My app has many pages, but this is my minimal reproducible example of my problem.
I know when my "template" page is created, the text in the name box is blank, so I need to somehow pass the variable when the 'load_page' function is called, but I cannot figure out how to make this work. Any help is appreciated.
The code below gives the error: AttributeError: type object 'template' has no attribute 'name_box' - The problem I have is I do not know how to specify the name box from one page to grab the entered text, and then insert it into another box in another page.
See code below:
import tkinter as tk
from tkinter import font as tkfont, filedialog, messagebox
class SLS_v1(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title('SLS')
self.geometry("552x700")
self.resizable(False, False)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frames["MenuPage"] = MenuPage(parent=container, controller=self)
self.frames["template"] = template(parent=container, controller=self)
self.frames["MenuPage"].grid(row=0, column=0, sticky="nsew")
self.frames["template"].grid(row=0, column=0, sticky="nsew")
self.show_frame("MenuPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class MenuPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
name_label = tk.Label(self, text='Name:')
name_label.pack(pady=(20,0))
self.name_var = tk.StringVar()
self.name_entry = tk.Entry(self, width=10, textvariable=self.name_var)
self.name_entry.pack()
template = tk.Button(self, text='Template', height=3, width=20, bg='white', font=('12'),
command=lambda: self.load_page(controller))
template.pack(pady=50)
def load_page(self, controller):
controller.show_frame('template')
template.name_box.insert(tk.END, self.name_entry.var.get())
class template(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.grid(columnspan=10, rowspan=10)
top = tk.Label(self, height=3, width=80, bg='dark grey')
top.grid(row=0, column=0, columnspan=10)
self.back_btn = tk.Button(self, text='BACK', font=('Helvetica', '14'), bg='dark grey',
command=lambda: controller.show_frame('MenuPage'))
self.back_btn.grid(row=0, column=0, columnspan=2, padx=10, pady=10)
name_var = tk.StringVar()
name_box = tk.Entry(self, width=10, textvariable=name_var)
name_box.grid(row=1, column=1)
if __name__ == "__main__":
app = SLS_v1()
app.mainloop()
Edit: Fixed error in code.
You can solve this by importing GC and changing the function that is called when you click the template button to adding these few lines:
import gc
...
...
def load_page(self, controller):
controller.show_frame('template')
for obj in gc.get_objects():
if isinstance(obj, template):
obj.name_box.delete(0, tk.END)
obj.name_box.insert(tk.END, self.name_entry.get())
I am trying to align a label in the center of the window with two buttons under it, also centered. I have been googling and looking on here to figure out how to do it and I have found grid to be helpful but it is not doing what I expect. It works as I would expect if I put each of the widgets in a different row and column but if I put them in different rows and the same column, they just stay aligned left. What am I doing wrong with grid? Also, any suggestions on how I can improve the code overall would be appreciated.
I left out the LoadedMachine and CreateMachine classes because I don't feel they are needed. If they would be helpful, I can edit the question to add them.
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=True)
self.frames = {}
for F in (StartPage, LoadedMachine, CreateMachine):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0)
frame.config(bg='white')
self.show_frame('StartPage')
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
self.rowconfigure(2, weight=1)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.columnconfigure(2, weight=1)
welcome_label = tk.Label(self, text='Welcome', bg='green', fg='white', font=('Verdana', 80))
welcome_label.grid(row=0, column=1)
loadButton = tk.Button(self, text='Load an existing state machine', command=lambda: controller.show_frame('LoadedMachine'))
loadButton.config(highlightbackground='green', font=('Verdana', 18))
loadButton.grid(row=1, column=1)
createButton = tk.Button(self, text='Create a new state machine', command=lambda: controller.show_frame('CreateMachine'))
createButton.config(highlightbackground='green', font=('Verdana', 18))
createButton.grid(row=2, column=1)
if __name__ == '__main__':
app = App()
app.title('Cognitive State Machine')
app.geometry('800x600')
app.mainloop()
This is what I get:
I want the buttons to be closer together and closer to the label.
One suggestion is to first add some background colors when you create your frames for troubleshooting.
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self,bg="yellow")
container.pack(side='top', fill='both', expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
...
When you run this, you will see a bunch of yellow color, which means your StartPage frame is not filling up the space. So you need to change it:
for F in (StartPage,):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0,column=0,sticky="nesw")
frame.config(bg='green')
Now you can see your background becomes green colour which means your StartPage frame correctly scales. Finally you can work on your labels:
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.columnconfigure(0, weight=1)
...
On why you need to add a weight to your columns, there is an excellent post here.
Add padding to the grid to align it how you want
You can add padx or pady according to your need
loadButton.grid(row=1, column=1, padx=10, pady=20)
Helpfull link to further play with grid layout
Also instead of 'lambda' you could use 'partial' as we need to call the function from the command function and define it there.
in a [former question][1] received a perfect script from #acw1668 for creating popup-windows (see below).
How can this be rewritten in a form that the new windows are not popups but just a switch from one page to the next (the listboxes/candvas are not necessarily needed here)?
Edit: tried to amend the code according to #Bryan Oakley's suggestions.
My issue here: I do not manage to pass the list lst from the GUI class to the other page classes without an error message:
File "/.spyder-py3/temp.py", line 25, in __init__
frame = F(parent=container, controller=self)
TypeError: __init__() missing 1 required positional argument: 'lst'
What am I missing here?
And I do not understand what's happening here:
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self,)
self.frames[page_name] = frame
If somebody could explain, please?
import tkinter as tk
from tkinter import ttk
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.lst = ['a', 'b', 'c']
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self,)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage", self.lst)
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
def show_popup(self, page, lst):
win = page(self, lst)
win.grab_set() # make window modal
self.wait_window(win) # make window modal
class StartPage(tk.Frame):
def __init__(self, parent, controller, lst):
tk.Frame.__init__(self, parent)
self.controller = controller
self.lst = lst
# ------------------------------------------------------------------- #
label = tk.Label(self, text="Check this out")
label.pack(pady=10,padx=10)
# ------------------- create buttons ---------------------------------
button1 = ttk.Button(self, text="show all",
width = 25, command=lambda:
controller.show_popup(App, self.lst))
button1.pack(pady=10, padx=10)
button2 = ttk.Button(self, text="show page one",
width = 25, command=lambda:
controller.show_frame(PageOne))
button2.pack(pady=10, padx=10)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1")
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2")
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class App(tk.Toplevel):
def __init__(self, parent, lst):
tk.Toplevel.__init__(self, parent)
self.lst = lst
self.title('This is the pop up window')
self.geometry('400x200')
label = tk.Label(self, text=self.lst)
label.pack(side="top", fill="x", pady=10)
parent.grid_rowconfigure(0, weight = 1)
parent.grid_columnconfigure(0, weight = 1)
if __name__ == '__main__':
app = GUI()
app.mainloop()
[1]: http://stackoverflow.com/questions/41181809/how-to-open-and-close-another-window-with-scrollbar-in-tkinter-for-python-3-5/41182843?noredirect=1#comment69580999_41182843
Your class initializers are defined like this:
class StartPage(tk.Frame):
def __init__(self, parent, controller, lst):
In order to create an instance of this class it requires three arguments (plus self): parent, controller, and lst.
Now, let's look at how you're creating the instance:
frame = F(parent=container, controller=self,)
Notice how you have the parent and you have the controller, but you haven't passed in anything for lst. That is why the error states "missing 1 required positional argument: 'lst'" -- because you are literally missing one required argument named "lst".
To fix this problem, you simply need to provide this extra argument. For example:
frame = F(parent=container, controller=self, lst=self.lst)
HOWEVER, you probably shouldn't do that. The architecture of this little block of code you copied makes it possible to access values on the GUI class from any of the "page" classes without having to do any extra work.
Because this variable is an attribute of the GUI class, and you are passing a reference to the instance of the GUI class to each "page" (the controller attribute), you can access this data any time you want without having to pass it in at construction time. You can remove it from __init__ and from where you're creating the pages (ie: go back to the original code before your modifications), and then just use self.controller.lst whenever you need the value.
For example:
class SomePage(tk.Frame):
def __init__(self, parent, controller):
self.controller = controller
...
def some_function(self):
print("The value of 'lst' is:", self.controller.lst)
I want to build a Tkinter app in python 3.5. with a StartPage and a another window PageTwo that includes a table with a scolldownbar. I have tried to apply a framework from an online tutorial and the listbox example from another website.
My problem is: when I run the program both pages are loaded directly. How can I manage to let PageTwo only open on click on Button in StartPage, and then apply another button in PageTwo that closed PageTwo again and redirects to StartPage?
Second question: Alternatively to the listbox example I would like to use canvas with scrollbar on PageTwo. But how and where do I have to introduce the canvas? I get totally messed up with all the inheritances throughout the different classes.
If you would suggest a complete different setup, this would also be fine.
Many thanks for your help.
import tkinter as tk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame = StartPage(container, self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise() # zeigt Frame oben an
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Your choice?")
label.pack(pady=10,padx=10)
button1 = tk.Button(self, text="Open PageTwo",
width = 25, command=lambda: controller.show_frame(PageTwo))
button1.pack(pady=10, padx=10)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
master = tk.Tk()
scrollbar = tk.Scrollbar(master)
scrollbar.pack(side=tk.RIGHT, fill="y")
listbox = tk.Listbox(master, yscrollcommand=scrollbar.set)
for i in range(1000):
listbox.insert(tk.END, str(i))
listbox.pack(side=tk.LEFT, fill="both")
scrollbar.config(command=listbox.yview)
if __name__ == '__main__':
app = GUI()
app.mainloop()
To fix the issues:
initialize PageTwo only when the button is clicked
use Toplevel for popup window
use root as the StartPage
Below is a demo based on your posted code:
import tkinter as tk
from tkinter import ttk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
label = tk.Label(self, text="Your choice?")
label.pack(pady=10,padx=10)
button1 = ttk.Button(self, text="Open PageTwo", width=25, command=lambda: self.show_frame(PageTwo))
button1.pack(pady=10, padx=10)
button2 = ttk.Button(self, text="Open PageCanvas", width=25, command=lambda: self.show_frame(PageCanvas))
button2.pack(pady=10, padx=10)
def show_frame(self, page):
win = page(self)
# make window modal
win.grab_set()
self.wait_window(win)
class PageTwo(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title('Two')
scrollbar = tk.Scrollbar(self)
scrollbar.pack(side=tk.RIGHT, fill="y")
listbox = tk.Listbox(self, yscrollcommand=scrollbar.set)
for i in range(1000):
listbox.insert(tk.END, str(i))
listbox.pack(side=tk.LEFT, fill="both")
scrollbar.config(command=listbox.yview)
class PageCanvas(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title('Canvas')
self.geometry('400x600')
canvas = tk.Canvas(self, bg='white', scrollregion=(0, 0, 400, 20000))
canvas.pack(fill='both', expand=True)
vbar = tk.Scrollbar(canvas, orient='vertical')
vbar.pack(side='right', fill='y')
vbar.config(command=canvas.yview)
canvas.config(yscrollcommand=vbar.set)
for i in range(1000):
canvas.create_text(5, i*15, anchor='nw', text=str(i))
if __name__ == '__main__':
app = GUI()
app.mainloop()