Waiting inside a for loop for button click in Tkinter - python

I am trying create a for loop based on the decimal value of the option menu button.
If the option button chosen is 2, then the for loop circulates for two times.
But the for loop needs to wait until the button created inside the for loop gets clicked.
Below code shows the same.
Option menu has range of 1 to 10 i.e. user can choose 1 to 10 device to be created.
The moment the option menu is choose, the command button for the option menu gets operated i.e. comp_gt_data.
This basically circulates in a for loop to take the configuration of the N number of devices.
Once after taking the configuration for the 1st device, I create a "Enter the Next Device Configuration" button inside the for loop and wanted the button to be clicked to take the next device configuraiton.
But the "Enter Next Device ...." button is not getting created and keeps circulating in while loop.
I tried using time.sleep(60) but still didn't work.
The "Enter Next Device.." basically tries to clear the old device configuration and tries to set cvadt_val_loop_exit=0 so that the while loop can exit.
QUERY: HOW TO Basically wait inside a for loop for a button to be clicked to proceed further?
self.cvo1 = OptionMenu(root, cvagt_val, *cvagt_list, command=self.comp_gt_data)
self.cvo1.pack()
def comp_gt_data(self, *args):
if cvagt_val.get()!=0:
cvagt_val_loop = cvagt_val.get()
for cvagt_loop in range(1, cvagt_val_loop+1):
self.cvad_l0 = Label(root, text="Enter the %d Device Config"% cvagt_loop)
self.cvad_l0.pack()
self.cvad_l1 = Label(root, text="How Many D~S You Want To Create: ")
self.cvad_l1.pack()
self.cvad_e1 = Entry(root)
self.cvad_e1.pack()
self.cvad_l2 = Label(root, text="How Many MS You Want To Create: ")
self.cvad_l2.pack()
self.cvad_e2 = Entry(root)
self.cvad_e2.pack()
self.cvad_l3 = Label(root, text="How Many SD You Want To Create: ")
self.cvad_l3.pack()
self.cvad_e3 = Entry(root)
self.cvad_e3.pack()
if cvagt_loop < cvagt_val_loop:
if not (self.cv_next_dev_cfg_button):
self.cv_next_dev_cfg_button = Button(root, text="Enter Next Device Configuration", command=self.cv_next_dev_cfg_button_code)
self.cv_next_dev_cfg_button.pack()
# Waiting For cvadt_val_loop_exit bit to get set when the cv_next_dev_cfg_button is clicked but its not happening and circulating in the while loop
cvadt_val_loop_exit = 1
while(cvadt_val_loop_exit):
print "Waiting For Next Device Config Button To Be Hit"
time.sleep(60)
else:
self.cv_next_dev_cfg_button_code()
if not (self.return_button):
self.return_button = Button(root, text="Return To Main Menu", command=self.return_button_code)
self.return_button.pack()
else:
print "Choose Atleast One Device To Create"
def cv_next_dev_cfg_button_code(self):
if (self.cvad_l0):
self.cvad_l0.pack_forget(); self.cvad_l0.destroy(); self.cvad_l0 = None
if (self.cvad_l1):
self.cvad_l1.pack_forget(); self.cvad_l1.destroy(); self.cvad_l1 = None
if (self.cvad_l2):
self.cvad_l2.pack_forget(); self.cvad_l2.destroy(); self.cvad_l2 = None
if (self.cvad_l3):
self.cvad_l3.pack_forget(); self.cvad_l3.destroy(); self.cvad_l3 = None
if (self.cvad_e1):
self.cvad_e1.pack_forget(); self.cvad_e1.destroy(); self.cvad_e1 = None
if (self.cvad_e2):
self.cvad_e2.pack_forget(); self.cvad_e2.destroy(); self.cvad_e2 = None
if (self.cvad_e3):
self.cvad_e3.pack_forget(); self.cvad_e3.destroy(); self.cvad_e3 = None
if (self.cv_next_agt_cfg_button):
self.cv_next_agt_cfg_button.pack_forget(); self.cv_next_agt_cfg_button.destroy(); self.cv_next_agt_cfg_button = None
cvadt_val_loop_exit = 0

A for() is not the answer here. Instead call a function the correct number of times and wait however long you want in between. This should give you enough to get started.
try:
import Tkinter as tk ## Python 2.x
except ImportError:
import tkinter as tk ## Python 3.x
class ButtonForLoop():
def __init__(self, top):
self.top=top
self.but_1=tk.Button(top, text="Start for()", command=self.start_for)
self.but_1.grid(row=0, column=0)
self.num_for=0
## for simplicity, assume 3 iterations through the loop
self.max_num=3
def next_pass(self):
## create button again
self.but_1=tk.Button(top, text="Pass # %d" % (self.num_for),
command=self.start_for)
self.but_1.grid(row=0, column=0)
def start_for(self):
""" button clicked so call next_pass() the correct
number of iterations
"""
## destroy whatever button is showing
self.but_1.destroy()
if self.num_for < self.max_num:
self.num_for += 1
self.top.after(500, self.next_pass)
else:
self.top.quit()
top=tk.Tk()
BF=ButtonForLoop(top)
top. mainloop()

Related

Sleep while loop without break it, for write new entry in tkinter

In this UI there is 2 buttons and 1 entry box. Buttons named "Start Loop" and "Print".
When i write a text into the entry box, i should able to see it's print out when pressed the related button. What i am trying to do is, put that button press in a while loop. But the interesting part is trying to print a new entry in every 10 seconds.
When you entered a entry and press the "Start Loop" button, it becomes run. Meanwhile user interface window will froze. Can't write a new entry or press "Print" button. Even i use time.sleep function it still frozen. It prints old entry, but i want to write a new entry at every iteration. Check out the code:
import time
from tkinter import *
class tkin(Frame):
def __init__(self,parent):
Frame.__init__(self, parent)
self.parent = parent
self.UI()
def UI(self):
self.down = StringVar()
self.down_entry = Entry(self, textvariable=self.down)
self.down_entry.grid(row=2, column=0)
self.start_loop_buuton = Button(text="Start Loop", command=self.loop_func)
self.start_loop_buuton.place(x=10,y=40)
self.print_func_button = Button(text="Print ", command=self.pprint)
self.print_func_button.place(x=120,y=40)
self.pack()
def loop_func(self):
start = time.time()
while True:
print("standart print out")
end = time.time()
if (end- start) >10:
time.sleep(10)
self.print_func_button.invoke() ## press print_func_button
start = time.time()
def pprint(self):
print("WHICH PRINT LINE I WANT TO PRINT IN LIKE EVERY 10 SECONDS")
print(self.down.get())
def main():
root = Tk()
tkin(root)
root.geometry("195x100+300+300")
root.mainloop()
main()
Any advice would be nice. Thanks in advance.
this is how I would redefine Your method:
def loop_func(self):
self.print_func_button.invoke() # press print_func_button
self.after(3000, self.loop_func)
time is in miliseconds so this will be 3 seconds
In this example, i just separated the looping "loop_func()" using thread in Button function's argument of command. Like that:
import threading
...
...
...
self.start_loop_buuton = Button(text="Start Loop", command=threading.Thread(target=self.loop_func).start())
So, these two loops have been separated.

Create and call Variables In a for loop

i am trying to make it so that a user inputs a number, and that number of buttons are created Using TKinter, I have tried doing it by using the following, Where the Buttons are successfully created, however i am struggling with calling them in order to place them / display them on the grid (Added randint to simulate user input (User Input not limited to 9 and may be as high as 40))
from tkinter import *
from random import randint
inputValue = randint(3,9)
print(inputValue)
root = Tk()
while inputValue > 0: # for every number in inputted value
inputValue = int(inputValue) - 1 # take one
globals()['Sailor%s' % inputValue] = Button(root, text="Lap :" + str(inputValue), command=lambda: retrieve_input()) # Create the button function in the format 'Sailors{Inputnumber}'
('Sailors%s' % inputValue).grid(row=inputValue, column=1, columnspan=2) # Place the button (Doesn't work)
root.mainloop() # Does work (required)
Howerver the following does not work (It is meant to place the button),
('Sailors%s' % inputValue).grid(row=inputValue, column=1, columnspan=2) # Place the button (Doesn't work)
Can you think of a method i can use in order to create and place Amount of buttons?
Thanks in advance
You should never create dynamic variable names like you are attempting to do. It adds a lot of complexity, reduces clarity, and provides no real benefit.
Instead, use a dictionary or list to keep track of the buttons. In your case, however, since you're never using the buttons anywhere but in the loop you can just use a local variable.
Example using a local variable, in case you never need to access the button in code after you create it:
for count in range(inputValue):
button = Button(...)
button.grid(...)
Here's how you do it if you need to access the buttons later in your code:
buttons = []
for count in range(inputValue):
button = Button(...)
button.grid(...)
buttons.append(button)
With the above you can iterate over all of the buttons in buttons:
for button in buttons:
button.configure(state='disabled')
If you need to configure a single button, use its index:
button[0].configure(...)
You can calling grid on a string at the moment, which throws your error.
Your need to replace ('Sailors%s' % inputValue) with globals()['Sailor%s' % inputValue], with orders your buttons on individual rows, labeled 0-8.
So, your current code is:
from tkinter import *
from random import randint
inputValue = randint(3,9)
print(inputValue)
root = Tk()
while inputValue > 0: # for every number in inputted value
inputValue = int(inputValue) - 1 # take one
globals()['Sailor%s' % inputValue] = Button(root, text="Lap :" + str(inputValue), command=lambda: retrieve_input()) # Create the button function in the format 'Sailors{Inputnumber}'
globals()['Sailor%s' % inputValue].grid(row=inputValue, column=1, columnspan=2)
root.mainloop() # Does work (required)
When retrieve_input is defined, the code will work fine.
Just to point out, instead of inputValue = int(inputValue) - 1 you can use inputValue -= 1.

How to Check to See Which Listbox Item Was Selected in TkInter

I'm trying to figure out how to see if the user picks certain currencies (EUR'S and CAD'S right now) from a Listbox, but when I try to configure the callback of the buttons, it does nothing. Here's what I'm trying:
if select_option == 1:
global btn_convert
btn_convert.config(command=euro_callback)
elif select_option == 2:
global btn_convert
btn_convert.config(command=cad_callback)
But this doesn't work. This is my list:
select_option = tk.Listbox(window, height=2, selectmode="SINGLE")
select_option.insert(1, "EURO")
select_option.insert(2, "CAD")
Am I doing it wrong? Can't I ask if the user selected the option "EURO" or "CAD" directly in the script?
And all I found when I searched is stuff about regular list, or finding the results of many separate lists (but what I'm trying to figure out is how to get that result and apply a command to a button). When I tried most of these, it just does nothing.
Here's the full code:
# Imports the tkinter module, which is vital for the GUI
import tkinter as tk
# Imports the html module from the lxml module. We'll be getting our exchange rates from https://www.bloomberg.com
from lxml import html
import requests
# Makes the window
window = tk.Tk()
# Titles the window
window.title("Currency Converter")
# Makes the window 275 x 200 pixels
window.geometry("275x200")
# Makes the background crimson
window.configure(bg="#900C3F")
# window.wm_iconbitmap("penny.ico")
# Gets the information from Bloomberg Markets
page_euro = requests.get('https://www.bloomberg.com/quote/EURUSD:CUR')
page_cad = requests.get('https://www.bloomberg.com/quote/USDCAD:CUR')
tree_euro = html.fromstring(page_euro.content)
tree_cad = html.fromstring(page_cad.content)
'''
When the "Convert" button is pressed, it'll get the value from the text
entry where you put in your value you want to convert to (EUROS or CAD
(Canadian Dollars)), and it'll ask itself; "Is the value all numbers? Or does
it have characters too?". If it doesn't have characters, it'll run as normal.
If it DOES have characters, it'll inform you that, like; "Whoops! You can
only put numbers there!"
'''
def euro_callback():
usd_amount = ent_convert.get()
if str(usd_amount).isdigit():
usd_amount = float(ent_convert.get())
# <div class="price">1.****</div>
euro_exchange = tree_euro.xpath('//div[#class="price"]/text()')
euro_exchange = float(str(euro_exchange[0]))
euro_amount = usd_amount / euro_exchange
lbl_amount.config(text="Euro Amount: %.2f€" % euro_amount)
else:
lbl_amount.config(text="Whoops! You can only put numbers there!")
def cad_callback():
cad_amount = ent_convert.get()
if str(cad_amount).isdigit():
usd_amount = float(ent_convert.get())
# <div class="price">1.2652</div>
cad_exchange = tree.xpath('//div[#class="price"]/text()')
cad_exchange = float(str(cad_exchange[0]))
cad_amount = usd_amount / cad_exchange
lbl_amount.config(text="Canadian Dollar amount: %.2f$" % cad_amount)
else:
lbl_amount.config(text="Whoops! You can only put numbers there!")
btn_convert.config(command=callback)
def callback():
selection = select_option.curselection()[0]
if selection == 1:
# euro_callback()
elif selection == 2:
# cad_callback()
# The list of available currencies to convert to
# lbl_usd = tk.Label(window, text="Enter the USD ($) here:", bg="#900C3F", fg="#FFFFFF")
select_option = tk.Listbox(window, height=2, selectmode="SINGLE")
select_option.insert(1, "EURO")
select_option.insert(2, "CAD")
ent_convert = tk.Entry(window)
# A blank label, followed by a button, which has the usd_callback() command
lbl = tk.Label(window, text=" ", bg="#900C3f")
btn_convert = tk.Button(window, text="Convert", command=callback)
# A blank label, followed by a label that outputs the EURO amount you would get
lbl2 = tk.Label(window, text=" ", bg="#900C3f")
lbl_amount = tk.Label(window, bg="#900C3F", fg="#FFFFFF")
# Packs (adds) all the labels, entries and buttons into the window
select_option.pack()
# lbl_usd.pack()
ent_convert.pack()
lbl.pack()
btn_convert.pack()
lbl2.pack()
lbl_amount.pack()
# Loops the window, and starts the program
window.mainloop()
Any help would be appreciated. Thank you!!
Edit:
I figured it out! This is the answer I was looking for:
def callback():
selection = select_option.curselection()[0]
if selection == 0:
# euro_callback()
btn_convert.config(command=euro_callback)
elif selection == 1:
# cad_callback()
btn_convert.config(command=cad_callback)
# The list of available currencies to convert to
select_option = tk.Listbox(window, height=2, selectmode="SINGLE")
select_option.insert(0, "EURO")
select_option.insert(1, "CAD")
Thanks to Novel and Nelson for helping me!!
The problem is that the callback is configured once (ideally), when the program boots. The best solution is to use a callback that checks the listbox and can sort out what to do.
For example:
btn_convert.config(command=callback)
#...
def callback():
selection = select_option.curselection()[0]
if selection == 1:
euro_callback()
elif selection == 2:
cad_callback()

Trying to produce function for a custom number of tkinter Entry fields.

I've been trying to create a piece of code that would take a integer as a argument and create that number of tkinter entry fields. With a submit button at the end that would retrieve the data from the fields add these data to a list then close the window.
I have been able to get it working however I cant find a way to convert this to a callable function; a requirement to use it with the rest of my program.
This is the code I have produced so far, thanks:
import tkinter as tk
b = input("Enter: ")
b = int(b)
root = tk.Tk()
newdict = dict()
outputs = list()
for i in range(b):
newdict["entry" + str(i)] = tk.Entry(root)
newdict["entry" + str(i)].pack()
button1 = tk.Button(root, text="Submit", command=lambda: Get(newdict))
button1.pack()
def Get(newdict):
for j in range(b):
outputs.append(newdict["entry" + str(j)].get())
root.quit()
root.mainloop()
print(outputs)
The basic idea is to create a window, then use the wait_window method to wait for the window to be destroyed. Once it has been destroyed you can return some value.
The problem is that the values you want to fetch must not be attributes of the window, since it will have been destroyed by the time you are ready to fetch them. You need to set up your code to save the values before the window is destroyed.
A simple way is to provide an "OK" button which gets the values and then destroys the window. Another way would be to put a trace on variables associated with each entry, and save the values immediately as they are edited.
Which method you choose depends on what behavior you want when the user clicks the window control to close the window (eg: the red circle on OSX, the [x] button on windows, etc). Do you want to return what they had input, or do you treat that as a cancel action and return nothing?
Here's a simple example using an OK button. This example assumes that you aren't already running a GUI, and that this is to be run as part of a non-GUI application.
import tkinter as tk
class Dialog(object):
def show(self, num_fields):
self.num_fields = num_fields
self.root = tk.Tk()
self.entries = []
for i in range(num_fields):
entry = tk.Entry(self.root)
entry.pack(fill="x")
self.entries.append(entry)
ok = tk.Button(self.root, text="OK", command=self.ok)
ok.pack(side="bottom", anchor="e", pady=(10,0), padx=10)
# wait for the window to be destroyed, then
# return the values. If the user clicks the OK button
# the values will be set; if they cancel the dialog
# this will return None.
self.values = None
self.root.wait_window()
return self.values
def ok(self):
# save all the values, then destroy the window
self.values = []
for i in range(self.num_fields):
self.values.append(self.entries[i].get())
self.root.destroy()
Assuming you're running a non-gui program, here's an example of how you would use this class:
b = input("Enter: ")
b = int(b)
result = Dialog().show(b)
print("result:", result)

Simple gui that display a repeated set of images when a button is pushed

I made a very simple gui that has a button and shows an image(.gif). My goal is to output another .gif whenever you press the button. There are 2 .gif files in my file directory and the point is to keep switching between these two whenever you press the button.
#Using python2.7.2
import Tkinter
root = Tkinter.Tk()
try:
n
except:
n = 0
def showphoto(par):
if par%2 == 0:
try:
label2.destroy()
except:
pass
photo = Tkinter.PhotoImage(file="masc.gif")
label2 = Tkinter.Label(image=photo)
label2.image = photo
label2.pack()
else:
try:
label2.destroy()
except:
pass
photo = Tkinter.PhotoImage(file="123.gif")
label2 = Tkinter.Label(image=photo)
label2.image = photo
label2.pack()
myContainer1 = Tkinter.Frame(root, width = 100, height = 100)
myContainer1.pack()
def callback(event):
global n
showphoto(n)
n = n + 1
button1 = Tkinter.Button(myContainer1)
button1["text"]= "Next pic"
button1["background"] = "green"
button1.bind("<Button-1>", callback(n))
button1.pack()
root.mainloop()
The current code just outputs the first image (masc.gif) but when I press the button it doesn't switch to the other image(123.gif). What am I doing wrong?
This can achieved much easier with classes as the class holds all the data necessary without the use of global variables.
import Tkinter as tk
from collections import OrderedDict
class app(tk.Frame):
def __init__(self,master=None, **kwargs):
self.gifdict=OrderedDict()
for gif in ('masc.gif','123.gif'):
self.gifdict[gif]=tk.PhotoImage(file=gif)
tk.Frame.__init__(self,master,**kwargs)
self.label=tk.Label(self)
self.label.pack()
self.button=tk.Button(self,text="switch",command=self.switch)
self.button.pack()
self.switch()
def switch(self):
#Get first image in dict and add it to the end
img,photo=self.gifdict.popitem(last=False)
self.gifdict[img]=photo
#display the image we popped off the start of the dict.
self.label.config(image=photo)
if __name__ == "__main__":
A=tk.Tk()
B=app(master=A,width=100,height=100)
B.pack()
A.mainloop()
Of course, this could be done more generally ... (the list of images to cycle through could be passed in for example), and this will switch through all the images in self.gifs ...
This approach also removes the necessity to destroy and recreate a label each time, instead we just reuse the label we already have.
EDIT
Now I use an OrderedDict to store the files. (keys=filename,values=PhotoImages). Then we pop the first element out of the dictionary to plot. Of course, if you're using python2.6 or earlier, you can just keep a list in addition to the dictionary and use the list to get the keys.
button1 = Tkinter.Button(myContainer1)
button1["text"]= "Next pic"
button1["background"] = "green"
button1.bind("<Button-1>", callback(n))
First, you bind the <Button-1> event to None (that's what callback(n) evaluates to). You should bind it to callback (no parentheses a.k.a the call operator).
Second, I suggest you change callback to not accept any arguments, remove the bind call and create your button as:
button1 = Tkinter.Button(myContainer1, command=callback)

Categories

Resources