How to alter attributes in Tkinter - python

Is there any way to alter options of a widget after creating/drawing it? I can't seem to find any way to do so. What I'm currently aiming for is altering the fg of a Label once its temp0 textvariable is >= 50.
This code is part of a bigger program, so I didn't want to put all of that here, since the essential part is that I am not sure how to change the fg (i.e. font color) for that Label once I get the b[0] value and find out that it is above 50. Is the self.t0.config(fg="red") the proper syntax for that?
class App:
def __init__(self, master):
#live updating TkInter variables
self.temp0 = DoubleVar()
frame = Frame(master)
self.t0 = Label(frame, fg="blue", textvariable=self.temp0,font=(20)).grid(row=2, column=0)
frame.pack(padx=10, pady=10)
def start(self):
# calculates temperature
self.temp0.set(b[0])
# changes color of text to red if temp >= 50
if b[0] >= 50:
self.t0.config(fg="red")

Yes that works. You can use either:
self.t0.config(fg="red")
or:
self.t0["fg"] = "red"
Both methods do the same thing, so you can choose what you want.
Also, to get everything working, you will want to make this line of code:
self.t0 = Label(frame, fg="blue", textvariable=self.temp0,font=(20)).grid(row=2, column=0)
into two lines:
self.t0 = Label(frame, fg="blue", textvariable=self.temp0,font=(20))
self.t0.grid(row=2, column=0)
Now, self.t0 will point to the label like it should and not the return value of .grid, which is None.

Related

Python/Tkinter - clear the graphical interface screen

i am developing an application to calculate some taxes and show the result in the graphical interface. The code itself works perfectly, but if i use numbers with bigger squares, the result overlaps over the previous one. My question is, is it possible to clear the previous result and calculate the new one?
Follow the complete code below:
from tkinter import *
root = Tk()
l_vlrRec = Label(root, text='Receita')
l_vlrRec.place(x=10, y=10)
e_vlrRec = Entry(root)
e_vlrRec.place(x=75, y=10, width=75)
def calcular():
receita = float(e_vlrRec.get())
l_result = Label(root, text='{:.2f}'.format(receita))
l_result.place(x=10, y=150)
e_vlrRec.delete(0, END)
bt = Button(root, text='Calcular', command=calcular)
bt.place(x=10, y=50)
root.mainloop()
You can use the label's textvariable and also you don't have to instantiate a new Label every time the button is pressed:
v_result = DoubleVar()
l_result = Label(root, textvariable=v_result)
l_result.place(x=10, y=150)
def calcular():
v_result.set(round(float(e_vlrRec.get()),2))
You can do the same for your Entry object e_vlrRec so you don't have to cast the string you get by calling e_vlrRec.get() but use the variable's get() instead
Without using textvariable you can also reconfigure the label's text parameter:
l_result.configure(text='{:.2f}'.format(receita))
or
l_result['text'] = '{:.2f}'.format(receita)

Tkinter - Retrieve Values from Dynamically Generated Widgets - Callback

I'm trying to make a GUI through Tkinter that will calculate production based on some user input. Based on the number of systems the user selects, I have that number of option menus pop up for the inverter type and that number of entry widgets pop up for modules per string, strings per inverter, and inverters per system. See the picture for an example if the user selects 2 systems.
I'm using a callback function to grab the user selected number of systems real time to dynamically generate the inverter/module widgets discussed above.
My issue is that I'm unable to retrieve the values from these widgets. My attempt is shown in the weather calculation function.
I'm assuming the issue is because I generate the widgets/variables within the callback function. However, I haven't been able to figure out a way to dynamically generate the number of widgets based on user input outside of the callback function.
Any assistance with this would be greatly appreciated!
class Window:
# Define User Inputs:
def __init__(self, master):
master.title('Production Analysis Tool')
# EQUIPMENT PARAMETERS
# callback function to create entry boxes based on number of systems
def callback(*args):
self.system_size = int(self.system_size_raw.get())
# Modules per String
self.L3 = Label(root, text = "Number of Modules Per String").grid(row=20, column=1, sticky=E)
self.modules_string_raw = IntVar(root)
modules_per_string =[]
for i in range(self.system_size):
self.label = Label(root, text = "System {}".format(i+1)).grid(row=21+i, column=1, sticky=E)
self.widget = Entry(root).grid(row=21+i, column=2, sticky=W)
modules_per_string.append(self.widget)
# Number of Systems
self.L1 = Label(root, text = "Number of Systems").grid(row=1, column=1, sticky=E)
self.system_size_raw = IntVar(root)
choices = [1,2,3,4,5,6,7,8,9,10]
self.popupMenu2 = OptionMenu(root, self.system_size_raw, *choices).grid(row=1, column=2, sticky=W)
self.system_size_raw.trace("w", callback)
#Calculation Function
def weather_calculation(self):
# Get Values from User Input
self.mod_strings = np.float(self.modules_string_raw.get())
root = Tk()
root.configure()
window = Window(root)
root.mainloop()
All you need to do is save a reference to your Entry widgets in a list. You can then iterate over that list to get the value of each widget.
It appears that you're already saving the widgets to the list variable modules_per_string. All you need to do is make that global or an object attribute rather than a local variable so other functions can reference it.
As Bryan Oakley said, make list for widgets to store each objects of entries and label in two list.
For Example:
import tkinter as tk
class Demo:
def __init__(self):
self.root = tk.Tk()
self.root.geometry("600x600")
systems_label = tk.Label(self.root, text="No Of Systems:")
systems_label.place(x=100, y=20)
no_Of_System_Ent = tk.Entry(self.root, width=15)
no_Of_System_Ent.place(x=200, y=20)
submit_Button = tk.Button(self.root, text="Submit", command=lambda: self.process(no_Of_System_Ent.get()))
submit_Button.place(x=350,y=20)
def display(self,sys_len):
for i in range(sys_len):
buffer = self.obj_of_entries[i].get()
print(buffer)
def delete(self,sys_len):
for i in range(sys_len):
self.obj_of_entries[i].destroy()
self.obj_of_labels[i].destroy()
def process(self,length_sys):
self.obj_of_entries = []
self.obj_of_labels = []
y_pos = 80
for i in range(int(length_sys)):
#Adding objects of label in list 'obj_of_labels'
self.obj_of_labels.append(tk.Label(self.root,text="System "+str(i)))
self.obj_of_labels[len(self.obj_of_labels)-1].place(x=100,y=y_pos)
#Adding objects of entry in list 'obj_of_entries'
self.obj_of_entries.append(tk.Entry(self.root,width=15))
self.obj_of_entries[len(self.obj_of_entries)-1].place(x=200,y=y_pos)
#Increments Y by 50
y_pos = y_pos + 50
self.delete_Button = tk.Button(self.root, text="Delete All", command=lambda: self.delete(int(length_sys)))
self.delete_Button.place(x=200,y=400)
self.print_Button = tk.Button(self.root, text="Print All", command=lambda: self.display(int(length_sys)))
self.print_Button.place(x=350,y=400)
ob=Demo()
In this example:
I created a entry and button in the init function to take no of systems from user.
def __init__(self):
self.root = tk.Tk()
self.root.geometry("600x600")
systems_label = tk.Label(self.root, text="No Of Systems:")
systems_label.place(x=100, y=20)
no_Of_System_Ent = tk.Entry(self.root, width=15)
no_Of_System_Ent.place(x=200, y=20)
submit_Button = tk.Button(self.root, text="Submit", command=lambda: self.process(no_Of_System_Ent.get()))
submit_Button.place(x=350,y=20)
After clicking submit button,it will go to process function.
Ps: length_sys is the no of systems.
def process(self,length_sys):
self.obj_of_entries = []
self.obj_of_labels = []
y_pos = 80
for i in range(int(length_sys)):
#Adding objects of label in list 'obj_of_labels'
self.obj_of_labels.append(tk.Label(self.root,text="System "+str(i)))
self.obj_of_labels[len(self.obj_of_labels)-1].place(x=100,y=y_pos)
#Adding objects of entry in list 'obj_of_entries'
self.obj_of_entries.append(tk.Entry(self.root,width=15))
self.obj_of_entries[len(self.obj_of_entries)-1].place(x=200,y=y_pos)
#Increments Y by 50
y_pos = y_pos + 50
self.delete_Button = tk.Button(self.root, text="Delete All", command=lambda: self.delete(int(length_sys)))
self.delete_Button.place(x=200,y=400)
It will append the entry and label obj in its respective list and place the current obj in GUI window.
At Last,It will increment y axis by 80 so that next label and entry comes down to the previous one.
If user clicks delete all button,then it will go to delete all list obj of both entries and labels.
Ps: sys_len is the no of systems.
def delete(self,sys_len):
for i in range(sys_len):
self.obj_of_entries[i].destroy()
self.obj_of_labels[i].destroy()
To see the content,use this code:
(It will print in the Python shell so you can see if data is correct or not.)
def display(self,sys_len):
for i in range(sys_len):
buffer = self.obj_of_entries[i].get()
print(buffer)
I think I solved the doubt.
Ciao!

Weird tk Entry box behavior

I am building a simple GUI with multiple entry boxes, check boxes, etc. and came across a very peculiar behavior in the Tkinter.Entry class and I'm wondering if anyone else has run into it and/or if I'm just doing something silly.
I created a simple class to wrap each Tkinter.Entry object and interface with. I wanted to implement a way to change how wide each box is, so I added a parameter width to the class. When I did this, all of my boxes became "linked" and when I type into one box, I typed into every box. before I implemented this functionality it worked fine and when I take it out it works again. Here is my class:
import Tkinter as tk
class EntryBox:
def __init__(self, master, row, column, default_val="", width=20):
self.val = tk.StringVar()
self.default_val = default_val
self.width = width
# with the width parameter specified
self.e = tk.Entry(master, text="cb_text", textvariable=self.val, width=self.width)
# without the width parameter specified (defaults to a width of 20)
# self.e = tk.Entry(master, text="cb_text", textvariable=self.val)
self.e.grid(row=row, column=column, sticky=tk.E)
self.e.insert(0, self.default_val)
def get_val(self):
return self.val.get()
def activate(self):
self.e.config(state=tk.NORMAL)
def deactivate(self):
self.e.config(state=tk.DISABLED)
def focus(self):
self.e.focus()
Here is my program WITHOUT the width parameter included:
Here is my program WITH the width parameter included:
As you can see, all of the default values filled in to every box and whenever I edit one, I edit all of them.
Here is how I instantiate each object (I don't suspect this to be the issue, but just to be thorough):
import Tkinter as tk
ip_addr_box = EntryBox(root, 1, 1, default_val="192.168.201.116")
ip_addr_label = tk.Label(root, text="IP Address").grid(row=1, column=2, sticky=tk.W)
# set up the IP port entry box
ip_port_box = EntryBox(root, 2, 1, default_val="8000")
ip_port_label = tk.Label(root, text="IP port").grid(row=2, column=2, sticky=tk.W)
# set up the number of plot points scroll box
# set up the filename entry box
filename_box = EntryBox(root, 4, 1, default_val="log.xlsx")
filename_label = tk.Label(root, text="File Name").grid(row=4, column=2, sticky=tk.W)
# set up how long the test lasts
meas_time_box = EntryBox(root, 3, 4, default_val="5", width=10)
meas_time_label = tk.Label(root, text="Measurement Period")
meas_time_label.grid(row=3, column=5, columnspan=2, sticky=tk.W)
test_time_box = EntryBox(root, 4, 4, default_val="30", width=10)
test_time_label = tk.Label(root, text="Test Duration").grid(row=4, column=5, columnspan=2, sticky=tk.W)
My guess is that this is a weird bug in Tkinter or something to do with namespaces that I don't know enough about.
EDIT: I updated the class code to specify how I don't include the width parameter. I just don't even include it as a named argument when calling tk.Entry.
Remove the attribute text='cb_text'. I don't know what you think that's doing, but text is just an abbreviation of textvariable, so using it is the same as doing Entry(..., textvariable='cb_text', textvariable=self.var, ...).
Apparently there's a bug in how the tkinter Entry widget processes named arguments, and it is triggered when you add the width argument. Every entry widget ends up with the textvariable attribute set to "cb_text", meaning they all share the same storage for the value.
Inheriting from the Tkinter Entry class works well. I have slightly manipulated your code.
import Tkinter as tk
class EntryBox(tk.Entry):
def __init__(self, master, row, column, default_val="", width=20):
tk.Entry.__init__(self, master, text="cb_text")
self.val = tk.StringVar()
self.default_val = default_val
self.width = width
# with the width parameter specified
# self.e = tk.Entry(master, text="cb_text", textvariable=self.val, width=self.width)
# without the width parameter specified (defaults to a width of 20)
# self.e = tk.Entry(master, text="cb_text", textvariable=self.val)
# self.e.grid(row=row, column=column, sticky=tk.E)
self.config(textvariable=self.val, width=self.width)
self.insert(0, self.default_val)
self.grid(row=row, column=column, sticky=tk.E)
def get_val(self):
return self.val.get()
def activate(self):
self.e.config(state=tk.NORMAL)
def deactivate(self):
self.e.config(state=tk.DISABLED)
def focus(self):
self.e.focus()
Unfortunately I don't have enough rep points to comment directly.

How to make a background image on frame in python

I have a problem i want to put a image as the background for this little converter can someone help me? I was looking online and on stackoverflow but none of the things i found would work with the class.
__author__ = 'apcs'
from tkinter import *
class FarenheitToCelsius(Frame):
def __init__(self):
Frame.__init__(self)
self.master.title("Farenheit To Celsius Conversion")
self.grid()
self.farenheitLabel = Label(self, text="Farenheit")
self.farenheitLabel.grid(row=0, column=0)
self.farVar = DoubleVar()
self.farEntry = Entry(self, textvariable=self.farVar)
self.farEntry.grid(row=0, column=1)
self.celsiusLabel = Label(self, text="Celsius")
self.celsiusLabel.grid(row=1, column=0)
self.celVar = DoubleVar()
self.celEntry = Entry(self, textvariable=self.celVar)
self.celEntry.grid(row=1, column=1)
self.button = Button(self,
text="Convert to Celsius",
command=self.convertToFarenheit)
self.button2 = Button(self,
text="Convert to Farenheit",
command=self.convertToCelsius)
self.button.grid(row=2, column=1, columnspan=1)
self.button2.grid(row=2, column=0, columnspan=1)
def convertToFarenheit(self):
fare = self.farVar.get()
cels = (fare - 32) * 5 / 9
self.celVar.set(cels)
def convertToCelsius(self):
cel = self.celVar.get()
far = cel * 9 / 5 + 32
self.farVar.set(far)
def main():
FarenheitToCelsius().mainloop()
main()
I can think of at least three ways to do this:
Create an image in a label, and use place to put it in the frame. Then create all of the other widgets and use pack or grid as you normally would. Make sure you create the frame first, then the label with the image, then the children of the frame, so that the stacking order is correct.
Use a canvas instead of a frame, and use the create_image method to add an image. Then you can pack/place/grid children as normal.
Instead of a frame, use a label as the container, and then pack/place/grid widgets into the label (yes, you can add children to a label widget).

Label and value display

I'm using a simple code that displays the square root of a number in a label, but for some reason the values get overlaped in a way that I couldn't avoid it, which means that, if I use it for a number that has a exact square root, then the answer goes messed up with the previous answer of many digits.
I've been use the next code so far:
from Tkinter import *
def square_calc():
x = x_val.get()
sqx = x ** 0.5
print x, "** 0.5 =", sqx
sqx_txt = Label(root, text = "x ** 0.5 =").grid(row=3, column=0)
sqx_lab = Label(root, text = sqx).grid(row=3, column=1)
root = Tk()
root.title("Calculating square root")
x_val = DoubleVar()
x_lab = Label(root, text = "x").grid(row=0, column=0)
nmb = Entry(root, textvariable = x_val).grid(row=0, column=1)
calc = Button(root, text = "Calculate", command=square_calc).grid(columnspan=2)
y_lab = Label(root, text = " ").grid(row=3, column=0)
root.mainloop()
The problem is that, every time you call square_calc(), you are simply creating and placing another Label. The right way to do this is to create a Label outside the function, then have the function update the Label's text with mylabel.config(text='new text').
The display is getting messed-up because every time your square_calc() function is called it creates new pair of Labels, but this may leave some parts of any previous ones visible. Since the one on the left is the same every time, so it's not noticeable with it, but the text in one on the right in column 1 is potentially different every time.
A simple way to fix that is to make the Label a global variable and create it outside the function, and then just change its contents in the function. As with all Tkinter widgets, this is can be done after it's created by calling the existing obj's config() method.
Here's a minimally-modified version of your code that illustrates doing that. Note, it also adds a sticky keyword arugment to the grid() method call for the label to left-justify it within the grid cell so it's closer to the text label immediately to its left (otherwise it would be center-justified within the cell).
from Tkinter import *
def square_calc():
x = x_val.get()
sqx = x ** 0.5
# print x, "** 0.5 =", sqx
sqx_txt = Label(root, text = "x ** 0.5 =").grid(row=3, column=0)
sqx_lab.config(text=sqx)
sqx_lab.grid(row=3, column=1, sticky=W)
root = Tk()
root.title("Calculating square root")
x_val = DoubleVar()
x_lab = Label(root, text = "x").grid(row=0, column=0)
nmb = Entry(root, textvariable = x_val).grid(row=0, column=1)
calc = Button(root, text = "Calculate", command=square_calc).grid(columnspan=2)
y_lab = Label(root, text = " ").grid(row=3, column=0)
sqx_lab = Label(root, text = " ")
root.mainloop()
There's another potentially serious flaw in your code. All those assignments of the form
variable = Widget(...).grid(...)
result in assigning the value None to the variable because that's what the grid() method returns. I didn't fix them because they do no harm in this cause since none of those variables are ever referenced again, but it would have been a problem if the new globally variable sqx_lab had been done that way since it is referenced elsewhere.

Categories

Resources