Tkinter: refresh frame/window from an outside function - python

I would like to refresh the MainPage when the updateCustomerList is finished updating the list so that this updated list is shown on the MainPage widgets.
I tried playing around with tk.show_frame(<frame>) and etc. but since the function itself isn't tied to the main Tkinter frame itself or isn't even a Tkinter object, then I'm not entirely sure how to reload the page. Any suggestions?
The code below is a snippet of my entire program:
customerList = [] #list is updated at the updateCustomerList function; global variable
class POS(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 (ErrorPage, PaymentPage, MainPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column = 0, sticky = "nsew")
#show frame here
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
"""
Tkinter frame that I would like to "refresh" and use the new updated list
"""
frame5Button = ttk.Button(frame5, text = "Add Item", command = lambda: updateCustomerList(barCode, quantity))
frame5Button.grid(row = 0, column = 5, padx = 90, pady = 10)
#This button allows me to go into the updateCustomerList function
def updateCustomerList(barCode, quantity):
#some code to update a list
#when function finishes updating the list, I would like to go back to the MainPage Tk frame and reload all the widgets like labels and entry boxes using the updated customerList list
app = POS()
app.geometry("700x700")
app.resizable(False, False)
app.after(100, MasterFilePopUp)
app.mainloop()

Just remove and recreate the instance of Mainframe inside as the last line of updateCustomerList:
container = 0 #global variable
Add "global container" inside POS(tk.Tk)
def updateCustomerList(barCode, quantity):
global app
...
app.frames[MainPage].destroy()
app.frames[MainPage] = MainPage(container, app)
app.frames[MainPage].grid(row=0, column = 0, sticky = "nsew")
app.frames[MainPage].tkraise()
#function ends here

Related

I can't seem to pass on an object to another class in Python/Tkinter

I have an object which I want to pass to a new frame through a method in another frame class. I found a solution that looked similar to what I was after and tried it, however, the object doesn't get passed. In fact, I got an error message saying "Value after * must be an iterable, not PackingList", where PackingList is the class which the object is made out of. I tried to instead get the "name" attribute of the object and pass it to the next frame, but it just returns an empty tuple. I am really stuck and I appreciate some help.
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)
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0, weight = 1)
self.frames = {}
for F in (Homescreen, Menuscreen, Create, ShowUpcoming, ShowAll, OpenList, Search, Edit):
frame = F(container, self, *args, **kwargs)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(Homescreen)
def show_frame(self, container, *args, **kwargs):
frame = self.frames[container]
frame.tkraise()
...
class ShowAll(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Label(self, text = "Alla packningslistor", font = ("Times new roman", 30)).place(x = 110, y = 0)
self.controller = controller
objectList = PK.main()
objectOpen = PK.showAll(objectList)
self.radiobutton_list = []
self.objectList = objectList
for i in range(len(objectOpen)):
radiobutton = tk.Radiobutton(self, text = objectOpen[i], command = functools.partial(self.objectToOpen, idx = i)).place(x = 150, y = 100 + i*30)
self.radiobutton_list.append(radiobutton)
def objectToOpen(self, idx):
objectID = self.objectList[idx]
return self.controller.show_frame(OpenList, *objectID)
class OpenList(tk.Frame):
def __init__(self, parent, controller, *objectID):
tk.Frame.__init__(self, parent)
#lala = getattr(objectID, "name")
#print(lala)
As I said I tried to pass just the name of the object but it prints out as an empty tuple in the next frame class.
I did not understand your question. But here are my corrections to your App class. I hope these corrections can help your understanding of Python and tkinter and debug the rest of your codes.
If you need more detailed help, it will be helpful if you can more specific by stating in the comment section what you want to do with classes App, ShowAll, and OpenList (or their method), and I will see how I can help you by elaborating my answer further.
import tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__()
self.container = tk.Frame(self)
self.container.pack(side="top", fill="both", expand=True)
self.frames = {}
colors = ('white', 'black', 'red', 'green', 'blue', 'cyan', 'yellow', 'magenta')
frames = ("Homescreen", "Menuscreen", "Create", "ShowUpcoming", "ShowAll", "OpenList", "Search", "Edit")
col=0
for C, F in zip(colors, frames):
print(f'{F=}')
self.frames[F] = tk.Frame(self.container, background=C, width=100, height=50)
self.frames[F].grid(row=0, column=col, sticky="nsew")
# col += 1 # uncomment this to see all frames
self.show_frame('Create') # you should see red
def show_frame(self, container):
self.frames[container].tkraise()
if __name__ == "__main__":
app = App()
app.mainloop()

Reaching an Object Outside of the Class in Python

I have started to learn Python recently and I am trying to make a basic GUI. I stucked at some point as you imagine. I found that code from the internet (possibly in stackoverflow) and am trying to modify it for the GUI which I want to make.
All code;
# imported necessary packages etc.
class tkinterApp(tk.Tk):
# __init__ function for class tkinterApp
def __init__(self, *args, **kwargs):
# __init__ function for class Tk
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack()
container.pack_propagate(0)
# initializing frames to an empty array
self.frames = {}
# iterating through a tuple consisting
# of the different page layouts
for F in (LoadPage, StartPage):
frame = F(container, self)
# initializing frame of that object from
# startpage, page1, page2 respectively with
# for loop
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky ="nsew")
self.show_frame(LoadPage)
# to display the current frame passed as
# parameter
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def load():
try:
import data
except:
print("Error...")
# first window frame loadpage
class LoadPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
top_container = tk.Frame(self)
top_container.pack()
top_container.pack_propagate(0)
lbl_title = tk.Label(top_container, text = "TITLE")
lbl_title.pack(side="bottom")
lbl_title.pack_propagate(0)
center_container = tk.Frame(self)
center_container.pack()
center_container.pack_propagate(0)
a_image = ImageTk.PhotoImage(Image.open(r"images/image.png"))
image_area = tk.Label(center_container, image = a_image)
image_area.image = a_image
image_area.pack(side = "top", pady=50)
image_area.pack_propagate(0)
b_image = ImageTk.PhotoImage(Image.open(r"images/image.png"))
btn_load = tk.Button(center_container, image = b_image, command=lambda:[load(), controller.show_frame(StartPage)])
btn_load.image = b_image
btn_load.pack(pady=10)
btn_load.pack_propagate(0)
lbl_load_inf = tk.Label(center_container, text = "INFO...")
lbl_load_inf.pack(pady=5)
lbl_load_inf.pack_propagate(0)
bottom_container = tk.Frame(self)
bottom_container.pack()
bottom_container.pack_propagate(0)
# second window frame startpage
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
top_container = tk.Frame(self)
top_container.pack()
top_container.pack_propagate(0)
lbl_title = tk.Label(top_container, text = "TITLE")
lbl_title.pack(side="top")
lbl_title.pack_propagate(0)
toolbar = tk.Frame(top_container)
toolbar.pack(side="bottom")
toolbar.pack_propagate(0)
btn_1 = tk.Button(toolbar, text ="BUTTON_1", \
command = lambda : controller.show_frame(StartPage))
btn_1.place(relheight=1, width=220, relx=0)
btn_2 = tk.Button(toolbar, text ="BUTTON_2", \
command = lambda : controller.show_frame(StartPage))
btn_2.place(relheight=1, width=200, relx=0.18)
center_container = tk.Frame(self)
center_container.pack()
center_container.pack_propagate(0)
figure = plt.Figure()
ax = figure.add_subplot(111)
chart_type = FigureCanvasTkAgg(figure, center_container)
chart_type.get_tk_widget().place(relx = 0.10, rely = 0.3, relwidth = 0.6, relheight = 0.4)
df["Column"].value_counts().sort_values().plot(kind='barh', legend=True, ax=ax)
ax.set_title('Title')
bottom_container = tk.Frame(self)
bottom_container.pack()
bottom_container.pack_propagate(0)
# Driver Code
app = tkinterApp()
app.mainloop()
If you look at the StartPage class, there is a code block in init function;
figure = plt.Figure()
ax = figure.add_subplot(111)
chart_type = FigureCanvasTkAgg(figure, center_container)
chart_type.get_tk_widget().place(relx = 0.10, rely = 0.3, relwidth = 0.6, relheight = 0.4)
df["Column"].value_counts().sort_values().plot(kind='barh', legend=True, ax=ax)
ax.set_title('Title')
at this code block there is a DataFrame (df). It comes from data.py (imported via load function which triggered by a button in LoadPage). I said it comes from but I should have said it should come from. Unfortunately I could not get the data. DataFrame imported perfectly but somehow I cannot reach that df. How can I reach i? Possibly it is about class system but I cannot figure it out.
I got the error;
NameError: name 'df' is not defined
Note: Code is a bit mess right now, I know. I will modify more in future. I am trying to learn how Tkinter, GUI, Python works just right now. Please pardon me for that messy code :)

How to return string value of Console output to put it into a label in Tkinter

I made a python script that has a lot of print statements showing what the program is doing rather than just having it just sit there and the python script works fine now I am creating a front end with tkinter.
What I used to send the print statements to return them is some thing like this:
test.py
def PrintX():
X = [1,2,3,4,5]
for x in X:
print(x)
My plan is to have a tkinter frame in that I put a label and set the text variable to my function in my script. My tkinter page script looks like this so far:
class TradingBotapp(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 (InitalSetup):
frame = F(container,self)
self.frames[F] = frame
frame.grid(row=0,column=0,sticky='nsew')
self.show_frame(InitalSetup)
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class InitalSetup(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
label = tk.Label(self, text='Setup', font = LARGE_FONT).pack()
Frame = tk.Frame(self,width=768,height=576).pack()
lbl = tk.Message(Frame, text='').pack()
button1 = ttk.Button(self, text='Start Setup',command=lambda:callback2(lbl)).pack()
def callback2(object):
old_stdout = sys.stdout
sys.stdout = StdoutRedirectorLabel(lbl)
setup()
#lbl['text'] = sys.stdout.result.strip()
sys.stdout = old_stdout
class StdoutRedirectorLabel(object):
def __init__(self,widget):
self.widget = widget
self.widget['text'] = ''
def write(self, text):
self.widget['text'] += text
app = TradingBotapp()
app.mainloop()
But nothing is showing up, but when I press the button I get self.widget['text'] = ''
TypeError: 'NoneType' object does not support item assignment any help would be much appreciated
Haha... You've fallen victim to one of the classic blunders!! (In all seriousness, I've done this many times before, you're not alone) I believe you need to pack your labels with label1.pack() after their creation.
The problem was that lbl was set and packed at the same time.
what was wrong: lbl = tk.Label(Frame, text='').pack()
to fix it I had to do this instead:
lbl = tk.Label(Frame, text='')
lbl.pack()
But it is not working the way I want it to so I have to find another way

Tkinter: Updating Images in labels that aren't directly accessible

In my GUI, i wanted to display an image that changes depending on some value. The image would change between self.img1 and self.img2. I created separate classes for the container and the pages. The container is defined as such:
class Gui(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack(side="top", fill = "both", expand = TRUE)
container.grid_rowconfigure(0, weight = 1)
self.MyReading = StringVar()
self.redpic = Image.open("red.png")
self.redpic = self.redpic.resize((100,100), Image.ANTIALIAS)
self.greenpic = Image.open("green.png")
self.greenpic = self.greenpic.resize((100,100), Image.ANTIALIAS)
self.img1 = ImageTk.PhotoImage(self.redpic)
self.img2 = ImageTk.PhotoImage(self.greenpic)
self.frames={}
for F in (StartPage, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
And the page displaying the image:
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self,parent)
label = Label(self, text="StartPage")
label.grid()
label1 = Label(self, textvariable = controller.MyReading)
label1.grid();
self.label4 = Label(self, image = controller.img1)
self.label4.grid();
self.label4.image = controller.img1
button1 = Button (self, text = "Show PageOne", command = lambda: controller.show_frame(PageOne))
button1.grid()
It is currently displaying img1. Now, to instantiate the GUI:
root = Gui()
update_reading()
root.mainloop()
update_reading() updates my other labels defined with StringVar(). I was wondering how would I go about updating label4 (which shows the image) if I can only instantiate/get access to Gui()? I only know that I could change the label4 through configure(). Is there a textvariable equivalent for images?
EDIT: I forgot to put the logic that I wanted to implement. It is basically:
If foo == TRUE:
--change the image to img1--
else:
--change the image to img2--
for some foo that exists outside of Gui.
EDIT2: Following through a previous comment's logic, I made some small changes to the code In the Gui:
class Gui(Tk):
def __init__(self, *args, **kwargs):
self.ColorVar = DoubleVar()
And within StartPage(), the changes are:
class StartPage(Frame):
def __init__(self, parent, controller):
controller.ColorVar.trace("w",self.IdkChief(controller))
def IdkChief(self, controller):
global val1
if float(val1) < 2.50 :
self.label4.configure(image = controller.img2)
self.label4.image = controller.img2
else:
self.label4.configure(image = controller.img1)
self.label4.image = controller.img1
Then the changes on ColorVar is defined in update_reading()as such:
def update_reading():
global val1
root.ColorVar.set(val1)
root.after(100,update_reading)
Where val1 is a changing float value. I decided to change it from a boolean logic to a float one to increase flexibility. It would then throw me a generic error
Exception in Tkinter callback Traceback (most recent call last):
File
"C:\Users\AppData\Local\Programs\Python\Python37\lib\tkinter__init__.py",
line 1705, in call
return self.func(*args) TypeError: 'NoneType' object is not callable
This error would repeat until the GUI is closed.
You can use tkinter variable trace function to set up a callback function to be executed whenever the variable is updated. Inside the callback function, you can then update the label based on the value of the variable.
Below is sample code blocks (based on your posted code design) to achieve your goal:
class Gui:
def __init__(self, *args, **kwargs):
...
self.ColorVar = DoubleVar()
...
class StartPage(Frame):
def __init__(self, parent, controller):
...
# register a callback to be executed whenever variable is modified
controller.ColorVar.trace('w', lambda *args: self.IdkChief(controller))
def IdkChief(self, controller):
img = controller.img1 if controller.ColorVar.get() < 2.5 else controller.img2
self.label4.config(image=img)

Python 2.7 - How do I use an Observer in a Tkinter GUI, where you switch between frames?

I'm currently working on a GUI which is based on the thread How to get variable data from a class. Since there will be a lot of data to handle, I would like to use a Model-Class, which get's its updates via Observer.
Right now, changes in the ttk.Combobox on Page One are registered via <<ComboboxSelect>>, pulled into the variable self.shared_data of the Controller and passed to the Model. This way, no Oberserver/Observable logic is used. Instead, the data in Model is changed, whenever the user takes a corresponding action in the GUI.
I, however, would love not to have to use bindings like <<ComboboxSelect>> to change the corresponding data in the Model, but an Observer/Observable logic, which detects, that i.e. the entry "Inputformat" in the dictionary self.shared_data within the Controller was changed, which in turn refreshes the data in the Model, i.e. self.model_data, where the actual state of the ttk.Combobox is saved.
In short, I want to achieve the following, by using an Observer:
User selects i.e. "Entry 01" in the ttk.Combobox --> self.shared_data["Inputformat"] in the Controller is now filled with "Entry 01" --> an Observer/Observable logic detects this --> the corresponding variable in the Model is beeing changed.
For you to have something to work with, here is the code.
# -*- coding: utf-8 -*-
import csv
import Tkinter as tk # python2
import ttk
import tkFileDialog
# Register a new csv dialect for global use.
# Its delimiter shall be the semicolon:
csv.register_dialect('excel-semicolon', delimiter = ';')
font = ('Calibri', 12)
'''
###############################################################################
# Model #
###############################################################################
'''
class Model:
def __init__(self, *args, **kwargs):
# There shall be a variable, which is updated every time the entry
# of the combobox is changed
self.model_keys = {}
self.model_directories = {}
def set_keys(self, keys_model):
self.model_keys = keys_model
keys = []
keyentries = []
for key in self.model_keys:
keys.append(key)
for entry in self.model_keys:
keyentries.append(self.model_keys[entry].get())
print "model_keys: {0}".format(keys)
print "model_keyentries: {0}".format(keyentries)
def get_keys(self):
keys_model = self.model_keys
return(keys_model)
def set_directories(self, model_directories):
self.model_directories = model_directories
print "Directories: {0}".format(self.model_directories)
def get_directories(self):
model_directories = self.model_directories
return(model_directories)
'''
###############################################################################
# Controller #
###############################################################################
'''
# controller handles the following: shown pages (View), calculations
# (to be implemented), datasets (Model), communication
class PageControl(tk.Tk):
''' Initialisations '''
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs) # init
tk.Tk.wm_title(self, "MCR-ALS-Converter") # title
# initiate Model
self.model = Model()
# file dialog options
self.file_opt = self.file_dialog_options()
# stores checkboxstatus, comboboxselections etc.
self.shared_keys = self.keys()
# creates the frames, which are stacked all over each other
container = self.create_frame()
self.stack_frames(container)
#creates the menubar for all frames
self.create_menubar(container)
# raises the chosen frame over the others
self.frame = self.show_frame("StartPage")
''' Methods to show View'''
# frame, which is the container for all pages
def create_frame(self):
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = ttk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
return(container)
def stack_frames(self, container):
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")
# overarching menubar, seen by all pages
def create_menubar(self, container):
# the menubar is going to be seen by all pages
menubar = tk.Menu(container)
menubar.add_command(label = "Quit", command = lambda: app.destroy())
tk.Tk.config(self, menu = menubar)
# function of the controller, to show the desired frame
def show_frame(self, page_name):
#Show the frame for the given page name
frame = self.frames[page_name]
frame.tkraise()
return(frame)
''' Push and Pull of Data from and to Model '''
# calls the method, which pushes the keys in Model (setter)
def push_keys(self):
self.model.set_keys(self.shared_keys)
# calls the method, which pulls the key data from Model (getter)
def pull_keys(self):
pulled_keys = self.model.get_keys()
return(pulled_keys)
# calls the method, which pushes the directory data in Model (setter)
def push_directories(self, directories):
self.model.set_directories(directories)
# calls the method, which pulls the directory data from Model (getter)
def pull_directories(self):
directories = self.model.get_directories()
return(directories)
''' Keys '''
# dictionary with all the variables regarding widgetstatus like checkbox checked
def keys(self):
keys = {}
keys["Inputformat"] = tk.StringVar()
keys["Outputformat"] = tk.StringVar()
return(keys)
''' Options '''
# function, which defines the options for file input and output
def file_dialog_options(self):
#Options for saving and loading of files:
options = {}
options['defaultextension'] = '.csv'
options['filetypes'] = [('Comma-Seperated Values', '.csv'),
('ASCII-File','.asc'),
('Normal Text File','.txt')]
options['initialdir'] = 'C//'
options['initialfile'] = ''
options['parent'] = self
options['title'] = 'MCR-ALS Data Preprocessing'
return(options)
''' Methods (bindings) for PageOne '''
def open_button(self):
self.get_directories()
''' Methods (functions) for PageOne '''
# UI, where the user can selected data, that shall be opened
def get_directories(self):
# open files
file_input = tkFileDialog.askopenfilenames(** self.file_opt)
file_input = sorted(list(file_input))
# create dictionary
file_input_dict = {}
file_input_dict["Input_Directories"] = file_input
self.push_directories(file_input_dict)
'''
###############################################################################
# View #
###############################################################################
'''
class StartPage(ttk.Frame):
''' Initialisations '''
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
self.controller = controller
self.labels()
self.buttons()
''' Widgets '''
def labels(self):
label = tk.Label(self, text = "This is the start page", font = font)
label.pack(side = "top", fill = "x", pady = 10)
def buttons(self):
button1 = ttk.Button(self, text = "Go to Page One",
command = lambda: self.controller.show_frame("PageOne"))
button2 = ttk.Button(self, text = "Go to Page Two",
command = lambda: self.controller.show_frame("PageTwo"))
button_close = ttk.Button(self, text = "Close",
command = lambda: app.destroy())
button1.pack(side = "top", fill = "x", pady = 10)
button2.pack(side = "top", fill = "x", pady = 10)
button_close.pack(side = "top", fill = "x", pady = 10)
class PageOne(ttk.Frame):
''' Initialisations '''
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
self.controller = controller
self.labels()
self.buttons()
self.combobox()
''' Widgets '''
def labels(self):
label = tk.Label(self, text = "On this page, you can read data", font = font)
label.pack(side = "top", fill = "x", pady = 10)
def buttons(self):
button_open = ttk.Button(self, text = "Open",
command = lambda: self.controller.open_button())
button_forward = ttk.Button(self, text = "Next Page >>",
command = lambda: self.controller.show_frame("PageTwo"))
button_back = ttk.Button(self, text = "<< Go back",
command = lambda: self.controller.show_frame("StartPage"))
button_home = ttk.Button(self, text = "Home",
command = lambda: self.controller.show_frame("StartPage"))
button_close = ttk.Button(self, text = "Close",
command = lambda: app.destroy())
button_open.pack(side = "top", fill = "x", pady = 10)
button_forward.pack(side = "top", fill = "x", pady = 10)
button_back.pack(side = "top", fill = "x", pady = 10)
button_home.pack(side = "top", fill = "x", pady = 10)
button_close.pack(side = "top", fill = "x", pady = 10)
def combobox(self):
entries = ("", "Inputformat_01", "Inputformat_02", "Inputformat_03")
combobox = ttk.Combobox(self, state = 'readonly', values = entries,
textvariable = self.controller.shared_keys["Inputformat"])
combobox.current(0)
combobox.bind('<<ComboboxSelected>>', self.updater)
combobox.pack(side = "top", fill = "x", pady = 10)
''' Bindings '''
# wrapper, which notifies the controller, that it can update keys in Model
def updater(self, event):
self.controller.push_keys()
class PageTwo(ttk.Frame):
''' Initialisations '''
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
self.controller = controller
self.labels()
self.buttons()
self.combobox()
''' Widgets '''
def labels(self):
label = tk.Label(self, text = "This is page 2", font = font)
label.pack(side = "top", fill = "x", pady = 10)
def buttons(self):
button_back = ttk.Button(self, text = "<< Go back",
command = lambda: self.controller.show_frame("PageOne"))
button_home = ttk.Button(self, text = "Home",
command = lambda: self.controller.show_frame("StartPage"))
button_close = ttk.Button(self, text = "Close",
command = lambda: app.destroy())
button_back.pack(side = "top", fill = "x", pady = 10)
button_home.pack(side = "top", fill = "x", pady = 10)
button_close.pack(side = "top", fill = "x", pady = 10)
def combobox(self):
entries = ("Outputformat_01", "Outputformat_02")
combobox = ttk.Combobox(self, state = 'readonly', values = entries,
textvariable = self.controller.shared_keys["Outputformat"])
combobox.bind('<<ComboboxSelected>>', self.updater)
combobox.pack(side = "top", fill = "x", pady = 10)
''' Bindings '''
# wrapper, which notifies the controller, that it can update keys in Model
def updater(self, event):
self.controller.push_keys()
if __name__ == "__main__":
app = PageControl()
app.mainloop()
Since I wasn't able to implement an Observer to watch widgets like the ttk.Combobox, I've decided to create a workaround. Here are the steps I took, in order to achieve a MVC architecture from Bryan Oakleys example (link is in the question), which refreshes its model class via the controller class, whenever a user takes an action in the view (GUI).
Step 1: Add a model class
First, in order to use a MVC architecture, we have to seperate the code into model, view and control. In this example, model is class Model:, control is class PageControl(tk.Tk): and view are the pages class StartPage(tk.Frame), PageOne(tk.Frame) and PageTwo(tk.Frame).
Step 2: Set up your model class
Now we have to decide on which variables we want to have in the model class. In this example, we have directories and keys (status of the comboboxes), which we want to save in dictionaries. After setting them up empty, all we have to do is add setters and getters for each variable, so we can refresh data in model and also retrieve some, if we want. Additionally, we could implement delet methods for each variable, if we wanted to.
Step 3: Add push and pull methods to the control class
Now that there is a model class, we can refrence it via e. g. self.model = Model() in PageControl(tk.Tk) (control). Now we have the basic tools to set data in Model via e. g. self.model.set_keys(self.shared_keys) and also get data from Model. Since we want our control class to do that, we need some methods, that can achieve this. So we add the push and pull methods to the PageControl (e. g. def push_key(self)), which in turn can be refrenced from view (StartPage, PageOne, PageTwo) via controller.
Step 4: Add your widgets to the view class
Now we have to decide on which widgets shall be on which page and what you want them to do. In this example, there are buttons for navigation, which for the sake of the task can be ignored, two comboboxes and a button, which opens a file dialog.
Here, we want the comboboxes to refresh their status whenever it is changed and send the new status via controller to the model. Whereas the Open button of PageOne shall open a file dialog, where the user then selects files he/she wants to open. The directories we got from this interaction then shall be send via controller to model.
Step 5: Get all your functionality into the controller class
Since there is a controller variable, we can use it to refrence methods, which are in the controller class. This way, we can outsource all our methods from the pages into the controller and reference them via self.controller.function_of_controller_class. But we have to be aware, that methods, which are bound to commands via lambda: can't return any values, but they are also not called on programme startup. So keep that in mind.
Step 6: Set up your bindings and wrappers
Here we have to set up the .bind() for our comboboxes. Since the controller allready is set up to store data and the comboboxes have a textvariable, we can use this to gather information about the status of the comboboxes via combobox.bind(<<ComboboxSelect>>). All we have to do is to set up a wrapper which is called, whenever combobox.bind(<<ComboboxSelect>>) is throwing an event.
Closing statement
Now we have it, a programme based on Bryan Oakleys example of "How to get variable data from a class", which utilises a model, which is updated via controller whenever the user takes a corresponding action in the view. Unfortunately it doesn't utilise a Observer class, as first intended, but I'll keep working on it and update this, when I've found a satisfying solution.

Categories

Resources