Is it possible to remove/deactivate variables from a line of code once it has been executed? If not, what are my other options? I wrote a code here to demonstrate what I mean:
from Tkinter import *
root = Tk()
ent_var1 = StringVar()
ent_var2 = StringVar()
ent_var3 = StringVar()
cbtn_var1 = BooleanVar()
cbtn_var2 = BooleanVar()
cbtn_var3 = BooleanVar()
ent1 = Entry(textvariable=ent_var1).pack()
ent2 = Entry(textvariable=ent_var2).pack()
ent3 = Entry(textvariable=ent_var3).pack()
cbtn1 = Checkbutton(text=1, variable=cbtn_var1).pack(side = LEFT)
cbtn2 = Checkbutton(text=2, variable=cbtn_var2).pack(side = LEFT)
cbtn3 = Checkbutton(text=3, variable=cbtn_var3).pack(side = LEFT)
# prints what was written in entires
def set_variables():
lbl1 = ent_var1.get()
lbl2 = ent_var2.get()
lbl3 = ent_var3.get()
print lbl1, lbl2, lbl3
return
# calls set_variables
btn1 = Button(root, text="Done!", command=set_variables).pack()
root.mainloop()
When you fill the entries and press "Done!", what was written is printed. But how do I make it so that when I press the checkboxes, the entry linked to it will not be printed the next the I press "Done!"? The checkbox with the text "1" should be linked with the first entry, and so on.
I came up with this:
def should_print():
global lbl_print
if cbtn1:
lbl_print += lbl1
if cbtn2:
lbl_print += lbl2
if cbtn3:
lbl_print += lbl3
But it would only print the values of my variables at that very moment, not the variables themselves (meaning I'd have to run this code every time a variable changes).
Thank you!
Your question is very hard to understand. I think what you want is for set_variables to only print the variables associated with a checked checkbox. If so, does the following do what you want?
def set_variables():
to_print = []
if cbtn_var1.get():
to_print.append(ent_var1.get())
if cbtn_var2.get():
to_print.append(ent_var2.get())
if cbtn_var3.get():
to_print.append(ent_var3.get())
print " ".join(to_print)
return
There are other ways to accomplish the same thing, but I'm guessing your main goal is to decide what to print based on which checkbuttons are checked. This does that, albeit in a rather ham-fisted manner.
Why don't you simply check in your set_variables function if each button is pressed? For example:
def set_variables():
if not cbtn_var1.get():
print ent_var1.get(),
if not cbtn_var2.get():
print ent_var2.get(),
if not cbtn_var3.get():
print ent_var3.get(),
print
The commas at the end of each print statement will cause it to not print a newline, which is taken care of by the print at the end. Also, this will make so that if the box is checked, the value they entered won't print. If you want it to print only if the box is checked, then remove the nots.
If you refactor your code a little bit, you can do the same thing with one line. First, add this line:
cbtn_var1 = BooleanVar()
cbtn_var2 = BooleanVar()
cbtn_var3 = BooleanVar()
buttonsAndValues = [(cbtn_var1,ent_var1), (cbtn_var2,ent_var2), (cbtn_var3,ent_var3)]
With the variables in a list, you can use a list comprehension in some Python magic:
def set_variables():
print ' '.join(value.get() for checked, value in buttonsAndValues if checked.get())
If you haven't seen list comprehensions before, I'd suggest you read up about them - they can be very handy.
Related
Still pretty new to Python, so my apologies in advance...
I'm trying to use a button to move thru a List one item at a time.
It works the first time the button is clicked and moves to the second item in the List, but subsequent clicks keep returning the same values
from tkinter import *
window=Tk()
window.title('nextArrItem')
window.geometry("300x200+10+10")
options = ["H9Iu49E6Mxs", "YuWZNV4BkkY", "mBf6kJIbXLg", "Hz-xbM6jaRY"]
print("options[0] = " + options[0])
curItemText = options[0]
nextItemText = options[1]
curItem = 0
print('curItemText = '+curItemText)
print('nextItemText = '+nextItemText)
def nextArrItem(curItem=curItem+1):
print("str(curItem) = "+str(curItem))
try:
curItemText = options[curItem]
nextItemText = options[curItem+1]
print('curItemText = '+curItemText)
print('nextItemText = '+nextItemText)
curItem = curItem + 1
except:
print("End of Array Reached")
nextButton = Button(window, text="Next Item", command=nextArrItem)
nextButton.place(x=130, y=110)
window.mainloop()
When the Window opens initially, these values are returned:
options[0] = H9Iu49E6Mxs
curItemText = H9Iu49E6Mxs
nextItemText = YuWZNV4BkkY
The first click returns the following:
str(curItem) = 1
curItemText = YuWZNV4BkkY
nextItemText = mBf6kJIbXLg
Subsequent clicks keep returning the same values, so it only advances the first time and I'm not sure how to fix it. Although it probably doesn't look like it, this is the culmination of a lot of work just to get it to this point but I'm not sure where to go from here. I have the feeling the solution is going to be a true Homer Simpson "D'oh!" moment but I've steered this boat into shallow waters and need someone to help me from running aground...
Thanks in advance!
Paul
You need to increment the parameter each time to the next highest value. Currently your code just feeds the nextArrItem function the same value each time.
You could also try something to put the curItem inside a mutable data type so that it can be updated from within the scope of the function call like this:
...
options = ["H9Iu49E6Mxs", "YuWZNV4BkkY", "mBf6kJIbXLg", "Hz-xbM6jaRY"]
curItem = [0]
...
def nextArrItem(label=label):
try:
option = options[curItem[0]]
print(option)
label["text"] = option # updates label on each call
curItem[0] += 1 # increments index for option text
except IndexError:
print("End of Array Reached")
...
nextButton = Button(window, text="Next Item", command=nextArrItem)
...
Another way of doing it would be to bind the curItem variable to the window itself like this:
from tkinter import *
window=Tk()
window.curItem = 0
window.title('nextArrItem')
window.geometry("300x200+10+10")
options = ["H9Iu49E6Mxs", "YuWZNV4BkkY", "mBf6kJIbXLg", "Hz-xbM6jaRY"]
label = Label(window, text=options[window.curItem])
label.place(x=130, y=50)
def nextArrItem(label=label):
try:
option = options[window.curItem]
print(option)
label["text"] = option # updates label on each call
window.curItem += 1 # increments index for option text
except IndexError:
print("End of Array Reached")
nextButton = Button(window, text="Next Item", command=nextArrItem)
nextButton.place(x=130, y=110)
window.mainloop()
The issue you have is that curItem is both a global variable and a local variable in your callback function. You only ever update the local variable, so the change doesn't persist.
Here's how you're currently setting up the local variable, as an argument with a default value:
def nextArrItem(curItem=curItem+1):
The default value comes from the global variable, but it is only evaluated once, at the time the function is defined. It does not keep checking the global value, nor do changes to the local variable in the function change the global value either.
There's a better way. You can use a global statement to make it so that your function's code can directly read and write the global variable, so that there's only one curItem that everything is accessing the same way.
def nextArrItem():
global curItem
# the rest can be the same
I'm new in python programming and I'm having some issues in developing a specific part of my GUI with Tkinter.
What I'm trying to do is, a space where the user could enter (type) his math equation and the software make the calculation with the variables previously calculated.
I've found a lot of calculators for Tkinter, but none of then is what I'm looking for. And I don't have much experience with classes definitions.
I made this simple layout to explain better what I want to do:
import tkinter as tk
root = tk.Tk()
Iflabel = tk.Label(root, text = "If...")
Iflabel.pack()
IfEntry = tk.Entry(root)
IfEntry.pack()
thenlabel = tk.Label(root, text = "Then...")
thenEntry = tk.Entry(root)
thenlabel.pack()
thenEntry.pack()
elselabel = tk.Label(root, text = "else..")
elseEntry = tk.Entry(root)
elselabel.pack()
elseEntry.pack()
applybutton = tk.Button(root, text = "Calculate")
applybutton.pack()
root.mainloop()
This simple code for Python 3 have 3 Entry spaces
1st) If...
2nd Then...
3rd) Else...
So, the user will enter with his conditional expression and the software will do the job. In my mind, another important thing is if the user left the "if" space in blank, he will just type his expression inside "Then..." Entry and press the button "calculate" or build all expression with the statements.
If someone could give some ideas about how and what to do....
(without classes, if it is possible)
I'l give some situations for exemplification
1st using statements:
var = the variable previously calculated and stored in the script
out = output
if var >= 10
then out = 4
else out = 2
2nd Without using statement the user will type in "Then" Entry the expression that he want to calculate and that would be:
Then: Out = (((var)**2) +(2*var))**(1/2)
Again, it's just for exemplification...I don't need this specific layout. If anyone has an idea how to construct it better, is welcome.
Thanks all.
Here is a simple version of what you are trying to do.
We need to use the eval built in function to evaluate the math of a string.
We should also write our code with some error handling as there is a very good change a user will type a formula wrong and the eval statement will fail.
For more information on eval and exec take a look at this post here. I think it does a good job of explaining the two.
Here is what it would look like:
import tkinter as tk
root = tk.Tk()
math_label = tk.Label(root, text = "Type formula and press the Calculate button.")
math_label.pack()
math_entry = tk.Entry(root)
math_entry.pack()
result_label = tk.Label(root, text = "Results: ")
result_label.pack(side = "bottom")
def perform_calc():
global result_label
try:
result = eval(math_entry.get())
result_label.config(text = "Results: {}".format(result))
except:
result_label.config(text = "Bad formula, try again.")
applybutton = tk.Button(root, text = "Calculate", command = perform_calc)
applybutton.pack()
root.mainloop()
The first answer gets at the right thought, but it can also be matched a little more explicitly to the example you gave, in case you want to take this a little further.
Basically you want to use the eval statement to test your conditional, and then use the exec statement to run your python code blocks. You have to pass in the globals() argument in order to make sure your exec functions modify the correct variables in this case
See below:
import tkinter as tk
from tkinter import messagebox
var = 10
out = 0
def calculate():
global out
try:
if eval(IfEntry.get()):
exec(thenEntry.get(), globals())
else:
exec(elseEntry.get(), globals())
messagebox.showinfo(title="Calculation", message="out: " + str(out))
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
msg = traceback.format_exception(exc_type, exc_value, exc_traceback)
messagebox.showinfo("Bad Entry", message=msg)
root = tk.Tk()
Iflabel = tk.Label(root, text = "If...")
Iflabel.pack()
IfEntry = tk.Entry(root)
IfEntry.insert(0, "var >= 10")
IfEntry.pack()
thenlabel = tk.Label(root, text = "Then...")
thenEntry = tk.Entry(root)
thenlabel.pack()
thenEntry.insert(0, "out = 4")
thenEntry.pack()
elselabel = tk.Label(root, text = "else..")
elseEntry = tk.Entry(root)
elselabel.pack()
elseEntry.insert(0, "out = 2")
elseEntry.pack()
applybutton = tk.Button(root, command=calculate, text = "Calculate")
applybutton.pack()
applybutton.focus_displayof
root.mainloop()
I have a label that I am setting a random number to.
output.set(randint(1,4))
output = StringVar()
ttk.Label(mainframe, textvariable=output)
I also have an Entry where the user can specify how many random numbers they want to generate.
ttk.Entry(mainframe, textvariable=numberdice, width=5)
What I am trying to do is to get the two values to work together, and have the output label print multiple lines.
I got it to print to the console using a for loop:
for x in range(int(numberdice)):
print(randint(1,4))
But I cant get it to display the same in the GUI...
How to use the print function to display to a GUI
You can create a StringVar that supports write so you can print to it like a file:
import tkinter as tk
class WritableStringVar(tk.StringVar):
def write(self, added_text):
new_text = self.get() + added_text
self.set(new_text)
def clear(self):
self.set("")
Then using print(... , file=textvar) you can add to the string variable exactly the same way as printing to the console:
root = tk.Tk()
textvar = WritableStringVar(root)
label = tk.Label(root, textvariable=textvar)
label.pack()
for i in range(4):
print("hello there", file=textvar)
root.mainloop()
note that because print statements (by default) add a newline at the end there will be an extra blank line at the bottom of the label, if it bugs you it can be worked around by removing the newline and reinserting it next addition:
class WritableStringVar(tk.StringVar):
newline = False
def write(self, added_text):
new_text = self.get()
if self.newline: #add a newline from last write.
new_text += "\n"
self.newline = False
if added_text.endswith("\n"): #remove this newline and remember to add one next write.
added_text = added_text[:-1]
self.newline = True
new_text += added_text
self.set(new_text)
def clear(self):
self.set("")
self.newline = False
Either way you can make use of prints convenient semantics to get text on your GUI.
I'm trying to make a GUI that display lots of checkbuttons, i create them from a list; make a dictionary form the list and assign each checkbutton a variable from the dictionary so i can check it's state later.
Problem is that all the checkbuttons are displayed in an 'alternate' state, even if i set the variable to either 0 or 1, i've also tried changing states, but nothing seems to help.
y = 0
for x in get_dir_names(r'D:\SKL\test\win10'):
drv_check[x] = Variable()
drv_check[x].set(0)
center_window(150, 500, top_child)
drv = ttk.Checkbutton(child_frame, text=x, variable=drv_check[x])
drv.grid(row=y, column=0, sticky=W)
y += 1
for reference
def get_dir_names(dir_path):
"""Get names only of all directories from a given path (none recursive)"""
drv_list = [x for x in os.walk(dir_path).__next__()[1]]
drv_name = dict({})
for y in drv_list:
tmp_ver = dir_path + r'\\' + y
drv_name[y] = (os.walk(tmp_ver).__next__()[1]).pop()
return drv_name
Figured it out, I've made a "toggle all" button and it seemed to fix it, but it's weird that it didn't work before.
here's the function i used:
def toggle_all(*args):
while True:
if toggle_all_var.get() == '1':
for name in drv_check:
drv_check[name].set('1')
elif toggle_all_var.get() == '0':
for name in drv_check:
drv_check[name].set('0')
ttk.Checkbutton(drv_frame, text='Toggle all', variable=toggle_all_var).grid(row=y, column=0, sticky=W)
Also i run the function in a new thread.
I am writing a small Tkinter/Python program, that has a list of checkboxes with variable length (determined at run time).
I want to be able to read the state of all the checkboxes at any time, but I am not sure how I should go about that.
Here's the code snippet for generating the list (adopted from this post):
def relist(self):
self.text.delete(1.0,END)
p = subprocess.Popen (['ls', '/dev/'], stdout = subprocess.PIPE)
lst = p.communicate()[0].split('\n')
print lst
for item in lst:
v = tk.IntVar()
cb = tk.Checkbutton(text="/dev/%s" % item, variable=v, command=self.cb(index))
self.text.window_create("end", window=cb)
self.text.insert("end", "\n") # to force one checkbox per line
And my dummy handler:
def cb(self,idx):
print ("var is %s", str(idx))
lst[idx] = 1;
The problem is that my handler is getting called once (when the Checkbuttons are created), whereas I want it to get called everytime a Checkbutton is clicked (checked or unchecked), and when it is called, I want it to update lst.
Your CheckButton command is executing the callback because that's what you are telling it to do. The command is supposed to be a reference to a function that tkinter can execute when the checkbutton is clicked. Tkinter passes the event object to the callback function. See this Effbot tutorial, but it looks like you are trying to implement their pattern already. You can get a reference to the checkbutton from the event.widget attribute as explained here. Finally, you need to attach your variable to "self" if you want to refer to it in the callback.
def relist(self):
self.text.delete(1.0,END)
p = subprocess.Popen (['ls', '/dev/'], stdout = subprocess.PIPE)
lst = p.communicate()[0].split('\n')
print lst
self.var = tk.IntVar()
for item in lst:
cb = tk.Checkbutton(text="/dev/%s" % item, variable=self.var, command=self.myCallback)
self.text.window_create("end", window=cb)
self.text.insert("end", "\n") # to force one checkbox per line
def myCallback(self,event):
var = self.var.get()
print ("var is %s", str(var))
I think what you have asked for can be derived from here.
For each item in lst it must be previously created different IntVar() variable, just to indicate independent state of each checkbox.
I do not see other way than to create them manually (I assume you don't have hundred of checkboxes).
I will re-use the code from this answer and do the following:
def relist(self):
self.text.delete(1.0,END)
p = subprocess.Popen (['ls', '/dev/'], stdout = subprocess.PIPE)
lst = p.communicate()[0].split('\n')
print lst
self.var1 = tk.IntVar()
self.var2 = tk.IntVar()
self.var3 = tk.IntVar()
.
.
.
vars = [self.var1,self.var2,self.var3,...]
for item, var in zip(self.lst, vars):
cb = tk.Checkbutton(text="/dev/%s" % item, variable=var, command= lambda: self.myCallback(var))
self.text.window_create("end", window=cb)
self.text.insert("end", "\n") # to force one checkbox per line
def myCallback(self,event,var):
each_var = var.get()
print ("var is %s", str(each_var))
I had the same issue. Try this one:
cb = tk.Checkbutton(text="/dev/%s" % item, variable=v, command=lambda: self.cb(index))
If you pass method as lambda function it executes the method on every change of the variable.
Personally i don't use a tk.IntVar() / tk.StringVar() etc. but maybe i should.
It may not be the best way to do that but i think it's pretty much easy to understand. don't hesitate to criticize and tell me what's really bad and not pythonic so i can improve myself (i'm still a newbie).
i make an interator then i create my checkbuttons in a loop and in the callback I pass in parameter the value of the checkbutton and the iterator.
...
self.listeColonneFile1 = []
self.chbFile1 = []
indice = 0
for column in dfFile1.columns:
btn = ttk.Checkbutton(self.frameCheckButtonsFile1,
text=column,
command=lambda i=indice, col=column: self.callback_onCheck(col, i)
)
self.chbFile1.append(btn)
self.chbFile1[indice].grid(row = indice, column = 0, sticky="nw")
self.chbFile1[indice].state(['!alternate'])
indice += 1
In my callback, i have a list of all the checkButtons which are checked (well, not the ChB but its text or its value) :
def callback_onCheck(self, column, indice):
if self.chbFile1[indice].instate(['selected']) == True:
self.listeColonneFile1.append(column)
else:
self.listeColonneFile1.remove(column)
PS : dfFile1 is a pandas DataFrame, see the doc