So I'm trying to handle two windows in python tkinter that can edit an xml file containing the path to certain images, the problem is the function that reads the file is in the __init__ of my two class windows so whenever I switch between the windows the same images appear. My question is: is there any way to relaunch the classes so that the __init__ will run?
I believe the problem is within the show_frame function since tkraise does not run the functions in the class but just pops whatever is in the class to the top.
class xmleditApp(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 (FirstWindow, SecondWindow):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(FirstWindow)
def show_frame(self, cont):
frame=self.frames[cont]
frame.tkraise()
This is the class that I want to update automatically when I return to it:
class FirstWindow(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
secondwindow=tk.Button(self,text="edit xml",command=lambda: controller.show_frame(SecondWindow))
secondwindow.grid(row=3,column=0)
def getimagepath():
doc = parse('pruebasXML.xml')
paths = doc.getroot()
path = paths.findall('path')
pasthlist = []
for elem in range (0,len(path)):
pathlist+=[[]]
for tag in range (0,11):
pathlist[elem]+=[path[elem][tag].text]
return createbutton(pasthlist)
def createbutton(lst):
for elem in range(0,len(lst)):
buttonName=elem
photo=tk.PhotoImage(file=lst[elem][0]+'.gif')
buttonName=tk.Button(self, image=photo)
buttonName.grid(row=2, column=elem)
buttonName.image=photo
if os.path.exists('pathsXML.xml')==True:
getimagepath()
If you have a class called myclass, you can manually call __init__ by using myclass.__init__(self, etc). Here, self is the self argument, and etc are the other arguments.
Related
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()
So i m building a note app which contains 5 pages (login,register,welcoming,text_writting,archived_text)
the app can take multiple users and each user have his own notes stored in the database
the problem when i disconnect from a user and i want to switch to the other user it keeps the old user infos without refreshing and showing the new one ,it shows only when i close the app and reopen it
Plus -> when i check the database everything is going well the date do update the problem is just in the GUI
class main_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 = {}
data_baseconnect=sqlite3.connect('NOTES_DB.db')
data_cursor=data_baseconnect.cursor()
data_cursor.execute('SELECT current_state from LOGINS_STATE')
data_baseconnect.commit()
Current_person_state = data_cursor.fetchall()
for frame_class in (Login_page,Register_page, Welcoming_page,notes_page1,notes_page2):
frame = frame_class(container, self)
self.frames[frame_class] = frame
frame.grid(row=0, column=0, sticky="nsew")
if Current_person_state[0][0]==0:
self.show_frame(Login_page)
else :
self.update()
self.show_frame(Welcoming_page)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
#Login page
class Login_page(tk.Frame):
def __init__(self, parent, controller):
def user_connect() :
#Login_page_User_name_Entry is an entry when the user type the new person he want to connect
#through // this user is already in the data base
global Current_userV2
database_connection=sqlite3.connect('NOTES_DB.db')
data_currsor=database_connection.cursor()
Login_page_Error_connect.config(text='')
Current_userV2=str(Login_page_User_name_Entry.get())
data_currsor.execute("UPDATE LOGINS_STATE SET current_user=(?),current_state=(?)",((str(Login_page_User_name_Entry.get())),('1')))
database_connection.commit()
controller.show_frame(Welcoming_page)
tk.Frame.__init__(self,parent,bg='#EDD01C')
Login_page_connect_button=Button(Login_page_frame,text="Connect",width=20,height=2,bg='green',command=user_connect)
Login_page_connect_button.place(x=80,y=300)
.....
class Welcoming_page(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent,bg='#EDD01C')
def Leave_button() :
global Current_userV2
database_connect=sqlite3.connect('NOTES_DB.db')
data_cursor=database_connect.cursor()
data_cursor.execute('UPDATE LOGINS_STATE set current_user=NULL,current_state=0')
database_connect.commit()
Current_userV2=''
controller.show_frame(Login_page)
disconnect_button=Button(self,text="Disconnect",bg='red',command=Leave_button,font=('arial',15))
disconnect_button.place(x=350,y=500)
....
i figured out here s how :
just changing in the main_app class :
class main_app(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.container = tk.Frame(self)
self.container.pack(fill="both", expand=True)
self.current_frame = None
data_baseconnect=sqlite3.connect('NOTES_DB.db')
data_cursor=data_baseconnect.cursor()
data_cursor.execute('SELECT current_state from LOGINS_STATE')
data_baseconnect.commit()
Current_person_state = data_cursor.fetchall()
if Current_person_state[0][0]==0:
self.show_frame(Login_page)
else :
self.show_frame(Welcoming_page)
def show_frame(self, new_frame_class):
if self.current_frame:
self.current_frame.destroy()
self.current_frame = new_frame_class(self.container, controller=self)
self.current_frame.pack(fill="both", expand=True)
I'm a teacher and every week I grade students' participation score. Since I teach computers, I thought I'd create a program to handle that logic for me. I plan on using TkInter to create a start screen with the 4 periods of the day, and depending on the period, it would pull up that class. But I'm trying to use the same class for all 4 periods, since the code is exactly the same.
Here's my code:
class part(tk.Tk):
#Creates a class for the GUI
def __init__(self, *args, **kwargs):
#Initialization function of partGUI
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default="") #default icon in an .ico file
tk.Tk.wm_title(self, "Lucey's Participation Program") #title
window = tk.Frame(self)
window.pack(side="top", fill="both", expand=True)
window.grid_rowconfigure(0, weight=1)
window.grid_columnconfigure(0, weight=1)
self.frames= {}
for F in (StartPage, ClassPart, SettingsPage):
frame = F(window, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, window):
#Allows Program to switch windows/pages/frames
frame = self.frames[window]
frame.tkraise()
class StartPage(tk.Frame):
# Home Screen for Program
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
title = tk.Label(self, text="Participation Scores", font=LARGE_FONT)
title.pack(pady=10, padx=10)
btnPeriod1 = tk.Button(self, text="1st Period", fg="red",
command=lambda: controller.show_frame(ClassPart(controller, 1)))
btnPeriod1.pack()
class ClassPart(tk.Frame):
# Screen showing students, participation buttons & their scores/Hogwarts houses
def __init__(self, parent, controller, period):
tk.Frame.__init__(self, parent)
But this throws an error:
Traceback (most recent call last):
File "/home/klucey/Documents/partvers2.py", line 307, in <module>
window = part()
File "/home/klucey/Documents/partvers2.py", line 40, in __init__
frame = F(window, self)
TypeError: __init__() missing 1 required positional argument: 'period'
Any help to a beginner/intermediate would be greatly appreciated!
This boilerplate code that you (and everybody else on SO, it seems) are using to handle a multi-page Tkinter app is simply not suitable for handing multiple pages of the same class. You'd have to put multiple occurrences of ClassPart in the page list, and somehow arrange for them to be given a different period parameter when constructed - but this breaks the .show_frame() method, as you no longer have a unique identifier to select the page to be shown.
Here's what I would suggest instead, given that you have a fixed set of pages (this wouldn't work so well with dynamically-generated pages):
Get rid of the period parameter in your class (so that its constructor is compatible with the other pages).
Create a subclass of it for each period:
class ClassPart1(ClassPart):
period = 1
class ClassPart2(ClassPart):
period = 2
... and so on. Refer to self.period in the base class to access this value.
Change the initial page creation loop to for F in (StartPage, ClassPart1, ClassPart2, ClassPart3, ClassPart4, SettingsPage):. Use ClassPart1, etc. as the identifier to pass to .show_frame().
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)
I am trying to create a 2 mins countdown app with multiple windows. I am putting all windows(frames) into the main container, and then use tkraise() to raise the frame whenever the navigation button to specific window is clicked(eg:'startPage' frame will be raised if 'back to startPage' button is clicked).
The code below works well only when the object of class PracticePage was first created.
However, the timer is still running at the back when I navigate from the countdown timer frame(class PracticePage) to another page. In other words, the timer will not count down from 2 mins whenever I navigate from another page back to the countdown timer page. I want it to countdown from 2 mins whenever the timer frame is raised.
I am a beginner in programming. I apologized if my question and code is confusing. Can someone help? Thank you in advance.
Below is my code:
import tkinter as tk
from tkinter import *
from tkinter import ttk
import time
class App(tk.Tk): #we want this class to inherit from tk.Tk
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self,"PreSys")
container = tk.Frame(self, height = 1000, width =1000)
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 (SignInPage, StartPage, PracticePage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(SignInPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise() #to raise one of the frames up to the front
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.startButton = ttk.Button(self, text="スタート", command = lambda: controller.show_frame(PracticePage))
self.startButton.pack()
class PracticePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.timeLeft = tk.Label(self,text= "")
self.backButton = ttk.Button(self, text="やり直す", command = lambda: controller.show_frame(StartPage))
self.homeButton = ttk.Button(self, text="サインアウト", command = lambda: controller.show_frame(SignInPage))
self.timeLeft.pack()
self.backButton.pack()
self.homeButton.pack()
#rc.start_record(SignInPage.entry_name.get())
self.remaining = 0
self.countdown(121)
def countdown(self, remaining = None):
if remaining is not None:
self.remaining = remaining
if self.remaining <= 0:
self.timeLeft.configure(text="お疲れ様です!")
else:
mins, secs = divmod(self.remaining,60)
mins = round(mins)
secs = round(secs)
self.timeLeft.configure(text=str(mins) +"分"+ str(secs) +"秒")
self.remaining = self.remaining - 1
self.after(1000, self.countdown)
apps = App()
apps.mainloop()
Before we get to the solution, when you supply your code make sure it works. You removed the SignInPage because it wasn't necessary, but you didn't follow up and remove the other references to it (i.e.- self.show_frame(SignInPage) in App.__init__ and self.homebutton in PracticePage.
As for your issue with resetting the timer, there are many ways to go about it, but they all come down to the fact that all you are doing is raising frames: you don't include any code that influences countdown based on what frame you are currently switching to.
Since this is a relatively simple application, I'll suggest the simplest solution: when you raise PracticePage reset its remaining variable. To keep it as easy and consistent as possible, we'll just modify show_frame
## You should store variables that are meaningful (you can place this underneath the imports)
PRACTICETIME = 121
## App():
def show_frame(self, cont):
frame = self.frames[cont]
if cont == PracticePage:
frame.remaining = PRACTICETIME
frame.tkraise() #to raise one of the frames up to the front
In a larger application I would recommend either using widget.after_cancel() to stop the countdown completely or simply to destroy and recreate your Pages if reasonable to do so. With this method you may notice a 1-second lag where the PracticePage is still displaying the previous remaining time: if this really bothers you, then I would recommend extracting the code that formats PracticePage.timeLeft into its own function and then calling that function from App.show_frame right after you set remaining.