I am making a project in Tkinter where I gather some information in one frame (let's call it Frame One) and, based on that information, I want to change the layout of a different frame (let's call it Frame Two).
The code I'm attaching is a bare-bones version of what I want to achieve (my project is about 630 lines long).
The problem I ran into was how to access the frame, which is a class. Googling I came across Bryan Oakley's response on https://stackoverflow.com/questions/48731097/calling-functions-from-a-tkinter-frame-to-another#= , but when I tried to replicate it, I got an error.
EDIT: the following modification made it work, so it currently appends "testing" to testText when the button on PageTwo is clicked.
from tkinter import *
from tkinter import ttk
class MyApp(Tk):
def __init__(self):
Tk.__init__(self)
container = ttk.Frame(self)
container.pack(side="top", fill="both", expand = True)
self.frames = {}
for F in (PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky = NSEW)
self.show_frame(PageOne)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self, classname):
'''Returns an instance of a page given its class name as a string'''
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
def test_get_page(self, page_class):
return self.frames[page_class]
class PageOne(ttk.Frame):
def __init__(self, parent, controller):
self.controller=controller
ttk.Frame.__init__(self, parent)
ttk.Label(self, text='PageOne').grid()
self.testText = Text(self, height=2, width=30)
button1 = ttk.Button(self, text='Next Page',
command=lambda: controller.show_frame(PageTwo))
self.testText.grid()
button1.grid()
class PageTwo(ttk.Frame):
def __init__(self, parent, controller):
self.controller=controller
ttk.Frame.__init__(self, parent)
ttk.Label(self, text='PageTwo').grid()
button1 = ttk.Button(self, text='Previous Page',
command=self.testButton)#controller.show_frame(PageOne))
button1.grid()
def testButton(self):
page = self.controller.test_get_page(PageOne)
page.testText.insert(END,'testing')
self.controller.show_frame(PageOne)
app = MyApp()
app.title('Multi-Page Test App')
app.mainloop()
The expected result would have been for PageOne's "testText" widget to show "testing" once the 'Previous Page' button on PageTwo was clicked, but instead I got the following error:
"Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Max\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1702, in __call__
return self.func(*args)
File "C:\Users\Max\Dropbox\Programación\prueba.py", line 54, in testButton
page.testText.insert(0,'testing')
AttributeError: 'PageOne' object has no attribute 'testText'"
The error is given in the stack trace. The 'testText' variable is not currently an instance variable of the PageOne object. You should set self.testText = Text(self, height=2, width=30)
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
Edited version:
I am new to Tkinter and would like to perform a page switching functionality similar to the answer to this question (Switch between two frames in tkinter?). However, rather than have all of my pages in one single .py file, I would like to call each of the pages from its own .py file.
The reason why I want this is that later I will have multiple sub-pages, which will each represent different tools.
My question is,
How can I still keep the same frame switching functionality while
having each of my pages being called from different python files?
Here is what I have tried so far:
app.py
from page1 import *
import tkinter as tk
from tkinter import Tk, Frame, ttk
class AppWindow(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
# create a container for all the widgets (buttons, etc)
container = 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 loops for switching between pages
for F in (HomePage, PageOne, Tool1):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(HomePage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class HomePage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
label = ttk.Label(self, text="App Main Window", font=LARGE_FONT)
label.pack(pady=10, padx=10)
proc_btn = ttk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame(PageOne))
proc_btn.pack(ipadx=5, ipady=5, expand=1)
if __name__ == "__main__":
app = AppWindow()
app.mainloop()
page1.py
from apptest import *
import tkinter as tk
from tkinter import Tk, Frame, ttk
class PageOne(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
label = ttk.Label(self, text="Page One !!!!!!!!", font=LARGE_FONT)
label.pack(pady=10, padx=10)
proc1_btn = ttk.Button(self, text="Tool 1",
command=lambda: controller.show_frame(Tool1))
proc1_btn.pack(ipadx=5, ipady=5, expand=1)
home_btn = ttk.Button(self, text="Homepage",
command=lambda: controller.show_frame(HomePage))
home_btn.pack(ipadx=5, ipady=5, expand=1)
class Tool1(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
label = ttk.Label(self, text="Tool 1 !!!!!!!!", font=LARGE_FONT)
label.pack(pady=10, padx=10)
# Find what
ttk.Label(self, text='Find:')
keyword = ttk.Entry(self, width=30)
keyword.focus()
keyword.pack(ipadx=5, ipady=5, expand=1)
# Replace with:
ttk.Label(self, text='Replace:')
replacement = ttk.Entry(self, width=30)
replacement.pack(ipadx=5, ipady=5, expand=1)
proc1_btn = ttk.Button(self, text="PageOne",
command=lambda: controller.show_frame(PageOne))
proc1_btn.pack(ipadx=5, ipady=5, expand=1)
home_btn = ttk.Button(self, text="Homepage",
command=lambda: controller.show_frame(HomePage))
home_btn.pack(ipadx=5, ipady=5, expand=1)
My problem occurs when I try to use the "HomePage" button to go back to the HomePage when I am at the "PageOne" and "Tool1" pages. Here is the error that I am seeing.
Exception in Tkinter callback
Traceback (most recent call last):
File "...\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "...\GUI\page1.py", line 21, in <lambda>
command=lambda: controller.show_frame(HomePage))
File "C.../GUI/app.py", line 52, in show_frame
frame = self.frames[cont]
KeyError: <class 'apptest.HomePage'>
Thank you for your help in advance
Answer:
The answer to this question can be found here:
Switch between two frames in tkinter in separates files
Copy and remove all Class HomePage to page1.py module.
I commented out from apptest import *. Actually, I don't know what
it is.
Screenshot at the start:
Screenshot clicked on go to page one:
Screenshot clicked on go to Tool one:
Screenshot click on homepage:
Ok so, I am building an Sqlite browser in python(3) tkinter
Here is the problem. If you look at my code
The code derived from: http://stackoverflow.com/questions/7546050/switch-between-two-frames-in-tkinter
License: http://creativecommons.org/licenses/by-sa/3.0/
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from sqlite3 import *
import operator
file = 'chinook.db'
conn = connect(file)
c = conn.cursor()
c.execute("SELECT name FROM sqlite_master WHERE type = 'table'")
tables = c.fetchall()
tmplist = []
for i in tables:
tmplist.append(i[0])
LARGE_FONT= ("Verdana", 12)
print("done",tables)
global tabnam
tabnam = ''
def asign(sigh): #ok, yeah it was late and i didnt care much about naming convention
tabnam = sigh
#this function is supposed to assign the value given to the variable
def combine_funcs(*funcs):
def combined_func(*args, **kwargs):
for f in funcs:
f(*args, **kwargs)
return combined_func
#wrapper function
class SeaofBTCapp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Sea of BTC Client")
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):
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()
print("done")
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = ttk.Label(self, text=file, font=LARGE_FONT)
label.pack(pady=10,padx=10)
for i in tmplist:
button = ttk.Button(self, text="Visit Page " + str(i),
command=lambda:controller.show_frame(PageOne))
button.pack()
print("done")
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = ttk.Label(self, text='values', font=LARGE_FONT)
label.pack(pady=10,padx=10)
button1 = ttk.Button(self, text="Back to Home",command=lambda: controller.show_frame(StartPage))
button1.pack()
try:
c.execute("select * from {y}".format(y=tabnam))
for i in c.fetchall():
label = ttk.Label(text=i[0], font=LARGE_FONT)
label.pack(pady=10,padx=20)
except Exception as e:
print(e) # the error message indicates that the string formating identifies tabnam as " "
print("done")
app = SeaofBTCapp()
print("done")
app.mainloop()
print("done")
I'm trying to iterate an sqlite query. MEANING: i made it so that for every table, tkinter creates a button. This works. Tkinter displays a button with the name of every table. Now I want this code to be able to display every value in that table when that button is clicked. I have made a wrapping function to show the frame and reassign the current value of tabnam, the variable that represents the table name clicked by the user ; When the button is clicked, I reassign the value of a global variable named tabnam . But before the the buttons are even made, python just goes ahead and executes the query (thats yet to be formatted because user hasnt clicked button) and throws me an error. How do i fix this?
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.
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)