Adding a standard object as a Frame class attribute with Tkinter - python

I am trying to create a GUI using Tkinter that will allow me to interface with some lab equipment.
I would like to have a combo box reference the handles of various pieces of equipment in my "hardware manager".
Currently, in the top-level frame (that is, one below Root()), I instantiate the hardware manager as a class atribute. However, I am having some trouble accessing that information from nested frames.
Below is the test code (to simplify things):
from Tkinter import *
import ttk
class TestClass():
def __init__(self):
self.test_array = [1, 2, 3]
class TestFrame(Frame):
def __init__(self, frMaster):
Frame.__init__(self, frMaster, bg='gray', padx=5, pady=5)
self.frMaster = frMaster
self.testClass = TestClass()
self.subFrame = SubFrame(self.frMaster)
self.subFrame.grid(row=0, column=0, padx=10, pady=10)
class SubFrame(Frame):
def __init__(self, frMaster):
Frame.__init__(self, frMaster)
self.frMaster = frMaster
self.sHandle = StringVar()
self.cbTest = ttk.Combobox(self.frMaster, width=20, justify='left',
values=[0, 1, 2], textvariable=self.sHandle)
self.cbTest.grid(row=0, column=0, padx=10, pady=10, sticky='nesw')
root = Tk()
test = TestFrame(root)
root.mainloop()
What I would like to happen is to use the test_array attribute from the test_class as the value list for the combo box. So ideally, the line instantiating the combo box would actually look something like this:
self.cbTest = ttk.Combobox(self.frMaster, width=20, justify='left',
values=self.frMaster.testClass.test_array,
textvariable=self.sHandle)
This obviously won't work, though, as self.frMaster in that instance actually refers to the root window, not the test_frame instance. So I get an attribute error.
I also tried instantiating the SubFrame simply with "self" as the master. This technically works, but it fails to display the combo box.
So, is there a better way to access the data in the TestClass instance? Or, alternatively, how can I get the combo box to display if I pass the TestFrame instance as the master to the SubFrame instance?
Thanks!

In the simplest terms possible, if you need the attributes of an object (eg: test_array from TestClass), you need a reference to that object (eg: the instance of TestClass).
There are several ways to do it. The simplest is to pass the reference down to the code that needs it. For example:
class TestFrame(Frame):
def __init__(self, frMaster):
...
self.testClass = TestClass()
self.subFrame = SubFrame(self.frMaster, self.testClass)
...
class SubFrame(Frame):
def __init__(self, frMaster, testclass):
...
self.cbTest = ttk.Combobox(..., values=testclass.test_array, ...)

Related

Override class where mutliple kwargs are used in different functions

I am looking to override the Tkinter Frame, Button, Lable and Entry widgets to have them creted and packed in the same line of code. (IMO 2 lines to do this and get a reference for the obj is ugly, inefficient and harder to read even if it provides more flexability).
My previous code was this:
def cFrame(self, element, bg="white", borderwidth=0, relief="groove", side=None, padx=0, pady=0, height=0, width=0, expand=0, fill=None, image=None, highlightbackground=None, highlightcolor=None, highlightthickness=0, ipadx=0, ipady=0):
f = self.TkUtil.Frame(element, bg=bg, borderwidth=borderwidth, relief=relief, height=height, width=width, image=image, highlightbackground=highlightbackground, highlightcolor=highlightcolor, highlightthickness=highlightthickness)
f.pack(side=side, padx=padx, pady=pady, ipadx=ipadx, ipady=ipady, expand=expand, fill=fill)
return f
My proposed class would look like this:
class cFrame(Frame):
def __init__(self, master, **kwargs):
Frame.__init__(*(self, master), **kwargs)
self.pack(**kwargs)
The issue with this being **kwargs does not care if the keyword is valid and returns 'bad option -"kwarg"'.
I have tried expanding the function a bit more but I keep having issues:
class cButton(Button):
def __init__(self, master, **kwargs):
Button.__init__(*(self, master))
self.conf(**kwargs)
def conf(self, **kwargs ):
__pk_ops = {}
for k, v in kwargs.items():
try:
self.configure({k:v})
except:
__pk_ops[k] = v
self._pk(__pk_ops)
def _pk(self, __ops):
self.pack(__ops)
In this code, I have to loop through all kwargs before and indervidually configure them which is a lot of wasted time over the 1000's of widgets needed. Additionally, I have issues where self.configure(text="sometext") errors so 'text' is passed through to the self.pack() method because for some magic reason it doesn't like that option in particular (same for compound in this example too).
My end goal is to have x = Frame(root, bg="black") \n x.pack(side=TOP) be replaced with x = cFrame(root, bg="black", side=TOP) without default any of the options like my current code making any changes difficult and any sort of theam almost impossible.
You can create a custom class to override the pack() as below:
def cWidget:
def pack(self, **kw):
super().pack(**kw) # call original pack()
return self # return widget itself
# can do the same for grid() and place()
Then create custom widget class as below:
import tkinter as tk
class cButton(cWidget, tk.Button): pass
# do the same for other widgets you want
Now you can use the custom button class as below:
...
root = tk.Tk()
def clicked():
print('Button clicked')
btn['text'] = 'Clicked' # can use 'btn' to change its text as well
btn = cButton(root, text='Click me!', command=clicked, bg='gold').pack(padx=10, pady=10)
print(f'{btn=}') # here you won't get None
print(isinstance(btn, tk.Button)) # here you get True
...
Note that for Python 3.8+, you can simply use the Walrus Operator (:=):
(btn := tk.Button(root, text='Click me!')).pack(padx=5, pady=5)

String Variable not setting initial value

class Lay():
def __init__(self):
root=Tk()
root.configure(background="black")
var=StringVar()
var.set("OVERVIEW")
Label(root,textvariable=var).grid(row=1,column=1,sticky=W+E+N+S)
Entry(root, textvariable = var).place(rely=1.0,relx=1.0,x=0,y=0,anchor=SE)
root.mainloop()
Hello, when i run this the initial value of the string variable does not appear, but when i type into the entry box, the text i type appears in the label. I'm not quite sure why this occurs, but i get an empty label to begin with, with the entry box. Thank you for any help.
Although, I couldn't reproduce the problem, I refactored your code to initialize tkinter widgets through a class(inspired by the snippet in the docs) and also increased the window size so that the widgets are clearly viewed. If there is anything else in your code that is calling multiple windows as #jasonharper suggested, you should share that.
import tkinter as tk
class Lay(tk.Tk):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.var=tk.StringVar()
self.var.set("OVERVIEW")
self.Widgets()
def Widgets(self):
self.displaylbl = tk.Label(self,textvariable=self.var)
self.displaylbl.grid(row=2,column=1,sticky=tk.W+tk.E+tk.N+tk.S)
self.entry = tk.Entry(self, textvariable = self.var)
self.entry.place(rely=1.0,relx=1.0,x=0,y=0,anchor=tk.SE)
app = Lay()
app.geometry("200x200")
app.mainloop()
Output:

Can't read entry value from one class's instance to another in Tkinter while reading from the same class's init works fine

I have two classes in my project. One is the GUI and the other makes the calculations. I read data from two Entry boxes from the first and want to pass them to the second. So far, what I do is this:
class OptimizationGUI:
land_entry = 0
pop_entry = 0
def __init__(self, master):
self.land_entry_text = IntVar()
self.pop_entry_text = IntVar()
self.master = master
OptimizationGUI.land_entry = Entry(master, textvariable=self.land_entry_text)
self.advanced_options = Button(master, text="Advanced", command=self.advanced)
self.run = Button(master, text="RUN", command=self.start, bg='black', fg='red', bd=4)
def advanced(self):
advanced_window = Toplevel()
OptimizationGUI.pop_entry = Entry(advanced_window, textvariable=self.pop_entry_text)
def start(self):
opt = Optimization()
method = getattr(opt,'get_result')
yo = method()
In the above code, I initiate the two as class variables and then I create the Entries. I go from class OptimizationGui to the other with the getattr. The code of the other class is below:
class Optimization():
def __init__(self):
self.population = OptimizationGUI.pop_entry.get()
self.land = OptimizationGUI.land_entry.get()
print self.population, self.land
The weird thing is, that while it prints the data of land_entry correctly, when it comes to the self.population = OptimizationGUI.pop_entry.get() line, it prints the following error:
AttributeError: 'int' object has no attribute 'get'
The only difference I see between these two is that the pop_entry variable is not in the init function but in the "advanced". What is the way to overcome this?
Call the advanced method inside init:
def __init__(self, master):
...
self.advanced()
The difference is that the class variable integer 0 land_entry gets overwritten by an Entry object whereas pop_entry isn't overwritten, as advanced is the part that overwrites it and it is never called.

tkinter, "refresh" the mainwindow

Though I think that the solution might be similar to this one: tkinter, display a value from subwindow in mainwindow , I still decided to ask this question since I have trouble to figure it out on my own.
I have the list "fields" with which I am creating any given number of rows, with two labes inside of them. After opening a subwindow, I want to be able to manipulate that list (in my example simply just append at the moment) and after clicking on a button (here "ADD"), I want the mainwindow to update, so that it shows the rows of the manipulated list. It works fine for the most part but I dont what is the best way to update the mainwindow in this example.
The only solution I was able to come up with, is to destroy the mainwindow and recreate it but I have the feeling that this might not be the best solution. Is there a better one?
import tkinter as tk
a=0
fields=[("a",1),("c",2),("e",3)]
class clsApp(object):
def __init__(self):
self.root=tk.Tk()
self.root.title("MainWindow")
##Labels##
self.rootLabel=tk.Label(self.root, text="WindowAppExperiment", padx=100)
self.aLabel=tk.Label(self.root, text=a, padx=100)
##Buttons##
self.BtExit=tk.Button(self.root, text="Quit", fg="red", command=self.root.quit)
###self.BtNewWindow=tk.Button(self.root, text ="Edit", command=lambda:self.clsNewWindow(self.root, self.aLabel).run())
self.BtNewField=tk.Button(self.root, text ="New Field", padx=30, command=lambda:self.clsNewFields(self.root).run())
def grid (self):
self.rootLabel.pack()
self.aLabel.pack()
self.fckPackFields()
self.BtNewField.pack()
self.BtExit.pack(side="left")
###self.BtNewWindow.pack(side="right")
def fckPackFields(self):
if fields:
for field in fields:
##create labels##
row=tk.Frame(self.root)
nameLabel=tk.Label(row, text =field[0], width=20, anchor="w")
valueLabel=tk.Label(row, text =field[1], width=5)
##pack labels##
row.pack(side="top", fill="x", padx=5, pady=5)
nameLabel.pack(side="left")
valueLabel.pack(side="right", expand=True, fill="x")
def run(self):
self.grid()
self.root.mainloop()
self.root.destroy()
class clsNewFields(object):
def __init__(self, Parent):
self.parent=Parent
##Window##
self.top=tk.Toplevel()
self.top.title("Add Fields")
##Labels##
self.enterNameLabel=tk.Label(self.top, text ="Enter fieldname", padx=10)
self.enterValueLabel=tk.Label(self.top, text ="Enter value", padx=10)
##Entryfields##
self.EntryName=tk.Entry(self.top)
self.EntryValue=tk.Entry(self.top)
##Buttons##
self.BtADD=tk.Button(self.top, text ="ADD", command=lambda:self.fckAddField(self.EntryName, self.EntryValue))
self.BtClose=tk.Button(self.top, text ="Close", command=self.top. quit)
def grid(self):
self.enterNameLabel.pack()
self.enterValueLabel.pack()
self.EntryName.pack()
self.EntryValue.pack()
self.BtADD.pack()
self.BtClose.pack()
def fckAddField(self, Name, Value):
self.name=Name.get()
self.value=Value.get()
global fields
fields.append((self.name, self.value))
print(fields)
self.parent.update
def run(self):
self.grid()
self.top.mainloop()
self.top.destroy()
clsApp().run()
Welcome to StackOverflow.
First of all - do you really want to declare the clsNewFields inside your clsApp ? Yes, the Fields should be used inside App, but i do not see a need for using class-in-class-declaration.
Second - you are packing the Fields in def fckPackFields(self):. This is not automatically called when you update it.
You are not calling update function by using self.parent.update.
You are using global variable for fields, what does not really suit your needs. Why not having a list inside your App-class like:
def __init__(self):
self.__fields=[]
def __set_fields(self, value):
self.__fields=value
def __get_fields(self):
return self.__fields
Fields = property(__get_fields, __set_fields)
def __loadUI(self, event=None):
# This function should be called at the end of __init__
self.fieldFrame=tk.Frame(self.root)
self.fieldFrame.pack(side="top")
def fckPackFields(self):
#First clean area
[...]
#Then add fields
for field in self.__fields:
# create the row, etc.
# !!! but do it inside self.fieldFrame !!!
[...]
I would prefer using grid instead of pack over here, because there I think it is easier to place a frame at a certain position, then you could just destroy self.fieldFrame and recreate it at the same position for placing the fields in it.
UPDATE:
Just checked your code again. With some simple tricks your can tweak your GUI to do what you want:
def __init__(self):
self.fieldFrame=None #this line has been added
#completely reworked this function
def grid(self):
self.rootLabel.grid(row=1, column=0, columnspan=2, sticky=tk.NW+tk.SE)
self.fckPackFields()
self.BtNewField.grid(row=3, column=0, sticky=tk.NW+tk.SE)
self.BtExit.grid(row=3, column=1, sticky=tk.NW+tk.SE)
#Only one line changed / one added
def fckPackFields(self):
self.__cleanFields() #added this line, function beyond
if fields:
for field in fields:
##create labels##
row=tk.Frame(self.fieldFrame) #add the row to the fieldFrame
[...]
#In here we destroy and recreate fieldFrame as needed
def __cleanFields(self):
if self.fieldFrame:
self.fieldFrame.destroy()
##FieldFrame##
self.fieldFrame=tk.Frame(self.root)
self.fieldFrame.grid(row=2, column=0, columnspan=2)
In clsNewFields:
def fckAddField(self, Name, Value):
[...]
self.parent.fckPackFields() # instead of self.parent.update
EDIT:
Have a look at these two questions:
Class inside a Class
Benefit of nested Classes
I did not mean to point out that nested classes are to be avoided in general but I do want to focus you into the thought of "is there a real necessity or benefit of it for my use-case".

How to automatically insert several tkinter items in Tk() window using classes

I apologize in advance if this is a stupid simple question, but i am really bad att python classes and can't seem to get it to work!
Here is my code:
from tkinter import *
a = Tk()
class toolsGUI():
def __init__(self, rootWin):
pass
def frame(self):
frame = Frame(rootWin)
frame.configure(bg = 'red')
frame.grid()
def button(self, binding, text):
btn = Button(rootWin, text=text)
btn.configure(bg = 'orange', fg = 'black')
btn.bind('<'+binding+'>')
btn.grid(row=1, sticky = N+S+E)
I simply want the button() or frame() to understand that rootWin is the same as in __init__, in this case rootWin should be variable a, thus placing the button in the Tk() window. After looking around, I understand that this is not the way to do it. Do anyone have another suggestion that might work?
You're pretty close. You are passing a to the toolsGUI initializer which is the right first step. You simply need to save this as an instance variable, then use the variable whenever you need to reference the root window:
def __init__(self, rootWin):
...
self.rootWin = rootWin
...
def frame(self):
frame = Frame(self.rootWin)
...
An alternative is to have toolsGUI inherit from Frame, in which case you can put all of the widgets in the frame instead of the root window. You then need the extra step of putting this frame inside the root window.
class toolsGUI(Frame):
def __init__(self, rootWin):
Frame.__init__(self, rootWin)
def frame(self):
frame = Frame(self)
...
a = Tk()
t = toolsGUI(a)
t.pack(fill="both", expand=True)
a.mainloop()
As a final bit of advice: don't user variables that are the same name as methods if you can avoid it. "frame" is a poor choice of function names. Instead, call it "create_frame" or something, otherwise it could be confused with class Frame and the local variable frame

Categories

Resources