I am trying to call the instance variable of my inherited class account_validation from my PageTwo class. I think i may be getting the error because of my inhertied class selfService, which main functions is to display various tkinter windows. Credit to #bryan oakely for the interface
class selfService(tk.Tk, Toplevel):
def __init__(self, *args, **kwargs):
selfService.database = Database()
self.employeeWindow = None
tk.Tk.__init__(self, *args, **kwargs)
self.container = tk.Frame(self)
self.container.pack(side="top", fill="both", expand = True)
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
selfService.restart = False
for F in (StartPage, PageOne, PageTwo):
self.frame = F(self.container, self)
self.frames[F] = self.frame
self.frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
self.frame = self.frames[cont]
self.frame.tkraise()
class PageTwo(tk.Frame, account_validation):
def __init__(self, parent, controller):
account_validation.__init__(self)
print(self.test)
tk.Frame.__init__(self, parent)
class account_validation():
def __init__(self):
self.test = 'test'
I am subject to:
print(self.test)
account_validation.init(self)
TypeError: init() missing 2 required positional arguments: 'parent' and 'controller'
Furthermore, I want to understand why people would rather inherit a class and access its properties that way instead of instantiating that class in another class and accessing that class properties that way? What is the best scenario for both? If I am using multiple class methods from the inherited class should I keep it inherited?
Related
I have created a tkinter application which uses a framework very similar to the one shown in this video https://www.youtube.com/watch?v=jBUpjijYtCk
Essentially there is a main class which uses other classes below it as pages. These pages are created as soon as the program is ran.
My problem is that I need data from one of these pages to be sent to another page so that it can display the appropriate data depending on the data that is sent from the previous page.
I have simplified my program down to showcase the issue
from tkinter import *
class TkinterApp(Tk):
def __init__(self):
Tk.__init__(self)
container = Frame(self)
container.grid(row=1, column=0, sticky="nesw")
self.frames = {}
for p in (ClassA, ClassB):
frame = p(parent = container, controller = self)
self.frames[p] = frame
frame.grid(row=0, column=0, sticky="nesw")
self.ShowFrame(ClassA)
def ShowFrame(self, page_name):
page = self.frames[page_name]
page.tkraise()
def PassText(self, text):
self.frames[ClassB].GetData(text)
class ClassA(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
button1 = Button(self, text="button1", command=lambda: self.SubmitInfo("button1"))
button1.grid(row=0, column=0)
def SubmitInfo(self, data):
self.controller.ShowFrame(ClassB)
self.controller.PassText(data)
class ClassB(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
self.label = Label(self, text=self.data)
self.label.grid(column=0, row=0)
def GetData(self, data):
self.data = data[0]
app = TkinterApp()
app.mainloop()
Desired outcome:
the text shown on the ClassB page being "button1"
Current outcome:
AttributeError: 'ClassB' object has no attribute 'data'
to my knowledge, this is happening as the class TkinterApp is causing ClassB to be ran before the value of data exists.
Any help would be greatly appreciated
In line 44, remove Label widget parameter text=self.data
Add .configure in line 49, self.label.configure(text=self.data)
snippet:
class ClassB(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
self.label = Label(self) #<-- line 44
self.label.grid(column=0, row=0)
def GetData(self, data):
self.data = data[0]
self.label.configure(text=self.data) #Add in <---line 49
I'm relatively new to Tkinter and Python and just started with Tkinter in object oriented way.
Im trying to change the background colour of all the different pages I have so i have the following code
import tkinter as tk
import sqlite3
from tkinter.ttk import *
from tkinter import *
LARGE_FONT = ("Verdana", 12)
HEIGHT = 700
WIDTH = 800
class programStart(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, minsize=WIDTH)
container.grid_columnconfigure(0, weight=1, minsize=HEIGHT)
self.frames = {}
for F in (StartPage, Register, LoginPage):
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() #Raises to front
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
I have tried container.configure(bg='red') and so on, to no success
How can I go about this issue?
Try this:
import tkinter as tk
import sqlite3
from tkinter.ttk import *
from tkinter import *
LARGE_FONT = ("Verdana", 12)
HEIGHT = 700
WIDTH = 800
class programStart(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, minsize=WIDTH)
container.grid_columnconfigure(0, weight=1, minsize=HEIGHT) #0 minimum, weight is priority
self.frames = {}
for F in (StartPage, Register, LoginPage):
frame = F(container, self, bg="red")
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() #Raises to front
class StartPage(tk.Frame):
def __init__(self, parent, controller, bg=None, fg=None):
tk.Frame.__init__(self, parent, bg=bg=, fg=fg)
# Make sure that all of the tkinter widgets have `bg=bg=, fg=fg`
Basically you need to tell all of the widgets that you are creating that the background should be red. When creating your widgets you can pass in the bg parameter (background).
This is a minimal version of the system I use to give the user of the GUI the ability to change the colors and fonts of all the widgets in the app according to color schemes he can choose himself.
import tkinter as tk
formats = {
'bg' : 'black',
'fg' : 'white'
}
class Labelx(tk.Label):
def __init__(self, master, *args, **kwargs):
tk.Label.__init__(self, master, *args, **kwargs)
def winfo_subclass(self):
''' a method that works like built-in tkinter method
w.winfo_class() except it gets subclass names
of widget classes custom-made by inheritance '''
subclass = type(self).__name__
return subclass
class Label(Labelx):
'''
If this subclass is detected it will be reconfigured
according to user preferences.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['bg'],
fg=formats['fg'])
class LabelNegative(Labelx):
'''
If this subclass is detected it will be reconfigured
according to user preferences.
'''
def __init__(self, master, *args, **kwargs):
Labelx.__init__(self, master, *args, **kwargs)
self.config(
bg=formats['fg'],
fg=formats['bg'])
def change_colors():
for widg in (lab1, lab2):
if widg.winfo_class() == 'Label':
if widg.winfo_subclass() == 'Label':
widg.config(bg=formats['fg'], fg=formats['bg'])
elif widg.winfo_subclass() == 'LabelNegative':
widg.config(bg=formats['bg'], fg=formats['fg'])
root = tk.Tk()
f1 = tk.Frame(root)
f1.grid(column=0, row=0)
lab1 = Label(f1, text='Label')
lab1.grid()
lab2 = LabelNegative(f1, text='LabelNegative')
lab2.grid()
button = tk.Button(root, text='colorize', command=change_colors)
button.grid()
root.mainloop()
I am trying to create a little GUI with multiple pages. The First page has a button which raises the second page and changes the label text on the second page. However, I fail to call the method of the second page which is supposed to change the text. Can somebody tell me, why I get the following error when calling the method: SecondPage.changeLabel()?
TypeError: changeLabel() missing 1 required positional argument: 'self'
UPDATE:
import tkinter as tk
class ExampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack()
self.frames = {}
for F in (FirstPage, SecondPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(FirstPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self, frame_class):
return self.frames[frame_class]
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
page = controller.get_page(SecondPage)
self.buttonFP = tk.Button(self, text="Next Page",
command=lambda : [f for f in [page.changeLabel(),
controller.show_frame(SecondPage)]])
self.buttonFP.pack()
class SecondPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.label = tk.Label(self, text="Test")
self.label.pack()
def changeLabel(self):
"""change text of label"""
newLabel = "changed"
self.label.configure(text=newLabel)
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()
You get the error because you are trying to call the method on the class rather than on the instance of the class.
Since your architecture has a controller, you can modify the controller to have a function that returns the instance of a page. You can then use the instance to call methods on that page.
Example:
class ExampleApp(tk.Tk):
...
def get_page(self, frame_class):
return self.frames[frame_class]
Later, you can use this method to get the instance of the page, and use the instance to call the method:
page = self.controller.get_page(SecondPage)
page.changeLabel()
I need the buttons within LeftFrame to change its appearance when clicked. In the class AccOne, I tried to do left_frame.acc1.config(releif='SUNKEN'), but I get NameError: name 'left_frame' not defined. I tried making left_frame global, but no luck.
Here's the script.
class MainApp(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack()
container.rowconfigure(4, weight=1)
container.columnconfigure(2, weight=1)
right_frame = RightFrame(container, self)
left_frame = LeftFrame(container, right_frame)
left_frame.pack(side=LEFT)
right_frame.pack()
class RightFrame(Frame):
def __init__(self, parent, controller, *args, **kwargs):
Frame.__init__(self, parent, *args, **kwargs)
self.frames = {}
for F in (Welcome, AccOne, AccTwo, AccThree, AccFour, AccFive):
frame = F(self, self)
self.frames[F] = frame
self.show_frame(Welcome)
def show_frame(self, cont):
frame = self.frames[cont]
frame.grid(row=0, column=0)
frame.tkraise()
class LeftFrame(Frame):
def __init__(self, parent, controller, *args, **kwargs):
Frame.__init__(self, parent)
acc1 = Button(self, text="Account 1", width=12, height=3, command=lambda: controller.show_frame(AccOne))
acc1.pack()
I figured it would make sense to configure the button under def show_frame(self,cont): but I have no idea where to start since that method isn't under LeftFrame.
When creating tkinter windows with classes, try and think about creating a 'widget tree', this being a path through which you can access all of your widgets. In this simple example, MainWindow and SubWindow can access all of eachother's widgets:
class MainWindow(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# widget
self.lbl = tk.Label(self, text='Title')
self.lbl.pack()
# create child window, as class attribute so it can access all
# of the child's widgets
self.child = SubWindow(self)
self.child.pack()
# access child's widgets
self.child.btn.config(bg='red')
class SubWindow(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# can use this attribute to move 'up the tree' and access
# all of mainwindow's widgets
self.parent = parent
# widget
self.btn = tk.Button(self, text='Press')
self.btn.pack()
# access parent's widgets
self.parent.lbl.config(text='Changed')
Things to change in your code
Firstly, every time you create a widget that you might want to access later, assign it to a class variable. For example (this is part of the cause of your problem):
self.left_frame
self.acc1
not
left_frame
acc1
Secondly, make proper use of your parent and controller arguments. You're doing these, but you never use them or assign them to an atribute, so they may as well not be there. Assign them to a self.parent or self.controller attribute, so if you need to access them in a method later, you can.
I don't know exactly what you're trying to do and I can't see your AccOne class, but you should be able to find a way to access that button by making these changes.
Good luck!
This problem appears to be hard to duplicate -- as I am able to correctly do it in a shorter program. What I'm hoping for is maybe some guidance on what could be going wrong. I have posted the version where it works correctly:
import tkinter as tk
from tkinter import *
from tkinter import ttk
class DIS(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "program")
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.usernameVar = StringVar()
self.frames = {}
for F in (StartPage, contactQues, nowTry, next):
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()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
button2 = ttk.Button(self, text = "Here's a Button", command= lambda: controller.show_frame(nowTry))
button2.pack()
class nowTry(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.entry1 = Entry(self)
self.entry1.pack()
self.button1 = ttk.Button(self, text = "Yes", command = self.go)
self.button1.pack()
self.entry1.bind("<Return>", self.go)
def go(self, event=None):
print (self.entry1.get())
self.controller.show_frame(contactQues)
class contactQues(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.entry1 = Entry(self)
self.entry1.pack(pady=10, padx=10)
self.button1 = ttk.Button(self, text = "Submit", command= self.UsernameSubmit)
self.button1.pack()
self.entry1.bind("<Return>", self.UsernameSubmit)
def UsernameSubmit(self, event=None):
UsernameEntryGet = self.entry1.get()
self.controller.usernameVar.set(UsernameEntryGet)
self.controller.show_frame(next)
class next(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, textvariable = self.controller.usernameVar)
label.pack() ###Label is posted with input correctly
The issue I'm having with my main program is that the self.controller.usernameVar label does not post like it does in this example (nothing shows up at all) when the Return key is pressed to submit the input. However, when the submit button is clicked with the mouse, the label appears properly.
So, given this information, I feel as if my bind("<Enter>"... command is being managed wrong. I've tried self.bind..., self.controller.bind..., self.entryX.bind... without success.
Any ideas with this framework what could be wrong?
I believe I figured it out. The issue was that in my full program, I had multiple bind commands. While I was trying to solve this issue, I had some entry prompts bound to the controller and others to the entry itself (e.g,. self.controller.bind in a few classes and self.entry.bind in others).
I changed them all to self.entry.bind and it apparently fixed it -- which is why this code snippet worked as expected.