switching windows in tkinter with classes - python-3.x

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)

Related

Python Tkinter: How reset a combobox label from selecting option in another combobox in a parent, controler, container model?

The code is correct (and very interesting indeed - From stackoverlflow, because facilitate working comboboxes in classes!). The code is a little long, but just to create the structure to make combobox work in this parent, controler, container model. On the other hand,comoboboxes are "burning my brains out". The hard part: I can reset the lower combobox clicking the button. But I need to reset it to 'I´m your friend' just choosing any option in upper combobox (without use of button). I've tried insert "self.combobox_HPFilter.set('I´m your friend')" in a function and insert this in the functions called "make_guess", unfortunately unsuccessfuly. I really appreciate any help.
# from Bryan Oakley and others from the "amazing" Stackoverflow
# https://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter/7557028#7557028
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
from tkinter import ttk
class SampleApp(tk.Tk):
'''
All the code is working.The sole issue is: to reset second combobox text direct from options in upper combobox
'''
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
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 = {}
for F in (StartPage, PageOne):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
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()
class StartPage(tk.Frame):
def make_guess01(self):
print("I've got a cute friend")
def make_guess02(self):
print('Ned is correct')
def make_guess03(self):
print('Patrick')
def make_guess04(self):
print('Best Hommer friend')
def labels_reset(self):
self.combobox_HPFilter.set('I´m your friend')
#-------------------------first combobox start
def change_Montage_combobox(self, event): # this method goes inside def montage_Combo(self):
myvar = (self.comboboxMontages.get())
# lines 1 2 3 4 say that values(function_name) in montageDict are functions in StartPage
function_name = self.montagesDict[myvar] #1 returns the function(method) in montageDic(value of key:value pair)
# just for reference: print(function_name)--> montage_Original (dictionary montagesDict value)
an_object = StartPage(self, tk.Frame) # 2--> the page (class) where function is
class_method = getattr(StartPage, function_name) # 3 returns method and says the page of the method
result = class_method(an_object) # 4 result is the method right for use
def montage_Combo(self):
self.montage_selector = tk.StringVar()
self.comboboxMontages = ttk.Combobox(self, values=sorted(list(self.montagesDict.keys())),
justify="center", textvariable=self.montage_selector, state="readonly",
)
self.comboboxMontages.bind('<<ComboboxSelected>>', lambda event: self.change_Montage_combobox(event))
self.comboboxMontages.pack()
self.comboboxMontages.set('Who is my friend')
#-------------------------first combobox end
#-------------------------second combobox start
def highPassFilter_Change_combobox(self, event): # this method goes inside def montage_Combo(self):
myvar = (self.combobox_HPFilter.get())
function_name = self.highPassFilterDict[myvar] # 1
an_object = StartPage(self, tk.Frame) # 2
class_method = getattr(StartPage, function_name) # 3
highPassFilter_result = class_method(an_object) # 4
def highPassFilter_Combo(self):
self.highPassFilter_selector = tk.StringVar()
self.combobox_HPFilter = ttk.Combobox(self, values=sorted(list(self.highPassFilterDict.keys())),
justify="center", textvariable=self.highPassFilter_selector,
state="readonly")
self.combobox_HPFilter.bind('<<ComboboxSelected>>', lambda event: self.highPassFilter_Change_combobox(event))
self.combobox_HPFilter.pack()
self.combobox_HPFilter.set('I´m your friend')
#-------------------------second combobox end
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.montagesDict = {'SpongeBob': 'make_guess01',
'Hommer': 'make_guess02'
}
self.highPassFilterDict = {'Patrick': 'make_guess03',
'Ned Flanders': 'make_guess04',
}
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="how reset lower combobox clicking upper combobox?",
command=self.labels_reset)
button1.pack()
self.montage_Combo()
self.highPassFilter_Combo()
#class PageOne just to make code play
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", font=controller.title_font)
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()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Python is both challenging and exciting, it seems a zen Koan. After "lots of try an error" and frustration...comes the light.
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
from tkinter import ttk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
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 = {}
for F in (StartPage, PageOne):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
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()
class StartPage(tk.Frame):
def make_guess01(self):
print("I've got a cute friend")
def make_guess02(self):
print('Ned is correct')
def make_guess03(self):
print('Patrick')
def make_guess04(self):
print('Best Hommer friend')
def labels_reset(self):
self.combobox_HPFilter.set('I´m your friend')
#-------------------------first combobox start
def change_Montage_combobox(self, event): # this method goes inside def montage_Combo(self):
myvar = (self.comboboxMontages.get())
# lines 1 2 3 4 say that values(function_name) in montageDict are functions in StartPage
function_name = self.montagesDict[myvar] #1 returns the function(method) in montageDic(value of key:value pair)
# just for reference: print(function_name)--> montage_Original (dictionary montagesDict value)
an_object = StartPage(self, tk.Frame) # 2--> the page (class) where function is
class_method = getattr(StartPage, function_name) # 3 returns method and says the page of the method
result = class_method(an_object) # 4 result is the method right for use
#################################
# answer: #
#################################
if self.combobox_HPFilter != '':
self.labels_reset()
else:
pass
def montage_Combo(self):
self.montage_selector = tk.StringVar()
self.comboboxMontages = ttk.Combobox(self, values=sorted(list(self.montagesDict.keys())),
justify="center", textvariable=self.montage_selector, state="readonly",
)
self.comboboxMontages.bind('<<ComboboxSelected>>', lambda event: self.change_Montage_combobox(event))
self.comboboxMontages.pack()
self.comboboxMontages.set('Who is my friend')
#-------------------------first combobox end
#-------------------------second combobox start
def highPassFilter_Change_combobox(self, event): # this method goes inside def montage_Combo(self):
myvar = (self.combobox_HPFilter.get())
function_name = self.highPassFilterDict[myvar] # 1
an_object = StartPage(self, tk.Frame) # 2
class_method = getattr(StartPage, function_name) # 3
highPassFilter_result = class_method(an_object) # 4
def highPassFilter_Combo(self):
self.highPassFilter_selector = tk.StringVar()
self.combobox_HPFilter = ttk.Combobox(self, values=sorted(list(self.highPassFilterDict.keys())),
justify="center", textvariable=self.highPassFilter_selector,
state="readonly")
self.combobox_HPFilter.bind('<<ComboboxSelected>>', lambda event: self.highPassFilter_Change_combobox(event))
self.combobox_HPFilter.pack()
self.combobox_HPFilter.set('I´m your friend')
def all_ComboBox_labels_reset(self):
self.combobox_HPFilter.set('I´m your friend')
#-------------------------second combobox start
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.montagesDict = {'SpongeBob': 'make_guess01',
'Hommer': 'make_guess02'
}
self.highPassFilterDict = {'Patrick': 'make_guess03',
'Ned Flanders': 'make_guess04',
}
label = tk.Label(self, text="This is the start page")
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="how reset lower combobox clicking upper combobox?",
command=self.labels_reset)
button1.pack()
self.montage_Combo()
self.highPassFilter_Combo()
#class PageOne just to make code play
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()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I used method self.combobox_HPFilter != '': because in my code there are multiple comboboxes to be reseted. "If" because at beginnig of script they are empty.
Excuse me I dont get the (-1)in my question, it is a question. It was usefull to me a beginner and no one answered until I found the answer. Indulge me and be more proactive explaining it. Best.

Refresh a tkinter frame on button press

I am utilising code from Switch between two frames in tkinter to make my GUI. I have a frame with refresh and restart buttons.
My original idea was for the restart button to go to the start page as in the code below but if this frame is called again it has the entries from the previous attempt still showing.
I've tried.destroy() for the refresh button but then I get an traceback message when I call the PLG frame again.
For the restart button, how would I close the PLG frame, go to the Start page and then be able to select PLG again?
For the refresh button, how would I remove the entries in the entry widget and text arrear so that another entry can be made and new answer returned?
class PLG(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Enter the engine size (cc) below", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
vcmd = (self.register(self.onValidate), '%S')
self.weight_entry = tk.Entry(self, validate='key', vcmd = vcmd)
self.weight_entry.pack(pady = 10)
tk.Button(self, text='Click here to display price', command=self.show_option).pack()
self.text = tk.Text(self)
self.text.pack(pady = 10)
self.text.config(state='disabled')
restart_button = tk.Button(self, text="Restart",
command=self.restart)
restart_button.pack()
refresh_button = tk.Button(self, text="Refresh", command=self.refresh).pack()
refresh_button.pack()
def onValidate(self,S):
if S in ['0','1','2', '3', '4', '5', '6', '7', '8', '9']:
return True
else:
self.bell() # adds a sound effect to error
self.text.delete(1.0, tk.END) # deletes the error message if valid entry provided
self.text.insert(tk.END, "Invalid entry. Please try again.") # displays an error message if a number not provided in entry widget
return False
def restart(self):
self.refresh()
show_frame("StartPage")
def refresh(self):
self.text.config(state='normal')
self.weight_entry.delete(0,tk.END)
self.text.delete("1.0", "end")
Advice on both elements would be appreciated.
The OP's question was about clearing input fields so prior input isn't still in the page when you expected to see empty fields for fresh input. I'm posting the finished code while omitting features of the OP's original code which were irrelevant to his question so the solution could easily be seen in its full context. I had been looking to solve this problem with this same frame-switching code from Bryan Oakley's famed tutorials on this topic. I also included an alternate version using grid_remove instead of tkraise since this is how I had solved the problem of ever-active but unseen frames trying to participate in the focus traversal as the user tries to tab through the page. It also kept the frames from all trying to be the same size.
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 = {}
# alternate ways to create the frames & append to frames dict: comment out one or the other
for F in (StartPage, PLG):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
# self.frames["StartPage"] = StartPage(parent=container, controller=self)
# self.frames["PLG"] = PLG(parent=container, controller=self)
# self.frames["StartPage"].grid(row=0, column=0, sticky="nsew")
# self.frames["PLG"].grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
# alternate version of show_frame: comment out one or the other
def show_frame(self, page_name):
for frame in self.frames.values():
frame.grid_remove()
frame = self.frames[page_name]
frame.grid()
# 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
label = tk.Label(self, text="start page")
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One", command=lambda: controller.show_frame("PLG"))
button1.pack()
button2 = tk.Button(self, text="focus traversal demo only")
button2.pack()
button2.focus_set()
button3 = tk.Button(self, text="another dummy button")
button3.pack()
lbl = tk.Label(self, text="tkraise messes up focus traversal\nwhich you can see by testing the two versions of show_frame.()\nUsing grid_remove instead of tkraise solves that,\nwhile preventing frames from being unable to resize to fit their own contents.")
lbl.pack()
class PLG(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Enter something below; the two buttons clear what you type.")
label.pack(side="top", fill="x", pady=10)
self.wentry = tk.Entry(self)
self.wentry.pack(pady = 10)
self.text = tk.Text(self)
self.text.pack(pady = 10)
restart_button = tk.Button(self, text="Restart", command=self.restart)
restart_button.pack()
refresh_button = tk.Button(self, text="Refresh", command=self.refresh)
refresh_button.pack()
def restart(self):
self.refresh()
self.controller.show_frame("StartPage")
def refresh(self):
self.wentry.delete(0, "end")
self.text.delete("1.0", "end")
# set focus to any widget except a Text widget so focus doesn't get stuck in a Text widget when page hides
self.wentry.focus_set()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
The first step is to have your button call a proper function rather than using lambda. Unless you understand why and when to use lambda, it usually just makes the code harder to write and understand.
Once you have it call a function, you can use the function to clear the entries.
Example:
class PLG(tk.Frame):
def __init__(self, parent, controller):
...
tk.Button(self, text="Restart", command=self.restart)
tk.Button(self, text="Refresh", command=self.refresh)
...
def restart(self):
self.refresh()
self.controller.show_frame("StartPage")
def refresh(self):
self.weight_entry.delete(0, "end")
self.text.delete("1.0", "end")
Simple way:
Just call that window with button or bind in which frame lies.
works good for windows refresh.

How to display a dataframe in tkinter

I am new to Python and even newer to tkinter.
I've utilised code from stackoverflow (Switch between two frames in tkinter) to produce a program where new frames are called and placed on top of each other depending on what options the user selects. A stripped down version of my code is below. There are a lot more frames.
import tkinter as tk
from tkinter import font as tkfont
import pandas as pd
class My_GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
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 = {}
for F in (StartPage, Page_2):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
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()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Welcome to....", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Option selected",
command=lambda: controller.show_frame("Page_2"))
button1.pack()
class Page_2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="The payment options are displayed below", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
#I want the able to be display the dataframe here
button = tk.Button(self, text="Restart",
command=lambda: controller.show_frame("StartPage"))
button.pack()
a = {'Option_1':[150,82.50,150,157.50,78.75],
'Option2':[245,134.75,245,257.25,128.63]}
df = pd.DataFrame(a,index=['a',
'b',
'c',
'd',
'e'])
print(df.iloc[:6,1:2])
if __name__ == "__main__":
app = My_GUI()
app.mainloop()
When Page_2 appears I want it to display a dataframe with the code below.
a = {'Option_1':[150,82.50,150,157.50,78.75],
'Option2':[245,134.75,245,257.25,128.63]}
df = pd.DataFrame(a,index=['a',
'b',
'c',
'd',
'e'])
print(df.iloc[:6,1:2])
I've searched SO e.g. How to display a pandas dataframe in a tkinter window (tk frame to be precise) (no answer provided) and other websites for an answer to similar question but without success.
How and where would I place my dataframe code selection to appear in the area I want when I select Page_2?
Check out pandastable.
It is quite a fancy library for displaying and working with pandas tables.
Here is a code example from their documentation:
from tkinter import *
from pandastable import Table, TableModel
class TestApp(Frame):
"""Basic test frame for the table"""
def __init__(self, parent=None):
self.parent = parent
Frame.__init__(self)
self.main = self.master
self.main.geometry('600x400+200+100')
self.main.title('Table app')
f = Frame(self.main)
f.pack(fill=BOTH,expand=1)
df = TableModel.getSampleData()
self.table = pt = Table(f, dataframe=df,
showtoolbar=True, showstatusbar=True)
pt.show()
return
app = TestApp()
#launch the app
app.mainloop()
and here a screenshot (also from their docs):
As a start, you could have a look at Label and Text widgets, that usually are used to display text in your GUI.
You could probably try something like:
class Page_2(tk.Frame):
def __init__(self, parent, controller):
# ... your code ...
global df # quick and dirty way to access `df`, think about making it an attribute or creating a function that returns it
text = tk.Text(self)
text.insert(tk.END, str(df.iloc[:6,1:2]))
text.pack()
# lbl = tk.Label(self, text=str(df.iloc[:6,1:2])) # other option
# lbl.pack() #
In the end, it really boils down to how fancy you want to be: the widgets are highly customizable, so you could achieve something very pleasing to the eye instead of the basic look of this example.
Edit:
I added a Combobox widget to select the option to display and a Button that prints it to the "display" widget of your choice.
from tkinter import ttk # necessary for the Combobox widget
# ... your code ...
class Page_2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="The payment options are displayed below", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
global df
tk.Label(self, text='Select option:').pack()
self.options = ttk.Combobox(self, values=list(df.columns))
self.options.pack()
tk.Button(self, text='Show option', command=self.show_option).pack()
self.text = tk.Text(self)
self.text.pack()
tk.Button(self, text="Restart",
command=lambda: controller.show_frame("StartPage")).pack()
def show_option(self):
identifier = self.options.get() # get option
self.text.delete(1.0, tk.END) # empty widget to print new text
self.text.insert(tk.END, str(df[identifier]))
The text that is displayed is the default string representation of a data-frame's column; a custom text is left as an exercise.

Tkinter updating labels in stacked frame windows

I am still dabbling with the stacked frames set up for a tkinter app and fairly new to OOP and Tkinter.
I have copied below code from another SO question and amended it slightly.
What I do not get done:
I want to update the label2 on the StartPage based on the click on Button2 on PageTwo from "Hello" to "5". But the update does not take place. What do I have to do differently to accomplish my task?
Many thanks in advance
import tkinter as tk
TITLE_FONT = ("Helvetica", 18, "bold")
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.var = tk.StringVar()
self.var.set('Hello')
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):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
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
label = tk.Label(self, text="This is the start page", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
label2 = tk.Label(self, textvariable=self.controller.var, font=TITLE_FONT)
label2.pack(side="top", fill="x", pady=10)
label2.config(text=self.controller.var)
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button2.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", font=TITLE_FONT)
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()
button2 = tk.Button(self, text="Change X",
command=lambda: self.calculate())
button2.pack()
def calculate(self):
self.controller.var = 5
return self.controller.var
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
There are many ways to accomplish what you want. Since you are using a StringVar for the label you want to change, the simplest solution is to update that variable with the new value:
def calculate(self):
self.controller.var.set(5)
This solution tightly couples the controller and the other class. That is, your PageTwo must know that the controller associates a StringVar with that label. If you modify the controller to use some other mechanism, you will have to change every other class that tries to set that variable.
A way to provide loose coupling is to have the controller provide an API for changing the value. In a sense, this is why controllers exist -- to control the flow of information between pages. The details of exactly how that value is stored and displayed is hidden from the other classes.
For example:
class SampleApp(...):
...
def set_label(self, value):
self.var.set(value)
class PageTwo(...):
...
def calculate(self):
self.controller.set_label(5)
The advantage to the above is that it provides loose coupling between the two classes. The other pages don't need to know that the label is implemented with a StringVar or a Label widget. The controller simply provides and interface that says "when you need to change variable X, call this function". As long as the controller maintains that function, you can change the implementation without having to modify every other class that may need to change that label.
See What is the difference between loose coupling and tight coupling in the object oriented paradigm? for more information about the difference between loose coupling and tight coupling.

Scrollbar - tkinter GUI - Python 3

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.

Resources