Showing a widget and hiding all other widgets by binding ComboboxSelected - python

I am new to python and GUI-Thinker. I'm learning about how to switch windows on GUI using Tkinker as UI, and python as a programming language.
I followed this guideline Switch between two frames in tkinter to switch frames in thinker and it worked.
Then, I'm trying to show widgets and hide all other widgets using bind("<<ComboboxSelected>>". But when I choose Monthly option, widgets as I set under DeleteOptions condition didn't show up. The same thing to Period option, widgets as I set under DeleteOptions condition didn't show up.
My Code:
from tkinter import *
import tkinter as tk
from tkinter import ttk
from tkinter import font as tkfont
from tkcalendar import DateEntry
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")
# 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 = 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
# 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()
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=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button1.pack()
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.grid()
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.grid()
def DeleteOptions():
if SVcmb_Delete.current()=="Monthly":
HideDeleteOptions()
lblMonthly.grid(row=4)
SVcmb_Monthly.grid(row=4, column=1)
if SVcmb_Delete.current()=="Period":
HideDeleteOptions()
lblFrom.grid(row=4)
txtFrom.grid(row=4, column=1)
lblTo.grid(row=5)
txtTo.grid(row=5, column=1)
lblSV_Search=tk.Label(self,text="Delete by")
lblSV_Search.grid(row=3)
SVcmb_Delete=ttk.Combobox(self,state="readonly",justify=CENTER,font=("times new roman",15))
SVcmb_Delete["values"]=("Select","Monthly","Period")
SVcmb_Delete.grid(row=3,column=1)
SVcmb_Delete.bind("<<ComboboxSelected>>", lambda event:DeleteOptions)
def HideDeleteOptions():
lblMonthly.grid_forget()
SVcmb_Monthly.grid_forget()
lblFrom.grid_forget()
txtFrom.grid_forget()
lblTo.grid_forget()
txtTo.grid_forget()
lblMonthly=tk.Label(self,text="Monthly")
SVcmb_Monthly=ttk.Combobox(self,state="readonly",values=[1,2,3],justify=CENTER)
lblFrom=tk.Label(self,text="From")
txtFrom=DateEntry(self,selectmode='day',date_pattern='mm/dd/y')
lblTo=tk.Label(self,text="To")
txtTo=DateEntry(self,selectmode='day',date_pattern='mm/dd/y')
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
On page one. I'm looking for a way like when I chose Monthly option, all widgets under DeleteOptions condition show up and all widgets of Period option are hidden as I set under DeleteOptions condition. The same thing to Period option, all widgets under DeleteOptions condition show up and all widgets of Monthly option are hidden as I set under DeleteOptions condition.
Please help me. Thank you so much.

There are two problems with your code. This first is with your lambda expression:
SVcmb_Delete.bind("<<ComboboxSelected>>", lambda event:DeleteOptions)
is wrong because it doesn't actually call the DeleteOptions() function the way it's written.
Instead, do it like this:
SVcmb_Delete.bind("<<ComboboxSelected>>", lambda event: DeleteOptions())
The second problem is with your use of the SVcmb_Delete.current() method in the DeleteOptions() function because it returns the index of the the current entry text, not the entry text itself. To obtain that, you should use the get() method. With that correction made the function should look like something like this:
def DeleteOptions():
if SVcmb_Delete.get() == "Monthly":
HideDeleteOptions()
lblMonthly.grid(row=4)
SVcmb_Monthly.grid(row=4, column=1)
if SVcmb_Delete.get() == "Period":
HideDeleteOptions()
lblFrom.grid(row=4)
txtFrom.grid(row=4, column=1)
lblTo.grid(row=5)
txtTo.grid(row=5, column=1)
Lastly I also strongly suggest that you read and start following the PEP 8 - Style Guide for Python Code to make your code more readable.

Related

Segmentation fault: 11 - tkinter python

Segmentation fault: 11 - not sure what it means, why it has happened. I thought it was an issue with Python on my machine by all other files run fine. I have, of course, tried restarting and re-installing Python but didn't help.
I'm just trying to implement frame switching via a menu bar with tkinter.
Any help greatly appreciated.
# import tkinter modules
from tkinter import *
from tkinter import ttk
import tkinter.font as tkFont
from PIL import ImageTk, Image
from tkcalendar import *
# import modules for restart functionality
import os
import sys
import time
# define self
class tkinterApp(Tk):
def __init__(self,*args, **kwargs):
Tk.__init__(self, *args, **kwargs)
# creating a container
container = Frame(self)
container.pack(side = "top", fill = "both", expand = True)
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0, weight = 1)
# initialising frames to an empty array
self.frames = {}
menu_bar = Menu(container)
menu_bar.add_cascade(label="Main Menu", menu=menu_bar)
menu_bar.add_command(label="Welcome page", command=lambda: self.show_frame(welcome_frame))
menu_bar.add_command(label="Book a vehicle", command=lambda: self.show_frame(booking_frame))
menu_bar.add_command(label="Register as new user", command=lambda: self.show_frame(register_frame))
Tk.config(self, menu=menu_bar)
for F in (welcome_frame, register_frame, booking_frame):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(welcome_frame)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class welcome_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
# welcome_frame = Frame(self, width=1000, height=800)
# welcome_frame.grid()
welcome = Label(welcome_frame, text="Hello, please use the menu above to navigate the interface")
welcome.grid(row=0, column=4, padx=10, pady=10)
class register_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
welcome = Label(self, text="New user - enter your details below to use the Collyer's car park.")
welcome.grid(row=0, column=4, padx=10, pady=10)
class booking_frame(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
app = tkinterApp()
app.geometry("1000x800")
app.mainloop()
You are trying to make a cascade menu where the cascaded menu is the same menu:
menu_bar.add_cascade(label="Main Menu", menu=menu_bar)
The menu option needs to point to a new menu menu.
main_menu = Menu(menu_bar)
menu_bar.add_cascade(label="Main Menu", menu=main_menu)
I'm guessing you also want to put the menu commands on that menu, too
main_menu.add_command(label="Book a vehicle", command=lambda: self.show_frame(booking_frame))
main_menu.add_command(label="Register as new user", command=lambda: self.show_frame(register_frame))
Unrelated to the question, this code is also wrong:
welcome = Label(welcome_frame, text="Hello, please use the menu above to navigate the interface")
You are trying to use a class as the parent/master of the Label widget. You can't do that. The first parameter needs to be a widget. In this case, it should be self.
You also need to make sure that show_frame is indented the same as the __init__ method of the tkinterApp class.

Why aren't radiobuttons working in a Tkinter window with multiple frames?

I copied Python code to create a Tkinter window with multiple frames. I put many kinds of widgets into it with no problem but when I add radiobuttons, those act funny, although they work fine in a regular window (without multiple pages). Whether I set the value or not, none of the radiobuttons are selected. What's worse, if I just pass the mouse pointer over a radiobutton, it looks like it gets selected although I didn't click it. If I pass the mouse pointer over both radiobuttons, they BOTH look selected, violating the one-of-many-selections rule of radiobuttons.
I should add that I tried this with a pack manager and with a grid manager. The results are the same.
Here's a stripped-down version of my code:
import tkinter as tk
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# Set the title of the main window.
self.title('Multi-frame window')
# Set the size of the main window to 300x300 pixels.
self.geometry('300x100')
# This container contains all the pages.
container = tk.Frame(self)
container.grid(row=1, column=1)
self.frames = {} # These are pages to which we want to navigate.
# For each page...
for F in (StartPage, PageOne):
# ...create the page...
frame = F(container, self)
# ...store it in a frame...
self.frames[F] = frame
# ..and position the page in the container.
frame.grid(row=0, column=0, sticky='nsew')
# The first page is StartPage.
self.show_frame(StartPage)
def show_frame(self, name):
frame = self.frames[name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='Start Page')
label.grid(row=1, column=1)
# When the user clicks on this button, call the
# show_frame method to make PageOne appear.
button1 = tk.Button(self, text='Visit Page 1',
command=lambda : controller.show_frame(PageOne))
button1.grid(row=2, column=1)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# When the user clicks on this button, call the
# show_frame method to make StartPage appear.
button1 = tk.Button(self, text='Back to Start',
command=lambda : controller.show_frame(StartPage))
button1.grid(row=1, column=1)
options_label = tk.Label(self, text='Choose an option: ')
options_label.grid(row=2, column=1)
options_value = tk.IntVar()
first_option = tk.Radiobutton( self , text = 'Option 1' ,
variable = options_value , value = 1 )
second_option = tk.Radiobutton( self , text = 'Option 2' ,
variable = options_value , value = 2 )
first_option.grid(row=2, column=2)
second_option.grid(row=2, column=3)
options_value.set(1)
if __name__ == '__main__':
app = MainWindow()
app.mainloop()
The problem is that options_value is a local value that gets destroyed when __init__ finishes.
You need to save a reference to it, such as self.options_value.

Variables from other classes in Tkinter [duplicate]

This is a shortened example of a longer application where I have multiple pages of widgets collecting information input by the user. The MyApp instantiates each page as a class. In the example, PageTwo would like to print the value of the StringVar which stores the data from an Entry widget in PageOne.
How do I do that? Every attempt I've tried ends up with one exception or another.
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()
class PageOne(ttk.Frame):
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
ttk.Label(self, text='PageOne').grid(padx=(20,20), pady=(20,20))
self.make_widget(controller)
def make_widget(self, controller):
self.some_input = StringVar
self.some_entry = ttk.Entry(self, textvariable=self.some_input, width=8)
self.some_entry.grid()
button1 = ttk.Button(self, text='Next Page',
command=lambda: controller.show_frame(PageTwo))
button1.grid()
class PageTwo(ttk.Frame):
def __init__(self, parent, controller):
ttk.Frame.__init__(self, parent)
ttk.Label(self, text='PageTwo').grid(padx=(20,20), pady=(20,20))
button1 = ttk.Button(self, text='Previous Page',
command=lambda: controller.show_frame(PageOne))
button1.grid()
button2 = ttk.Button(self, text='press to print', command=self.print_it)
button2.grid()
def print_it(self):
print ('The value stored in StartPage some_entry = ')#What do I put here
#to print the value of some_input from PageOne
app = MyApp()
app.title('Multi-Page Test App')
app.mainloop()
Leveraging your controller
Given that you already have the concept of a controller in place (even though you aren't using it), you can use it to communicate between pages. The first step is to save a reference to the controller in each page:
class PageOne(ttk.Frame):
def __init__(self, parent, controller):
self.controller = controller
...
class PageTwo(ttk.Frame):
def __init__(self, parent, controller):
self.controller = controller
...
Next, add a method to the controller which will return a page when given the class name or some other identifying attribute. In your case, since your pages don't have any internal name, you can just use the class name:
class MyApp(Tk):
...
def get_page(self, classname):
'''Returns an instance of a page given it's class name as a string'''
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
note: the above implementation is based on the code in the question. The code in the question has it's origin in another answer here on stackoverflow. This code differs from the original code slightly in how it manages the pages in the controller. This uses the class reference as a key, the original answer uses the class name.
With that in place, any page can get a reference to any other page by calling that function. Then, with a reference to the page, you can access the public members of that page:
class PageTwo(ttk.Frame):
...
def print_it(self):
page_one = self.controller.get_page("PageOne")
value = page_one.some_entry.get()
print ('The value stored in StartPage some_entry = %s' % value)
Storing data in the controller
Directly accessing one page from another is not the only solution. The downside is that your pages are tightly coupled. It would be hard to make a change in one page without having to also make a corresponding change in one or more other classes.
If your pages all are designed to work together to define a single set of data, it might be wise to have that data stored in the controller, so that any given page does not need to know the internal design of the other pages. The pages are free to implement the widgets however they want, without worrying about which other pages might access those widgets.
You could, for example, have a dictionary (or database) in the controller, and each page is responsible for updating that dictionary with it's subset of data. Then, at any time you can just ask the controller for the data. In effect, the page is signing a contract, promising to keep it's subset of the global data up to date with what is in the GUI. As long as you maintain the contract, you can do whatever you want in the implementation of the page.
To do that, the controller would create the data structure before creating the pages. Since we're using tkinter, that data structure could be made up of instances of StringVar or any of the other *Var classes. It doesn't have to be, but it's convenient and easy in this simple example:
class MyApp(Tk):
def __init__(self):
...
self.app_data = {"name": StringVar(),
"address": StringVar(),
...
}
Next, you modify each page to reference the controller when creating the widgets:
class PageOne(ttk.Frame):
def __init__(self, parent, controller):
self.controller=controller
...
self.some_entry = ttk.Entry(self,
textvariable=self.controller.app_data["name"], ...)
Finally, you then access the data from the controller rather than from the page. You can throw away get_page, and print the value like this:
def print_it(self):
value = self.controller.app_data["address"].get()
...
I faced a challenge in knowing where to place the print_it function.
i added the following to make it work though I don't really understand why they are used.
def show_frame(self,page_name):
...
frame.update()
frame.event_generate("<<show_frame>>")
and added the show_frame.bind
class PageTwo(tk.Frame):
def __init__(....):
....
self.bind("<<show_frame>>", self.print_it)
...
def print_it(self,event):
...
Without the above additions, when the mainloop is executed,
Page_Two[frame[print_it()]]
the print_it function executes before PageTwo is made Visible.
try:
import tkinter as tk # python3
from tkinter import font as tkfont
except ImportError:
import Tkinter as tk #python2
import tkFont as tkfont
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")
# data Dictionary
self.app_data = {"name": tk.StringVar(),
"address": tk.StringVar()}
# 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 = 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")
def show_frame(self, page_name):
''' Show a frame for the given page name '''
frame = self.frames[page_name]
frame.tkraise()
frame.update()
frame.event_generate("<<show_frame>>")
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=self.controller.title_font)
label.pack(side="top", fill="x", pady=10)
# Update the Name value only
self.entry1 = tk.Entry(self,text="Entry", textvariable=self.controller.app_data["name"])
self.entry1.pack()
button1 = tk.Button(self, text="go to page one", command = lambda: self.controller.show_frame("PageOne")).pack()
button2 = tk.Button(self, text="Go to page Two", command = lambda: self.controller.show_frame("PageTwo")).pack()
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=self.controller.title_font)
label.pack(side="top", fill="x", pady=10)
# Update the Address value only
self.entry1 = tk.Entry(self,text="Entry", textvariable=self.controller.app_data["address"])
self.entry1.pack()
button = tk.Button(self, text="Go to the start page", command=lambda: self.controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
# Bind the print_it() function to this Frame so that when the Frame becomes visible print_it() is called.
self.bind("<<show_frame>>", self.print_it)
label = tk.Label(self, text="This is page 2", font=self.controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: self.controller.show_frame("StartPage"))
button.pack()
def print_it(self,event):
StartPage_value = self.controller.app_data["name"].get()
print(f"The value set from StartPage is {StartPage_value}")
PageOne_value= self.controller.app_data["address"].get()
print(f"The value set from StartPage is {PageOne_value}")
if __name__ == "__main__":
app = SampleApp()
app.mainloop()

tkinter PIL image is not shown up in class

I can't show up the Image in tkinter PIL class.
Image is packed or placed successfully but it is not showing up.
windowxp wall paper and a man's face are not shown.
I made message box when click his face.
So if I click the location, message box is showing up. But picture is not showing up. Just location :(
I use windows 64bit and Python 3.6
I am Korean. I'm not goot at English.... Please understand me.
Please help me
This is right display: screenshot
from tkinter import *
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
##함수
class Mainwindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("김상곤이 좋아하는 꿈꾸는 학과 과목 문제 맞추기♡")
self.geometry("1600x900")
self.resizable(width=FALSE, height=FALSE)
wall = tk.PhotoImage(file="gif/bg.gif")
labelwall = tk.Label(self, image = wall)
labelwall.place(x=0, y=0)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
def func_make():
messagebox.showinfo("제작자", "김재온, 정성윤, 안예진, 이소유, 우연서")
def func_exit():
window.quit()
window.destroy()
mainMenu=Menu(self)
fileMenu=Menu(mainMenu)
self.config(menu=mainMenu)
mainMenu.add_cascade(label="파일", menu=fileMenu)
fileMenu.add_command(label="제작자", command=func_make)
fileMenu.add_separator()
fileMenu.add_command(label="종료", command=func_exit)
self.frames={}
for F in (MainPage, QuizPage):
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("MainPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
def 국어():
page == 1
subject=['국어', '과학', '역사', '사회', '기술']
#색이나 위치 숫자 설정
mint="#99FFFF"
subjectsize=30
subjectbutton=60
##위젯
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller=controller
def clickksk(event):
messagebox.showinfo("김상곤", "아주 좋아요^^")
labeltitle=tk.Label(self, text=
"""김상곤이 좋아하는 꿈꾸는
학과 과목 문제 맞추기♡""", font=("궁서체", 35), bg="#8DFD73")
ksk=tk.PhotoImage(file="gif/ksk.gif")
labelksk=tk.Label(self, image=ksk)
labelksk.place(x=400-subjectbutton, y=200)
labelksk.bind("<Button>", clickksk)
labelhow=tk.Label(self, text="게임방법! ", font=("맑은 고딕", 30), bg="#FFE400")
labelexplain=tk.Label(self, text=
"""원하는 과목을 택해 클릭한후,
OX퀴즈를 풀면 됩니다^^
난이도=중3""", font=("고딕체", 25), bg="#FFE400")
btKorean=tk.Button(self, text="국어", font=("양재블럭체", subjectsize), bg=mint,
command=lambda: controller.show_frame("QuizPage"))
btScience=tk.Button(self, text="과학", font=("양재블럭체", subjectsize), bg=mint)
btHistory=tk.Button(self, text="역사", font=("양재블럭체", subjectsize), bg=mint)
btSocial=tk.Button(self, text="사회", font=("양재블럭체", subjectsize), bg=mint)
bttech=tk.Button(self, text="기술", font=("양재블럭체", subjectsize), bg=mint)
##pack하는 장소(코드 순차대로)
labeltitle.place(relx= 0.25, rely=0.02, relwidth=0.5)
labelhow.place(x=610-subjectbutton, y=200, relwidth=0.3)
labelexplain.place(x=610-subjectbutton, y=260, relwidth=0.3)
btKorean.place(x=400-subjectbutton, y=600)
btScience.place(x=600-subjectbutton, y=600)
btHistory.place(x=800-subjectbutton, y=600)
btSocial.place(x=1000-subjectbutton, y=600)
bttech.place(x=1200-subjectbutton, y=600)
btKorean.bind("<Button-1>")
btScience.bind("<Button-1>")
btHistory.bind("<Button-1>")
btSocial.bind("<Button-1>")
bttech.bind("<Button-1>")
class QuizPage(tk.Frame):
def __init__(self, parent, controller):
OB=PhotoImage(file="gif/OB.gif")
XR=PhotoImage(file="gif/XR.gif")
tk.Frame.__init__(self, parent)
self.controller = controller
buttonOB=Button(self, image=OB)
buttonXR=Button(self, image=XR)
buttonOB.place()
buttonXR.place()
if __name__ == "__main__":
app = Mainwindow()
app.mainloop()
I think your main problem is you are using two subclassed frames and using grid to manage them but the main application window is not expanding the grid area. You need to add the following to MainWindow.__init__:
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
so that the (0,0) cell is expanded to fill all the available space. Then your currently selected frame will expand into that region as it has sticky='NSWE' set in the grid method call.
Where you define container you are both packing and gridding. You have to only use one. However, this looks like dead code. Maybe you intended to put the subclassed frames into this container at one time.
Also, you cannot just call buttonXR.place(), you need to give it a position. For instance: buttonXR.place(x=1, y=1).

AttributeError: type object 'class' has no attribute 'stringVar'

I am trying to make a GUI program which contain several pages with buttons and labels. I need the program to be able to dynamically change labels by pressing buttons
to interact with labels on pages that I am not currently working at. I have made an example program that runs, but I get an error when trying to change labels in a page that is not being shown.
import tkinter as tk
from tkinter import ttk
LARGE_FONT = ("Verdana", 12)
class ManyPagesApp(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 = {}
# This loop adds the pages into the frame
for F in (Page1, Page2):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Page1)
# This function raises the page to the "top" level of the frame
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
# Creates what shows in start page
class Page1(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button1 = ttk.Button(self, text="Go to Page 2", command=lambda: controller.show_frame(Page2))
button1.pack()
button2 = ttk.Button(self, text="THIS BUTTON CHANGES LABEL ON PAGE 1",
command=lambda: self.labelChanger('This label was changed by using button 2'))
button2.pack()
label = ttk.Label(self, text='This is Page 1', font=LARGE_FONT)
label.pack(pady=10, padx=10)
self.labelVariable1 = tk.StringVar()
self.labelVariable1.set('This label will be changed')
label = tk.Label(self, textvariable= self.labelVariable1)
label.pack()
def labelChanger(self, text):
self.labelVariable1.set(text)
class Page2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
objectofpage1 = Page1
button1 = ttk.Button(self, text="Goes to page 1", command=lambda: controller.show_frame(Page1))
button1.pack()
button2 = ttk.Button(self, text="Changes Label 2 in page 1",
command=lambda: objectofpage1.labelChanger(objectofpage1, 'You have changed the label in page 1'))
button2.pack()
label = ttk.Label(self, text='This is page 2', font=LARGE_FONT)
label.pack(pady=10, padx=10)
# Runs everything
if __name__ == "__main__":
app = ManyPagesApp()
app.mainloop()
My application got much bigger due to having more pages but I made this example program that gives me the same error.
OBS: The error only occurs when on page 2 and trying to change a label on page one.
And this is the error that pops up:
Exception in Tkinter callback
Traceback (most recent call last):
File "***\Python35-32\lib\tkinter\__init__.py", line 1549, in __call__
return self.func(*args)
File "***/TestApp.py", line 63, in <lambda>
command=lambda: objectofpage1.labelChanger(objectofpage1, 'You have changed the label in page 1'))
File "***/TestApp.py", line 51, in labelChanger
self.labelVariable1.set(text)
AttributeError: type object 'Page1' has no attribute 'labelVariable1'
PS: First time posting, I don't know if this is already been answered but I could not find anything to solve this problem so I made an account just to post this.
I appreciate the help a lot and thanks in advance!
The error is with this statement:
objectofpage1 = Page1
You are setting objectofpage1 to a class, not an instance of a class. Your program already has an instance, you just need to get the instance which is managed by the controller.
If each page needs access to the other pages, you simply need to add a method to the controller to return the instance. Something like this:
class ManyPagesApp(tk.Tk):
...
def get_page(self, page_class):
return self.frames[page_class]
class Page2(tk.Frame):
def __init__(self, parent, controller):
...
objectofpage1 = controller.get_page(Page1)
Note: you also have a bug in this statement:
command=lambda: objectofpage1.labelChanger(objectofpage1, 'You have changed the label in page 1'))
It should be:
command=lambda: objectofpage1.labelChanger('You have changed the label in page 1'))
For a more thorough discussion of sharing information between pages, see How to get variable data from a class

Categories

Resources