Edit
So I asked this question earlier and I received some good insight, but I feel like my question wasn't really answered. I'm building a small program to practice with python and making GUI's and I'm having a small problem with a button command.
#This is the temperature menu:
def temperM(self, *args):
self.clearscreen(self.frame)
self.frame2 = Frame(self.root)
self.frame2.grid(column = 0, row = 0)
self.firstunit = StringVar()
self.secondunit = StringVar()
self.entryspace = IntVar()
self.displayspace = IntVar()
#Create back button
#This is the part that needs to be fixed
self.back = Button(self.frame2, text = "< Back",
command = lambda: self.redo(self.frame2))
self.back.grid(column = 1, row = 3)
self.label = Label(self.frame2, text = "Convert from: ")
self.label.grid(column = 1, row = 1, padx = 4)
#Create the check boxes
self.celsius = Checkbutton(self.frame2, text = "Celsius",
variable = self.firstunit, onvalue = 'celsius')
self.celsius.grid(column = 2, row = 1)
self.fahrenheit = Checkbutton(self.frame2, text = "Fahrenheit",
variable = self.secondunit, onvalue = 'fahrenheit')
self.fahrenheit.grid(column = 3, row = 2)
#Create entry space to recieve text
#This is where the problem starts.
self.entry = Entry(self.frame2, width = 7,
textvariable = self.entryspace)
self.entry.grid(column = 3, row = 3)
self.compute = Calculate(self.entryspace.get())
self.button = Button(self.frame2, text = "Calculate",
command = lambda: self.displayspace.set(self.compute.celtoFah()))
self.button.grid(column = 3, row = 4)
self.display = Label(self.frame2, textvariable = self.displayspace)
self.display.grid(column = 2, row = 2)
I have this function inside of a class Menu with def__init__(self, root) which creates all the different menu options.
class Calculate:
def __init__(self, number):
self.number = number
def celtoFah(self):
try:
self.temp = Temperature()
self.number = float(self.number)
return self.temp.C2F(self.number)
except ValueError:
pass
And I have this class which holds all the different calculations that will be used in the code.
What I'm having trouble with is with my button command command = lambda: self.displayspace.set(self.compute.celtoFah()). When I run the code and press 'Calculate' which runs the command, self.displayspace.set(), it doesn't set self.displayspace to what I believe the returned value should be. Instead it returns and sets self.displayspace to what self.entryspace.get() is originally without modifications which is 32 and 0 respectively, which causes me to believe that the line self.compute = Calulate(self.entryspace.get()) is not updating when I put in a new value so self.entryspace is not getting a new value but its retaining the same initial value established by IntVar(). Am I doing something wrong in my code for self.entryspace not to be updating with a new value? At first I had it as a StringVar() which would convert to a float in celtoFah but I was throwing ValueError because it was receiving an empty string even after a user inputs a value. I really want to keep all calculations in a separate class since I will be having 20+ in the final version, but should I move these commands into class Menu or is there another I can do this by having a separate class? If you need to see my full code here is a link to it on github: https://github.com/Flameancer/Unit-Conversion-Program-in-Python
In general, you don't pass values between classes, but between instances of those classes. At any given time, you may have 0, 1, or 30 different Foo instances; how does a Bar instance even know which one you want?
The first question is, who's calling that something method on that Bar? Whoever it is, he has the value. Maybe it should be the Foo instance that's doing the calling.
For that to happen, the foo instance has to know about a bar instance. Maybe you want to create one in the constructor:
class Foo:
def __init__(self, argument):
# ...
self.bar = Bar(42)
# ...
… and now you can use it the same way as any other member:
def function(self, *args):
# ...
randomness = self.bar.something()
self.displayfield.set(randomness)
# ...
Or maybe you're already constructing one somewhere, and you just want to pass it to the Foo instance as a constructor:
class Foo:
def __init__(self, argument, bar):
# ...
self.bar = bar
# ...
bar = Bar(42)
foo = Foo(23, bar)
Or maybe you want to construct a new one locally each time you call the function method. Or maybe you want a global Bar instance shared by everyone, no matter how many Foo instances you have. Or…
As you can see, there are many different possible relationships between a Foo instance and a Bar instance, and which one is appropriate depends entirely on what Foo and Bar actually represent. This is the code idea behind object modeling: there are things of some kind that your objects represent, and the relationships between those things are reflected in the relationships between those objects.
Related
I am new to OOPs. I wrote following code where delText method clears text field when clicked on a text widget. I called delText method by binding it with a <FocusIn> but I am getting error
AttributeError: 'GuiAndFileMethods' object has no attribute 'delete'
and further I want to read text in some other method. I know that my method is not recognizing the widget on which delete to be done. so how to do it ?
my code
from tkinter import *
class GuiAndFileMethods(Frame):
def delText(obj,event=None):
obj.delete("1.0", END)
z = GuiAndFileMethods()
root = Tk()
fileName = Text(root, height = 1, width = 57, wrap = None )
fileName.insert(INSERT, "Filename")
fileName.grid(row = 1, column = 0,columnspan = 5, padx = (10,50),sticky = W)
fileName.bind("<FocusIn>", lambda x: z.delText(fileName))
replacementNum = Text(root, height = 1, width = 18, wrap = None )
replacementNum.insert(INSERT, "No Of Replacements")
replacementNum.grid(row = 1, column = 6,columnspan = 1,sticky = E)
replacementNum.bind("<FocusIn>", lambda x: z.delText(replacementNum))
root.mainloop()
You have to either define your function as a static function:
class GuiAndFileMethods(Frame):
#staticmethod
def delText(obj,event=None):
obj.delete("1.0", END)
Or pass self as the first argument of the function:
class GuiAndFileMethods(Frame):
def delText(self, obj,event=None):
obj.delete("1.0", END)
In this case, since you are not using any attribute of the class that you are defining, I suggest to go with the first approach.
When you define a method like that, Python will automatically insert the instance of the class as the first argument. This is called self by convention, but it doesn't have to be. So obj there is actually the instance of GuiAndFileMethods class, NOT the object you passed in. The object you pass in will be the second argument of:
def delText(instanceof_GuiAndFileMethods, obj, event=None):
obj.delete("1.0", END)
So, how you define it and how you call it are slightly different. You would call it like this:
instanceof_GuiAndFileMethods.delText(obj, event)
That object you call the method on gets inserted as the first argument (again, usually self).
I used a list as a parameter for the GUI window, and each individual item in the list is supposed to be created as a radio button in the GUI window. Currently, I used a for loop to generate different values for each radio button and another for loop to generate different columns. However, I am only able to create a radio button for the last item in the list.
This is what I currently have:
from tkinter import *
from tkinter.scrolledtext import *
class ExpenditureGUI:
def __init__(self, listOfCategories):
self._listOfCategories = listOfCategories
self._tk = Tk()
self._tk.title('Expenditure Tracker')
self._tk.geometry('500x200')
self._tk.resizable(False,False)
self.initWidgits()
self._tk.mainloop()
#property
def listOfCategories(self):
return self._listOfCategories
#listOfCategories.setter
def listOfCategories(self, newValue):
self._listOfCategories = newValue
def initWidgits(self):
#flow layout
topF = Frame(self._tk)
self._lblAmount = Label(topF, text = 'Amount: ')
self._txtAmount = Entry(topF, width=30)
rbtnF = Frame(topF)
self._rbtnVar = IntVar()
self._rbtnVar.set(0)
self._lblExpenditure = Label(topF, text = 'Type of Expenditure: ')
n = 0
for type in self._listOfCategories:
self._rbtntype = Radiobutton(rbtnF, text = f'{type}', value = n, variable = self._rbtnVar)
self._lblExpenditure.grid(row = 0, column = 0, sticky = E)
n = 0
for type in self._listOfCategories:
self._rbtntype.grid(row = 0, column = n)
I’m not an expert in the GUI library, but this looks pretty suspect:
for type in self._listOfCategories:
self._rbtntype = Radiobutton(rbtnF, text = f'{type}', value = n, variable = self._rbtnVar)
^^^^^^^^^^^^^^^^
After this loop ends, the self._rbtntype variable will contain text referring to only the last element in the list of categories (i.e. the loop variable type).
You may want to construct a new Radiobutton in the final loop in your code sample. Maybe something like this would work. It probably needs to update n in each loop iteration.
for type in self._listOfCategories:
rbtntype = Radiobutton(rbtnF, text = f'{type}', value = n, variable = self._rbtnVar)
rbtntype.grid(row = 0, column = n)
I'm working on a small project and I'm having issues retrieving the values stored in combo boxes. The program has a "plus" button that creates additional boxes beneath the existing ones. They are created by calling a "create" function that makes a new instance of the ComboBox class, where the box is created and put onto the screen. A separate "submit" function is then supposed to loop through and retrieve all of the box values and store them in a list. My main flaw is that I used data in the variable names, but I have no clue how else to do this in this scenario. Does anyone have an alternative solution?
(there are some off screen variables that are show used here as parameters, but there are definitely not the source of the issue)
class ComboBox:
def __init__(self, master, counter, fields):
self.master = master
self.counter = counter
self.fields = fields
self.field_box = ttk.Combobox(width=20)
self.field_box["values"] = fields
self.field_box.grid(row=counter + 1, column=0, pady=5)
def get_value(self):
value = self.field_box.get()
return value
def create():
global entry_counter
name = "loop"+str(entry_counter-1)
name = ComboBox(window, entry_counter, fields)
values.append(name.get_value())
entry_counter += 1
def submit():
for i in range(1, entry_counter):
name = "loop" + str(entry_counter-1)
values.append(name.get_value())
For example, if I created 2 boxes and selected the options "test1" and "test2" I would want the my values list to contain ["test1, "test2"]
Not sure I understand the question right, but I guess you are asking about how to loop throw all instances of ComboBox. You can just create an global array, append new instance into it in create() method:
comboboxes = []
def create():
...
comboboxes.append(new_instance)
def submit():
for combobox in comboboxes:
...
You're on the right track with .get(). I believe your solution is that your get_value function also needs an event parameter:
def get_value(self, event):
value = self.field_box.get()
return value
See the following:
Getting the selected value from combobox in Tkinter
Retrieving and using a tkinter combobox selection
I'm working on an application in tkinter. I have many Entry widgets in UI, and a few classes in app engine. I need to bind tkinter variables of those entries to instances attributes.
i.e.:
class Pipe(Variable):
"""class for pipes"""
def __init__(self):
self.diameter = 0
self.variables = {}
pipe1 = Pipe(self)
pipe2 = Pipe(self)
I want to bind value from one entry to pipe1.diameter, and value from another entry to pipe2.diameter. I'm doing it by a trace function, where is lambda statement, pointing to a function, which identifies entry, and, using a dictionary proper for each instance, pass a value from entry to dictionary value. Dictionaries are produced like here, and then passed as instance attribute:
def pipe1_vars(object_):
variables = {
'ui_variable_name_for_pipe1_diameter': [object_.diameter]
}
return variables
def pipe2_vars(object_):
variables = {
'ui_variable_name_for_pipe2_diameter': [object_.diameter]
}
return variables
pipe1.variables = pipe1_vars(pipe1)
pipe2.variables = pipe2_vars(pipe2)
Unfortunately, Variable class method, assigning value, isn't working properly.
class Variable():
def set_var_value(variable_name, value):
ui_variable = tkinterbuilder.get_variable(variable_name)
self.variables[variable_name][0] = value
if ui_variable.get() != value:
ui_variable.set(value)
Obviously self.variables[variable_name][0] is something different than self.diameter. The dictionary value is changing, but instance.diameter stays the same.
How can I pass a real instance attribute to this method, instead of a copy in a dictionary value?
I'm assuming it is important to my app, to build something working as those dictionaries, because i need to bind similar attributes of different pipes to different entries - so it's have to be defined outside of a Pipe() class. I don't know if I should change dictionary to something else, or maybe should I rebuild those functions, building dictionary. I've run out of ideas, what to ask google.
Code is much complex, I've posted only most important elements, but if any other details are important, please note in comment.
If the number of Pipe attributes is small, make them properties, and when you create a Pipe object, pass it the corresponding tk binded variable:
import tkinter as tk
from tkinter import Tk, ttk
root = Tk()
var_e1 = tk.StringVar()
def print_e1():
print(var_e1.get())
def inc_e1():
var_e1.set(int(var_e1.get())+1)
class Pipe():
def __init__(self, tkvar):
self.tkvar = tkvar
tkvar.set('')
#property
def diameter(self):
return self.tkvar.get()
#diameter.setter
def diameter(self, value):
self.tkvar.set(value)
e1 = tk.Entry(root, textvariable=var_e1)
b1 = tk.Button(root, text='Print e1', command=print_e1)
b2 = tk.Button(root, text='Increment e1', command=inc_e1)
e1.pack(side=tk.LEFT)
b1.pack()
b2.pack()
p1 = Pipe(var_e1)
p1.diameter = 200
root.mainloop()
I am trying to write a simple Python program that will allow a user to input an IP address in decimal, or dotted-decimal format, then convert it to the opposite format and display it in the same entry box (ie, if they enter a decimal IP address, they can click a button and their input will be replaced with the dotted-decimal equivalent).
The problem I'm having is with pulling the data out of the entry box, then putting the new data back into the entry box. I've written an example with just the GUI code, and none of my other conversion logic, to simplify the problem:
import tkinter as tk
root = tk.Tk()
root.title("Test")
win1 = tk.Frame(root)
win1.grid()
x = tk.StringVar()
y = tk.StringVar()
xBox = tk.Entry(win1)
xBox.grid(row = 0, column = 0)
xBox.textvariable = x
yBox = tk.Entry(win1)
yBox.grid(row = 1, column = 0)
yBox.textvariable = y
button = tk.Button(win1,text = "Calculate", command = lambda: copyVal())
button.grid(row = 2, column = 0)
def copyVal():
print("x: " + x.get())
print("y: " + y.get())
xVal = x.get()
print("xval: " + xVal)
y.set(xVal)
root.update_idletasks()
root.mainloop()
Here's what I expect to happen with this code:
The value entered in the top box should be stored in StringVar x.
Clicking the "Calculate" button should run the copyVal() function:
copyVal() gets the value of StringVar x and stores it as xVal.
copyVal() sets the value of StringVar y to match xVal.
The text in the bottom box should now match the text in the top box.
Instead, it does not retrieve the value of StringVar x, so there's nothing to set StringVar y to.
I've tried the following variations:
Using xVal = xBox.get() instead of xVal = x.get(): this retrieves the contents of the top entry box, and sets the value of StringVar y to match it, but the bottom entry box does not change.
Using command = copyVal() instead of command = lambda: copyVal(): the copyVal function executes immediately upon program execution, rather than when the button is pressed.
Moving the copyVal function outside the root mainloop: raises a NameError exception when the button is pressed (copyVal is seen as not defined).
Moving root.update_idletasks() outside the copyVal function has no effect.
I've looked around for solutions to this issue, but no matter how many people I find who are experiencing similar problems, none of their fixes seem to resolve the issue for me (I usually see them told to use StringVar() to get/set values). I am completely new to working with Tkinter, so I'm sure this is something really basic that I'm overlooking, and I appreciate any advice anyone can offer.
Python objects often allow you to add attributes to them arbitrarily:
>>> class Foo:
... pass
...
>>> foo = Foo()
>>> foo.a = 1 # No error. It makes a new attribute.
>>> foo.a
1
>>>
>>> def foo():
... pass
...
>>> foo.a = 1 # Works with function objects too.
>>> foo.a
1
>>>
So, when you do:
xBox.textvariable = x
...
yBox.textvariable = y
you are not actually setting the Entrys' textvariable options to x and y. Instead, you are creating new attributes named textvariable on each of those objects.
To fix the problem, either set each Entry's textvariable option when you create the widgets:
xBox = tk.Entry(win1, textvariable=x)
...
yBox = tk.Entry(win1, textvariable=y)
or use the .config method to change them later:
xBox.config(textvariable=x)
...
yBox.config(textvariable=y)