Interclass variable reference - python-3.x

This is an example of some code I'm using to generate a form of populated values that will update itself when a field is changed. I've been stuck for a few days reading and experimenting with calling a variable from one class inside a method of another class. The reason that I'm attempting it this way, is to make the code more modular and readable since I'll end up having roughly 30 sections. Also, as a rule, I don't usually use numbers when naming objects.
from tkinter import *
from tkinter import ttk
class Application(ttk.Frame):
def __init__(self, master=None):
ttk.Frame.__init__(self, master)
self.grid()
Frame1()
Frame2()
Frame3()
class Frame1(ttk.Frame):
def __init__(self, master=None):
ttk.Frame.__init__(self, master)
self.grid()
ttk.Label(self, text="Frame1").grid()
self.var1 = IntVar()
self.SetVar1()
ttk.Label(self, textvariable=self.var1).grid()
def SetVar1(self):
self.var1.set(2)
class Frame2(ttk.Frame):
def __init__(self, master=None):
ttk.Frame.__init__(self, master)
self.grid()
ttk.Label(self, text="Frame2").grid()
self.var2 = IntVar()
self.SetVar2()
ttk.Label(self, textvariable=self.var2).grid()
def SetVar2(self):
self.var2.set(3)
class Frame3(ttk.Frame):
def __init__(self, master=None):
ttk.Frame.__init__(self, master)
self.grid()
ttk.Label(self, text="Frame3").grid()
self.var3 = IntVar()
self.SetVar3()
ttk.Label(self, textvariable=self.var3).grid()
def SetVar3(self):
self.var3.set(Frame1.var1.get() + Frame2.var2.get())
app = Application()
app.master.title("Window")
app.mainloop()
Toward the bottom of the code the error occurs for
def SetVar3(self):
self.var3.set(Frame1.var1.get() + Frame2.var2.get())
and I cannot get past it. Apparently I cannot call var1 in this manner. It results in the following error.
AttributeError: type object 'Frame1' has no attribute 'var1'
When executed properly, the code should resemble:
Frame1
2
Frame2
3
Frame3
5
So, what am I missing?
(A moment ago there were comments below, but they've since disappeared which really helps discuss this topic. I'll pick this back up after some sleep. Maybe by then the comments below will have magically reappeared and I can respond to them in turn.
One comment mentioned creating new instances of Frame1 and Frame2 to call in Frame3, but it wouldn't help unless these new instances shared the values from the already initialized instances in Application. The other comment mentioned that I had errors in my code, but didn't elaborate.
While I responding to each comment, each disappeared and I was given a warning to edit my original post instead. Did I miss something about the point of comments?)

Frame1 and Frame2 are both classes, not instances. That's why you're getting an error in that line of code (var1 and var2 are instance variables, not class variables).
To make this work you'll need to pass a reference to your Frame1 and Frame2 instances (or perhaps, to those instances var1 and var2 variable) to Frame3.
Try changing Application to:
class Application(ttk.Frame):
def __init__(self, master=None):
ttk.Frame.__init__(self, master)
self.grid()
f1 = Frame1() # save frames to local variables, so we can reference them
f2 = Frame2()
f3 = Frame3(f1, f2) # pass previous frames as args!
Then make Frame3 use the parameters (here I just pass them on to SetVar3 directly from __init__, but you could save them as attributes on self instead, if you wanted):
class Frame3(ttk.Frame):
def __init__(self, f1, f2, master=None): # take other frames as parameters
ttk.Frame.__init__(self, master)
self.grid()
ttk.Label(self, text="Frame3").grid()
self.var3 = IntVar()
self.SetVar3(f1, f2) # pass frames on
ttk.Label(self, textvariable=self.var3).grid()
def SetVar3(self, f1, f2): # take frames as parameters here too
self.var3.set(f1.var1.get() + f2.var2.get()) # use the references
Obviously in real code the logic might need to be different. Think about what the logical relationships are between the frames, and let them pass (and perhaps save) references to each other as appropriate.

Related

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

inherit parent window and add an additional button in tkinter?

i'm trying to create two separate windows, one of which should inherit the others interface, and grid some additional buttons. How can I achieve this?
Below is an example piece of code:
f = ("Helvetica", 18)
bg = 'white'
g = '1400x800'
class MainUser(Frame):
def __init__(self, master):
Frame.__init__(self, master)
Frame.configure(self, background='white')
self.logo = PhotoImage(file="logo.gif")
Label(self, image=self.logo).pack()
Button(self, text='test', bg=bg, font=f).pack()
class MainAdmin(MainUser):
pass # What now?
You simply need to create a proper __init__ that calls the same function in the superclass. Then, add widgets like you would have done in the superclass.
Example:
class MainAdmin(MainUser):
def __init__(self, master):
super().__init__(master)
another_label = Label(self, text="Hello from MainAdmin")
another_label.pack(side="top", fill="x")

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

Setting label value from OptionMenu in tkinter

I would like to set a label value to the value of the current optionmenu value. If the latter changes I want the former to change too. My issue is that this gui elements are defined in separate classes (and I want them to be like that), but I do not know how to connect them together. Without classes I know I can use the OptionMenu's command method to set the value of the Label. But putting them into Frame containers I am stuck.
Here is a simplistic and functioning code what I want to resolve:
from tkinter import *
root = Tk()
opt=['Jan', 'Feb', 'March']
class MyOptMenu(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
self.var = StringVar(self)
self.var.set(opt[0])
self.om = OptionMenu(self, self.var, *opt)
self.om.pack(side=TOP)
self.var.trace('w', self.getValue)
def getValue(self, *args):
return(self.var.get())
class MyLabel(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack()
self.labstring = StringVar(self)
self.lab = Label(self, textvariable = self.labstring, bg='white')
self.lab.pack(side=TOP)
self.labstring.set('hello')
a = MyOptMenu(root)
b = MyLabel(root)
root.mainloop()
Could you give me some help how to proceed. Many thanks.
According to #j_4321's suggestion, here I post the solution that resolved my issue. I provide explanation in comments in between code lines.
from tkinter import *
root = Tk()
opt=['Jan', 'Feb', 'March']
var = StringVar(root) # initialization of a common StringVar for both OptionMenu and Label widgets
class MyOptMenu(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
var.set(opt[0]) # give an initial value to the StringVar that will be displayed first on the OptionMenu
self.om = OptionMenu(self, var, *opt)
self.om.pack(side=TOP)
var.trace('w', self.getValue) # continuously trace the value of the selected items in the OptionMenu and update the var variable, using the function self.getValue
def getValue(self, *args):
return(var.get()) # return the current value of OptionMenu
class MyLabel(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack()
self.lab = Label(self, textvariable = var, bg='white') # use the same StringVar variable (var) as the OptionMenu. This allows changing the Label text instantaneously to the selected value of OptionMenu
self.lab.pack(side=TOP)
a = MyOptMenu(root)
b = MyLabel(root)
root.mainloop()

Python3 class function definition confusion about NameError

I'm new to programming and this is my first post on the site. I'm sure I'm making a dumb mistake, but I'd really appreciate a push in the right direction. I'm trying to make a calculator, and want to make a function that produces a Button object for numbers. When I try to run this I get the error:
'NameError: name 'num_but_gen' is not defined'
Here is the code:
from tkinter import *
WINDOW_HEIGHT = 300
WINDOW_WIDTH = 325
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def num_but_gen(self, disp, xloc=0, yloc=0, wid=0, hei=0):
self.Button(text='{}'.format(disp),height=hei, width=wid)
self.place(x=xloc, y=yloc)
def init_window(self):
self.master.title('Calculator')
self.pack(fill=BOTH, expand=1)
Button1 = num_but_gen('1', xloc=0, yloc=200, wid=40, hei=40)
root = Tk()
app = Window(root)
root.geometry("{}x{}".format(WINDOW_WIDTH,WINDOW_HEIGHT))
root.mainloop()
Any help would be greatly appreciated! Also bonus points to anyone with suggestions on how to better phrase my question titles in future posts.
jasonharper is right, you need to add self in front of num_but_gen, but there are other problems in your code.
In num_but_gen:
your window class does not have a Button attribute, so you need to remove self. in front of Button
it is not the Window instance but the button that you want to place
you don't need to use text='{}'.format(disp), text=disp does the same.
In init_window:
you store the result of num_but_gen in a variable, but this function returns nothing so that's useless (and capitalized names should not be used for variables, but for class names only)
the width option of a button displaying text is in letters, not in pixels and its height option is in text lines, so wid=40, hei=40 will create a very big button. If you want to set the button size in pixels, you can do it through the place method instead.
Here is the corresponding code:
import tkinter as tk
WINDOW_HEIGHT = 300
WINDOW_WIDTH = 325
class Window(tk.Frame):
def __init__(self, master = None):
tk.Frame.__init__(self, master)
self.master = master
self.init_window()
def num_but_gen(self, disp, xloc=0, yloc=0, wid=0, hei=0):
button = tk.Button(self, text=disp)
button.place(x=xloc, y=yloc, height=hei, width=wid)
def init_window(self):
self.master.title('Calculator')
self.pack(fill=tk.BOTH, expand=1)
self.num_but_gen('1', xloc=0, yloc=200, wid=40, hei=40)
root = tk.Tk()
app = Window(root)
root.geometry("{}x{}".format(WINDOW_WIDTH,WINDOW_HEIGHT))
root.mainloop()

Resources