Make a Tkinter Gui using classes - python

I want to create a GUI in a class that can create pages using another class that creates frames and widgets. The pages are set using a class for them. I want to be able to GUI to be able to switch between the different set of pages. I can't create a button for the class of the Login_page that with switch the Login_page with the class of the Sign_page.
from tkinter import *
import random
class maingui:
def __init__(self,title, geometry,):
self.root = Tk()
self.root.title(title)
self.root.geometry(geometry)
self.pageshow = Login_Page(self.root)
self.root.mainloop()
def changepage(self, page):
self.page = page
if self.page == 0:
self.pageshow = Login_Page(self.root)
if self.page == 1:
self.pageshow = Sign_Page(self.root)
self.root.mainloop()
class createWindow:
def __init__(self,root, title, geometry,):
self.root = root
self.root.title(title)
self.root.geometry(geometry)
self.root.mainloop()
class createFrame:
def __init__(self,window):
self.window = window
self.frame = Frame(self.window)
self.frame.pack()
print('c')
def clear(self):
pass
def createlabel(self,message,postion = None):
self.message =message
self.postion = postion
self.label= Label(self.frame, text =self.message)
if self.postion == None:
self.label.pack()
print('a')
else:
print('b')
def createbutton(self, text, command):
self.text = text
self.command = command
self.button = Button(self.frame, text = self.text, command =self.command)
class Login_Page():
def __init__(self,window):
self.window = window
self.frame = createFrame(self.window)
self.frame.createlabel("Hello World")
self.frame.createbutton("1",maingui.changepage(self.window,1))
class Sign_Page():
def __init__(self,window):
self.window = window
self.frame = createFrame(self.window)
self.frame.createlabel("Hello ")
maingui = maingui("Rpg", "400x400")
Edit: Exception has occurred: AttributeError
'_tkinter.tkapp' object has no attribute 'root'
On line
if self.page == 1:
self.pageshow = Sign_Page(self.root)
On this line
self.frame.createbutton("1",maingui.changepage(self.window,1))
I try to create a button using a class and the command is from a different class.

Since you pass self.root (instance of Tk()) of maingui class to Login_Page and assign it to self.window. Then self.window is used in maingui.changepage(self.window, 1) inside Login_Page class. Since you use class name to access changepage(), self.window will be treat as the self argument of changepage() and used in Sign_Page(self.root). That means you want to access the attribute root of self (instance of Tk()) but Tk does not have attribute root.
Suggest to make maingui interit from Tk instead of creating it inside __init__().
Below is an updated example based on yours:
from tkinter import *
#import random
class maingui(Tk):
def __init__(self, title, geometry):
super().__init__()
self.title(title)
self.geometry(geometry)
self.pageshow = Login_Page(self)
def changepage(self, page):
self.page = page
if self.page == 0:
self.pageshow = Login_Page(self)
if self.page == 1:
self.pageshow = Sign_Page(self)
class createFrame:
def __init__(self,window):
self.window = window
self.frame = Frame(self.window)
self.frame.pack()
print('c')
def clear(self):
pass
def createlabel(self,message,postion = None):
self.message =message
self.postion = postion
self.label= Label(self.frame, text =self.message)
if self.postion == None:
self.label.pack()
print('a')
else:
print('b')
def createbutton(self, text, command):
self.text = text
self.command = command
self.button = Button(self.frame, text = self.text, command =self.command)
self.button.pack() ###
class Login_Page():
def __init__(self,window):
self.window = window
self.frame = createFrame(self.window)
self.frame.createlabel("Hello World")
###self.frame.createbutton("1", maingui.changepage(self.window, 1)) ###
self.frame.createbutton("1", lambda: self.window.changepage(1)) ###
class Sign_Page():
def __init__(self,window):
self.window = window
self.frame = createFrame(self.window)
self.frame.createlabel("Hello ")
maingui = maingui("Rpg", "400x400")
maingui.mainloop()

Here is a working code, I would also suggest you read and practice classes before moving further.
(Note: I have not defined your clear method or added any new method, I have just corrected your existing code and rectified your error. you may further if you want, define your clear method and destroy your widgets).
from tkinter import *
import random
class maingui:
def __init__(self, root, title, geometry):
self.root = root
self.root.title(title)
self.root.geometry(geometry)
self.pageshow = Login_Page(self, self.root)
def changepage(self, page):
self.page = page
if self.page == 0:
self.pageshow = Login_Page(self, self.root)
if self.page == 1:
self.pageshow = Sign_Page(self, self.root)
class createWindow:
def __init__(self,root, title, geometry):
self.root = root
self.root.title(title)
self.root.geometry(geometry)
class createFrame:
def __init__(self, parent, window):
self.window = window
self.frame = Frame(self.window)
self.frame.pack()
self.label = Label(self.frame)
self.button = Button(self.frame)
self.parent = parent
def clear(self):
pass
def createlabel(self,message,postion = None):
self.message =message
self.postion = postion
self.label.config(text=self.message)
if self.postion == None:
self.label.pack()
else:
pass
def createbutton(self, text='Login', cmd=0):
self.text = text
self.button.configure(text=self.text, command=lambda :self.parent.changepage(cmd))
self.button.pack()
class Login_Page():
def __init__(self, parent, window):
self.window = window
self.frame = createFrame(parent, self.window)
self.frame.createlabel("Hello World bye")
self.frame.createbutton("Welcome", 1)
class Sign_Page():
def __init__(self, parent, window):
self.window = window
self.frame = createFrame(parent, self.window)
self.frame.createlabel("Hello ")
def main():
root = Tk()
maingui(root, "Rpg", "400x400")
root.mainloop()
if __name__ =='__main__':
main()
update: here is a way you can switch between pages(in my case you can switch between the sign-up and login page). click on login it will direct you to sign-up and vice-versa.
from tkinter import *
import random
class maingui:
def __init__(self, root, title, geometry):
self.root = root
self.root.title(title)
self.root.geometry(geometry)
self.pageshow = Login_Page(self, self.root)
def changepage(self, page):
self.page = page
if self.page == 0:
#del self.pageshow
self.pageshow = Login_Page(self, self.root)
if self.page == 1:
#del self.pageshow
self.pageshow = Sign_Page(self, self.root)
class Login_Page:
def __init__(self, parent, window):
self.parent = parent
self.frame = Frame(window)
self.frame.pack()
self.welcm_lbl = Label(self.frame, text='welcome')
self.welcm_lbl.grid(row=0, column=1)
self.name_lbl = Label(self.frame, text='name:')
self.name_lbl.grid(row=1, column=0)
self.name_entry = Entry(self.frame)
self.name_entry.grid(row=1, column=1)
self.sbt = Button(self.frame, text='login', command=self.clicked)
self.sbt.grid(row=2, column=1)
def clicked(self):
self.frame.destroy()
self.parent.changepage(1)
class Sign_Page():
def __init__(self, parent, window):
self.parent = parent
self.frame = Frame(window)
self.frame.pack()
self.welcm_lbl = Label(self.frame, text='welcome sign-up')
self.welcm_lbl.grid(row=0, column=1)
self.name_lbl = Label(self.frame, text='name:')
self.name_lbl.grid(row=1, column=0)
self.name_entry = Entry(self.frame)
self.name_entry.grid(row=1, column=1)
self.sbt = Button(self.frame, text='sign-up', command=self.clicked)
self.sbt.grid(row=2, column=1)
def clicked(self):
self.frame.destroy()
self.parent.changepage(0)
def main():
root = Tk()
maingui(root, "Rpg", "400x400")
root.mainloop()
if __name__ =='__main__':
main()

Related

How can I use 2 classes on a tkinter button?

The Btn class is supposed to overlay an image on a button. In this case, it's supposed to highlight the button once hovered over it. I'm trying to also get a tooltip message to pop up CreateToolTip when hovering over the button. At the moment, it is only displaying one of the classes at a time. Is there a way I can display both without them clashing?
Here's my code:
from tkinter import Button, Toplevel, Label, PhotoImage
class Btn(Button):
def __init__(self, root, img1, img2, *args, **kwargs):
super().__init__(root, *args, **kwargs)
self.img = PhotoImage(file=img1).subsample(2, 2)
self.img2 = PhotoImage(file=img2).subsample(2, 2)
self['image'] = self.img
self.bind('<Enter>', self.enter)
self.bind('<Leave>', self.leave)
def enter(self, event):
self.config(image=self.img2)
def leave(self, event):
self.config(image=self.img)
class CreateToolTip(Btn):
def __init__(self, widget, text='widget info'):
self.waittime = 600 #miliseconds
self.wraplength = 180 #pixels
self.widget = widget
self.text = text
self.widget.bind("<Enter>", self.enter)
self.widget.bind("<Leave>", self.leave)
self.widget.bind("<ButtonPress>", self.leave)
self.id = None
self.tw = None
def enter(self, event=None):
self.schedule()
def leave(self, event=None):
self.unschedule()
self.hidetip()
def schedule(self):
self.unschedule()
self.id = self.widget.after(self.waittime, self.showtip)
def unschedule(self):
id = self.id
self.id = None
if id:
self.widget.after_cancel(id)
def showtip(self, event=None):
x = y = 0
x, y, cx, cy = self.widget.bbox("insert")
x += self.widget.winfo_rootx() + 20
y += self.widget.winfo_rooty() + 40
# creates a toplevel window
self.tw = Toplevel(self.widget)
# Leaves only the label and removes the app window
self.tw.wm_overrideredirect(True)
self.tw.wm_geometry("+%d+%d" % (x, y))
label = Label(self.tw, text=self.text, justify='left',
background="#ffffff", relief='solid', borderwidth=1,
wraplength = self.wraplength)
label.pack(ipadx=1)
def hidetip(self):
tw = self.tw
self.tw= None
if tw:
tw.destroy()
I imported the classes from another Python file. In my main project this is how they are used:
play = 'Button IMG/Play.png'
playHover = 'Button IMG/MouseHoverAni/Play.png'
playBtn = Btn(toolBar, img1=play, img2=playHover, borderwidth=0, command=run)
playBtn.pack(side=LEFT)
CreateToolTip(playBtn, 'Play')

How to change row-'value' every time i try to position a label using grid?

I am trying to create a terminal GUI using python tkinter module and I already managed to access the tk.Label from TextFrame class.
class TextFrame(tk.Frame):
def __init__(self, container):
super().__init__()
self.container = container
self['bg']='black'
self.pack(fill='both', expand=True)
self.label = tk.Label(self)
Now the problem is that, I can't change the row everytime I call show() function inside Instances class.
class Instances(Window):
def __init__(self):
super().__init__()
self.string = tk.StringVar()
self.index_row = 0
self.text_frame = TextFrame(self)
self.text_frame.pack()
self.entry_frame = EntryFrame(self)
self.entry_frame.pack(side='bottom',fill='both')
def show(self, e):
textvar = self.string.get()
self.entry_frame.entry.delete(0, 'end')
self.text_frame.label.config(text=textvar)
self.text_frame.label.grid(column=0, row=self.index_row, sticky="w")
self.index_row += 1
Here is the full code of my problem:
import tkinter as tk
class Window(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry('800x600')
self.title('Terminal')
class Instances(Window):
def __init__(self):
super().__init__()
self.string = tk.StringVar()
self.index_row = 0
self.text_frame = TextFrame(self)
self.text_frame.pack()
self.entry_frame = EntryFrame(self)
self.entry_frame.pack(side='bottom',fill='both')
def show(self, e):
textvar = self.string.get()
self.entry_frame.entry.delete(0, 'end')
self.text_frame.label.config(text=textvar)
self.text_frame.label.grid(column=0, row=self.index_row, sticky="w")
self.index_row += 1
class TextFrame(tk.Frame):
def __init__(self, container):
super().__init__()
self.container = container
self['bg']='black'
self.pack(fill='both', expand=True)
self.label = tk.Label(self)
class EntryFrame(tk.Frame):
def __init__(self, container):
super().__init__()
self.entry = tk.Entry(self, textvariable=container.string)
self.entry.pack(fill='both')
self.entry.bind('<Return>', container.show)
if __name__ == '__main__':
window = Instances()
window.mainloop()
Any help would be appreciated.
Problem Solved
I added 1 line of code on show() function
def show(self, e):
textvar = self.string.get()
self.entry_frame.entry.delete(0, 'end')
self.text_frame.label = tk.Label(self.text_frame, fg='green', bg='black')
self.text_frame.label.config(text=textvar)
if len(textvar) > 0 :
self.text_frame.label.grid(column=0, row=self.index_row, sticky="w")
self.index_row += 1

.delete is not working inside class in tkinter

I am trying to make a tkinter desktop application (Notepad) using classes but I found an Attribute Error in my code. I made three files "menu_option.py", "textarea.py" and "window_frame.py". Run the "menu_option.py" file so that you found the error. I am using python (3.9). Also is there any another way to connect "new_file" function to menu item.
Here is my code below:
menu_option.py
from tkinter import *
from textarea import TextArea
from window_frame import Window
class Menu_Option(TextArea):
file = None
def __init__(self, master, newfile=None):
super().__init__(root)
self.Menubar = 'MenuBar'
self.Filemenu = 'FileMenu'
self.root = self.root
self.master = self.master
self.newfile = newfile
def launch(self):
self.Menubar = Menu(self.root)
self.Filemenu = Menu(master=self.Menubar, tearoff=0)
self.Filemenu.add_command(label='New File...', command=self.newfile)
self.Menubar.add_cascade(label='File', menu=self.Filemenu)
self.root.config(menu=self.Menubar)
class Features(Menu_Option):
def __init__(self, master):
super().__init__(master, newfile=self.new_file)
def new_file(self):
global file
self.root.title(self.title)
file = None
self.textarea.delete(1.0, END)
if __name__ == '__main__':
root = Tk()
Window(root).launch()
TextArea(root).launch()
Menu_Option(root).launch()
Features(root).launch()
root.mainloop()
textarea.py
from tkinter import *
from window_frame import Window
from tkinter.scrolledtext import ScrolledText
class TextArea(Window):
def __init__(self, name):
super().__init__(name)
self.name = self.root
self.master = 'root'
self.textarea = 'text_area'
self.font = 'courier 14 normal'
def launch(self):
self.textarea = ScrolledText(self.root, font=self.font)
self.textarea.pack(expand=True, fill=BOTH)
if __name__ == '__main__':
root = Tk()
Window(root).launch()
TextArea(root).launch()
root.mainloop()
window_frame.py
from tkinter import *
class Window:
def __init__(self, root):
self.root = root
self.geometry = '1000x550+100+100'
self.title = 'Untitled - ProBook'
def launch(self):
self.root.geometry(self.geometry)
self.root.title(self.title)
if __name__ == '__main__':
root = Tk()
Window(root).launch()
root.mainloop()
Error:
Exception in Tkinter callback
Traceback (most recent call last):
File "D:\Installed Programs\Python\lib\tkinter\__init__.py", line 1892, in __call__
return self.func(*args)
File "C:\Users\vaish\OneDrive\ProBook\menu_option.py", line 35, in new_file
self.textarea.delete(1.0, END)
AttributeError: 'str' object has no attribute 'delete'
Since Menu_Option() has override launch() function, therefore TextArea.launch() will not be executed and so instance variable textarea is still a string.
If child class wants to inherit parent class launch() functionality, you need to call the parent class launch() in its launch() function:
textarea.py
class TextArea(Window):
...
def launch(self):
super().launch() # call parent class launch()
self.textarea = ScrolledText(self.root, font=self.font)
self.textarea.pack(expand=True, fill=BOTH)
menu_option.py
class Menu_Option(TextArea):
...
def launch(self):
super().launch() # execute parent class launch()
...
...
if __name__ == "__main__":
root = Tk()
#Window(root).launch() # <- no need to execute
#TextArea(root).launch() # <- no need to execute
#Menu_Option(root).launch() # <- no need to execute
Features(root).launch()
root.mainloop()
Note that Window(root).launch(), TextArea(root).launch() and Menu_Option(root).launch() are not required.
You have initialized self.textarea="text_area" in textarea.py . But when you import it in menu_option.py, you are overwriting the function launch, which is supposed to set the value of self.textarea to a ScrolledText and pack it. To solve this you have to include the code in launch function of textarea.py in the function launch of Menu_Option class.
from tkinter import *
from tkinter.scrolledtext import ScrolledText
from textarea import TextArea
from window_frame import Window
class Menu_Option(TextArea):
file = None
def __init__(self, master, newfile=None):
super().__init__(root)
self.Menubar = 'MenuBar'
self.Filemenu = 'FileMenu'
self.root = self.root
self.master = self.master
self.newfile = newfile
def launch(self):
self.Menubar = Menu(self.root)
#You have to include these 2 lines of code which were overwritten
self.textarea = ScrolledText(self.root, font=self.font)
self.textarea.pack(expand=True, fill=BOTH)
self.Filemenu = Menu(master=self.Menubar, tearoff=0)
self.Filemenu.add_command(label='New File...', command=self.newfile)
self.Menubar.add_cascade(label='File', menu=self.Filemenu)
self.root.config(menu=self.Menubar)

How to implement listener using tkinter?

I am trying to develop a dialog box to be used with tkinter that has an entry box and a button. I want to be able to enter a value in the entry box and have the entered value "returned" when the dialog is destroyed. The following code works, but does not perform as I've described. There are two buttons on the gui. The first launches the dialog box and the second retrieves the entered value. I wish to eliminate the second button and have a listener activate the getValues method when the "Save Input" button in the dialog is pressed. Here is the code:
from tkinter import *
class myDialog():
def __init__(self):
self.t = Toplevel()
self.t.title("Sample")
self.answer = None
self.v1 = StringVar()
self.e1 = Entry(self.t, textvariable=self.v1)
self.e1.focus()
self.e1.pack()
self.saveButton = Button(self.t, text="Save Input", command=self.buttonPressed)
self.saveButton.pack()
def buttonPressed(self):
print("Popup Button Pressed")
self.answer = self.e1.get()
self.t.destroy()
class myTk(Tk):
def __init__(self):
Tk.__init__(self)
self.button = Button(text="Display Dialog", command = self.displayPopButton)
self.button.pack()
self.getButton = Button(text="Print Values", command=self.getValues)
self.getButton.pack()
def displayPopButton(self):
self.b1 = myDialog()
def getValues(self):
print(self.b1.answer)
myTk().mainloop()
You could be passing in your main object as a param in your Dialog, and call the master method within the buttonPressed method:
class myDialog():
def __init__(self, master):
self.t = Toplevel()
self.master = master
# ... #
def buttonPressed(self):
print("Popup Button Pressed")
self.answer = self.e1.get()
self.master.getValues()
self.t.destroy()
class myTk(Tk):
# ... #
def displayPopButton(self):
self.b1 = myDialog(self)
This achieves what you want, but personally I don't think it's good OOP. It makes your Dialog reliant on having the expected master type and the required method. You could organize it a little differently to be more explicit::
class myDialog():
def __init__(self, func_to_call):
self.t = Toplevel()
self.btn_func = func_to_call
# ... #
def buttonPressed(self):
print("Popup Button Pressed")
self.answer = self.e1.get()
func_to_call()
self.t.destroy()
class myTk(Tk):
# ... #
def displayPopButton(self):
self.b1 = myDialog(self.getValues)
In any case, I would at least subclass myDialog as a Toplevel. And perhaps rethink how I want the objects to refer to each other.
You can make myDialog a modal dialog using grab_set() and wait_window():
from tkinter import *
class myDialog():
def __init__(self):
self.t = Toplevel()
self.t.title("Sample")
self.answer = None
self.v1 = StringVar()
self.e1 = Entry(self.t, textvariable=self.v1)
self.e1.focus()
self.e1.pack()
self.saveButton = Button(self.t, text="Save Input", command=self.buttonPressed)
self.saveButton.pack()
# handle user clicking the 'x' button at the title bar
self.t.protocol('WM_DELETE_WINDOW', self.buttonPressed)
def buttonPressed(self):
print("Popup Button Pressed")
self.answer = self.e1.get()
self.t.destroy()
def show(self):
# make the toplevel act like a modal dialog
self.t.grab_set()
self.t.wait_window(self.t)
# return the answer when the toplevel is closed/destroyed
return self.answer
class myTk(Tk):
def __init__(self):
Tk.__init__(self)
self.button = Button(text="Display Dialog", command = self.displayPopButton)
self.button.pack()
def displayPopButton(self):
self.b1 = myDialog().show()
print(self.b1)
myTk().mainloop()

Passing a class to functions complications

I am writing a python program with tkinter but I ran into some complications where I don't know how I should deal with.
UPDATE: functional code to demonstrate the problem
import tkinter as tk
class MainApp(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.label = tk.Label(self, text='Schedule')
self.label.pack()
self.mtp_enter = False
self.enter_small_shifts = False
self.Button_Frame = Button_Frame(self)
self.widgets = self.Button_Frame.New()
self.Button_Frame.pack()
def toggle_enter_small_shifts():
if self.enter_small_shifts == False:
if self.mtp_enter == True:
self.toggle_mtp_enter()
self.label.configure(text='MTP')
self.enter_small_shifts = True
else:
self.label.configure(text='schedule')
self.enter_small_shifts = False
def toggle_mtp_enter():
if self.mtp_enter == False:
if self.enter_small_shifts == True:
self.toggle_enter_small_shifts()
self.label.configure(text='MTP')
self.mtp_enter = True
else:
self.label.configure(text='schedule')
self.mtp_enter = False
if __name__ == "__main__":
root= tk.Tk()
root.wm_title("Shelter Schedule Maker")
app = MainApp(root)
app.pack()
root.mainloop()
class Button_Frame(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
def New(self):
widgets = {}
toggle_enter_mtp = tk.Button(self, text='Enter MTP\'s', command=app.toggle_mtp_enter, width=15)
widgets['enter mtp'] = toggle_enter_mtp
toggle_enter_mtp.pack()
toggle_enter_small_shifts = tk.Button(self, text='Enter small shift\'s', command=app.toggle_enter_small_shifts, width=15)
widgets['enter small shifts'] = toggle_enter_small_shifts
toggle_enter_small_shifts.pack()
return widgets
In conclusion: before MainApp I need to define Button_Frame, before Button_Frame I need the MainApp instance, before the Mainapp instance I need the MainApp class. There is a full circle.
How should I restruture this to work?
You should define all methods in the class to be instance methods, and use self within the class and app outside the class:
class MainApp(tk.Frame):
...
def toggle_mtp_enter(self):
if self.enter_mtp == False:
if self.enter_small_shifts == True:
self.toggle_enter_small_shifts()
self.shift_buttons_widgets = self.shift_buttons.Activate_mtp()
...
app = MainApp(...)
...
toggle_enter_mtp = tk.Button(self, text='Enter MTP\'s', command=app.toggle_mtp_enter, width=15)
...
Ideally, Button_Frame shouldn't rely on a global variable app. You're already passing in an instance of the app when you create Button_frame (as parent), so you can do something like this:
class Button_Frame(tk.Frame):
...
def New(self):
...
toggle_enter_mtp = tk.Button(..., command=self.parent.toggle_mtp_enter, ...)
...
toggle_enter_small_shifts = tk.Button(..., command=self.parent.toggle_enter_small_shifts, ...)
...
You also need to define all of your classes before creating the app:
class MainApp(...):
...
class Button_Frame(...):
...
if __name__ == "__main__":
...

Categories

Resources