How can I configure a widget that is in a different class? - python-3.x

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!

Related

How to pass variables from one class to another after a class has been instantiated?

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

Python OOP inheritance use cases and calling inherited object

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?

Where is my python tkinter grid from this inheritance?

this is a View and Controller part of a program that I am intending writing. My question is why I can't see my grid. My suspicion is that I am not inheriting correctly.
I think the problem is happening here:
"self.frame=Small_Frame(self)"
This is what I understand from my code. class Controller is inheriting from tk. class View is inheriting from tk.Frame. Up to here everything works.
class Small_Frame is my customer widget. The grid is just 12 instances of class Small_Frame using grid() method. I don't know why is it not showing up. Please help me understand. thank you.
import tkinter as tk
class View(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, bg= "yellow", bd =2, relief = tk.RIDGE)
self.parent = parent
self.controller = controller
self.pack(fill=tk.BOTH, expand=1)
for r in range(3):
self.rowconfigure(r, weight=1)
for c in range(4):
self.columnconfigure(c, weight=1)
self.frame=Small_Frame(self)
self.frame.grid(row = r, column = c, padx=1, pady = 1, sticky=
(tk.N, tk.S, tk.W, tk.E))
class Small_Frame(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, borderwidth=1, relief="groove")
self.parent = parent
self.pack(fill=tk.BOTH, expand=1)
class Controller():
def __init__(self):
self.root = tk.Tk()
self.view = View(self.root, self)
self.root.title("notbook my own try")
self.root.geometry("1200x650")
self.root.config(bg="LightBlue4")
self.root.mainloop()
if __name__ == "__main__":
c = Controller()
The problem is that you are mixing pack and grid with widgets that share a common parent.
First, you're creating a View object as a child of the root window, and you're calling pack to add it to the root window.
Next, you are creating a series of Small_Frame instances, but you are neglecting to pass the parent to the __init__ of the superclass so these instances become a child of the root window. The instance calls pack on itself, and then you call grid on the instance. Calling grid on the instance causes tkinter to get into an infinite loop as both grid and pack try to resize the parent in different ways. Each one triggers a redraw by the other one.
There are two things you need to do. First, remove self.pack(fill=tk.BOTH, expand=1) from the __init__ of Small_Frame. It's a bad practice to have a class call pack or grid on itself. The code that creates a widget should be responsible for adding it to the screen.
Second, you need to pass parent to __init__ method of the superclass in Small_Frame so that Small_Frame is a child of the correct parent. Your __init__ thus should look like this:
class Small_Frame(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, borderwidth=1, relief="groove")
self.parent = parent

Calling a Method of another class with a lambda expression in python

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()

Retrieving variable from another class

I am programming a GUI using Tkinter. In one of the classes I have defined a variable (entry_filename) and would like to use it in another class. A part of the code is as follows:
class Loginpage(tk.Frame,Search):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.controller=controller
self.label_user=tk.Label(self, text="Username")
self.label_user.grid(row=0, column=0)
self.label_pass=tk.Label(self, text="Password")
self.label_pass.grid(row=1, column=0)
self.entry_user=tk.Entry(self)
self.entry_user.focus_set()
self.entry_user.grid(row=0, column=1)
self.entry_pass=tk.Entry(self,show="*")
self.entry_pass.grid(row=1, column=1)
self.button=ttk.Button(self, text="Login",command= self.Logincheck)
self.button.grid(columnspan=2)
def Logincheck(self):
global username
global password
try:
username=self.entry_user.get()
password=self.entry_pass.get()
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.ssh.connect(server, username=username, password=password)#input your username&password
button1 = ttk.Button(self, text="Click to Continue",command= lambda: self.controller.show_frame(Inputpage))
button1.grid(columnspan=2)
except:
tm.showerror("Login error", "Incorrect username/password")
class Inputpage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller=controller
self.filein_label=tk.Label(self,text="Input file name")
self.filein_label.grid(row=0,column=0)
self.entry_filename=tk.Entry(self)
self.entry_filename.focus_set()
self.entry_filename.grid(row=0,column=1)
self.button1 = ttk.Button(self, text="Click to Continue",command= lambda: self.controller.show_frame(Graphpage))
self.button1.grid(columnspan=2)
class Graphpage(tk.Frame,Inputpage):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller=controller
self.label = tk.Label(self, text="Graph Page!", font=LARGE_FONT)
self.label.pack(pady=10,padx=10)
button1 = ttk.Button(self, text="Back to Input Page",command=lambda: self.controller.show_frame(Inputpage))
button1.pack()
filename=Inputpage.entry_filename.get()
The Graphpage calls the variable filename which is later used to create the graph (that part of the code is omitted here). When the code is run the following error is returned:
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Frame, Inputpage
It seems that I have hit another roadblock in attempting to solve the earlier issue, however, if I can understand the resolution to this, I hope that I can attempt to solve further issues. Thanks for your help
ssh is a local variable inside function LoginCheck so you are not able to retrieve it from another class. One thing possible to do is to define ssh as self.ssh so it will be accessible through instance_of_Loginpage.ssh. It will work only when you will pass an instance of Loginpage into an instance of Graphpage. If you need access to an ssh connection from many places I suggest to create another class just to handle ssh (you can use Borg patter to achieve it).
The culprit is that you should not share
class member variables that way.
If different classes share some common
data, that data is probably another class
and they can inherit from it.
class CommonData():
client = 100
class A(CommonData):
def __init__(self):
print(A.client)
class B(CommonData):
def __init__(self):
print(B.client)
a = A()
b = B()
CommonData.client = 300
print(a.client)
print(b.client)
In above case every instance of A and every instance of B
share all the CommonData class variables, like client.
CommonData.client = 400
class C():
pass
You can use multiple inheritance too.
define all common data as CommonData attributes
and use CommonData as a class to hold data, like
in above example, don't create instances from it:
class D(C, CommonData):
def __init__(self):
print(D.client)
c = C()
d = D()
A simpler option would be to just define
a variable CommonData in the outer scope and
use it from anywhere:
common_data = 500
class A():
def __init__(self):
global common_data
print(common_data)
common_data = 200
# ...
But global variables are generally seen as a bad thing in a program as their use can become a problem for several reasons.
Yet another way is to pass the variable to the object initializer.
That makes the instance to keep its own value copied from
the creation value:
common_data = 600
class A():
def __init__(self, data):
self.common = data
print(self.common)
a = A(common_data)
common_data = 0
print(a.common)
If you run all the code above it will print
100
100
300
300
400
600
600
Edit:
See my comment to your answer and a simple example here.
Here I opt for two global references to tkinter StringVars.
The stringvars exist themselves in the Tk() namespace, like the
widgets; besides they are global Python names.
import tkinter as tk
from tkinter import ttk
class Page1(tk.Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.title('Page1')
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=input_file1)
self.label1.pack(side=tk.LEFT)
self.entry1.pack()
class Page2(tk.Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.title('Page2')
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=input_file2)
self.button1 = ttk.Button(self, text='Copy Here', command=copy_filename)
self.label1.pack(side=tk.LEFT)
self.entry1.pack(side=tk.LEFT)
self.button1.pack()
def copy_filename():
input_file2.set(input_file1.get())
root = tk.Tk() # has to exist for the StringVars to be created
root.iconify()
input_file1 = tk.StringVar()
input_file2 = tk.StringVar()
page1 = Page1(root)
page2 = Page2(root)
root.mainloop()
Now in the next example see how I turn the stringvars into variables
of Page1 and Page2 instances (not classes), making them local instead
of global. Then I am forced to pass a reference for the widget page1
object into the widget page2 object.
This looks more close to what you are asking.
About MRO trouble, if you avoid multiple inheritance
it won't happen.
Or you deal with it usually by using super()
In your case the error is because you store the widget in
the object/instance (in self.somename), and then you try
to invoke a widget method qualifying with the class name.
There is no widget there in the class for you to use a method.
So the search using the method resolution order fails,
because there is no corresponding name there.
Note that I have not used multiple inheritance, so I could
have just written tk.Frame. instead of calling super. I like
super because it makes clear in the text that I am invoking the parent
class but super is really needed only when there are multiple parents
and various levels of subclassing (usually forming a diamond shape).
Now the example:
import tkinter as tk
from tkinter import ttk
class Page1(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.input_file1 = tk.StringVar()
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=self.input_file1)
self.label1.pack(side=tk.LEFT)
self.entry1.pack()
class Page2(tk.Frame):
# note the page1 reference being
# passed to initializer and stored in a var
# local to this instance:
def __init__(self, parent, page1, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.page1 = page1
self.input_file2 = tk.StringVar()
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=self.input_file2)
self.button1 = ttk.Button(self, text='Copy Here',
command=self.copy_filename)
self.label1.pack(side=tk.LEFT)
self.entry1.pack(side=tk.LEFT)
self.button1.pack()
def copy_filename(self):
# see how the page1 refernce is used to acess
# the Page1 instance
self.input_file2.set(page1.input_file1.get())
root = tk.Tk() # has to exist for the StringVars to be created
page1 = Page1(root)
page2 = Page2(root, page1) # pass a reference to page1 instance
page1.pack(side=tk.LEFT)
page2.pack(side=tk.LEFT)
root.mainloop()

Resources