Viewing and Interact with function that requires User input in Tkinter - python-3.x

I am Creating an App with Tkinter and for the App I need to use a function in a package that requires user input. I am trying to use sys.stdout in a thread and outputting sys.stdout to a Listbox() this works for another function of mine doing like the same thing but there isn't any user input. In the first line of this thread I am changing a button then doing the operation, and the button doesn't change and the App goes into non-responsive. My question is will the sys.stdout send what the input is asking? and How do I have user input from entry box send to the function while it is still running in the thread? Thanks in advance
Truncated code bellow:
class Login(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
global Key
global button1
Key = tk.StringVar()
#Buttton to start thread
button1 = ttk.Button(self, text='Login',command=lambda:Setupthread2(lbx,button1,Key,KeyEntry))
button1.pack()
#Entry for user to input
KeyEntry = tk.Entry(self, show= '*', textvariable=Key)
KeyEntry.pack()
scb = tk.Scrollbar(self)
scb.pack(fill='y' ,side='right')
lbx = tk.Listbox(self, yscrollcommand=scb.set)
lbx.pack()
scb.config(command=lbx.yview)
def Setupthread2(object,button1,Key,KeyEntry):
global flag
send_process = threading.Thread(target=callback(object,button1,Key,KeyEntry))
send_process.start()
flag = False
def callback(object,button1,Key,KeyEntry):
#Changing the button so I can use the same button for both starting the tread and to send to sample func
button1.config(text = 'Send Code', command = lambda:SendUsrInput(Key,KeyEntry))
old_stdout = sys.stdout
sys.stdout = StdoutRedirectorLabel(object)
sample()
sys.stdout = old_stdout
#After Output finished set scrollbar to bottom
object.yview_moveto(1.0)
class StdoutRedirectorLabel(object):
def __init__(self,widget):
self.widget = widget
def write(self,text):
self.widget.insert('anchor',text)
# Me trying to send what they type in entry box to function
def SendUsrInput(Key,KeyEntry):
Key = Key.get()
print(Key)
KeyEntry.delete(0,'end')
#Truncated version of the function I need to show console and to send input to
def sample():
usrInput = input('Enter Input to get something Back')
print(usrInput + ' Was my input')

So in the module where it asks for user input with help I made a Tk window asking for input with a timeout using after()
def sample()
sms_code = loginTimeout()
def loginTimeout():
root = tk.Tk()
sms_code = ''
def get_entry() -> str:
"""Gets and returns the entry input, and closes the tkinter window."""
nonlocal sms_code
sms_code = entry_var.get()
root.destroy()
return sms_code
# Your input box
entry_var = tk.StringVar()
tk.Tk.wm_title(root,'Trading Bot')
tk.Label(root, text='Please Enter the Authorization Code').pack()
tk.Entry(root, textvariable=entry_var).pack()
# A button, or could be an event binding that triggers get_entry()
tk.Button(root, text='Confirm', command=get_entry).pack()
# This would be the 'timeout'
root.after(300000, get_entry)
root.mainloop()
return sms_code

Related

tkinter catch value from lambda function

I'm trying to wrap my head around this problem.
Say I have a code like this:
def get_input(data_A, data_B):
all_data = [data_A.get(),dataB.get()]
return(all_data)
def the_gui():
root = Tk()
data_A = Entry(root)
data_B = Entry(root)
button = Button(root, text='Submit', command=lambda: get_input(data_A, data_B))
mainloop()
My goal is to get the value of data_A and data_B once I clicked the submit button.
I tried to use global variable and everything, but I kept failing to catch the value.
The only thing that works is when I put the whole get_input() function inside the_gui() function. However, I don't think that's a good practice to implement.
Any suggestions?
Here is a simple example of how you could write this to get the results you are looking for.
When using global is that all your root window and related fields are in a function. So you would have to define global in both function and this is not what you want to do.
Typically you will want to write the root window in the global namespace and not in a function or write it into a class so you can avoid global's all-together.
button = Button(...) may not be doing what you think it is. This does not return a value from the command once clicked. Tkinter buttons do not care about anything being returned. So you have to record that value elsewhere.
I am not sure how you code is working as you do not use geometry managers and mainloop() should be attached to the root window so I have added those in as well.
Example 1:
import tkinter as tk
def get_input():
global a_and_b
a_and_b = [data_a.get(), data_b.get()]
# If you want to keep a running record of all values submitted
# then you can do this instead:
# a_and_b.append([data_a.get(), data_b.get()])
def print_a_b():
print(a_and_b)
root = tk.Tk()
a_and_b = []
data_a = tk.Entry(root)
data_b = tk.Entry(root)
data_a.pack()
data_b.pack()
tk.Button(root, text='Submit', command=get_input).pack()
tk.Button(root, text='Print A/B List', command=print_a_b).pack()
root.mainloop()
Example 2 using OOP:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.a_and_b = []
self.data_a = tk.Entry(self)
self.data_b = tk.Entry(self)
self.data_a.pack()
self.data_b.pack()
tk.Button(self, text='Submit', command=self.get_input).pack()
tk.Button(self, text='Print A/B List', command=self.print_a_b).pack()
def get_input(self):
self.a_and_b = [self.data_a.get(), self.data_b.get()]
def print_a_b(self):
print(self.a_and_b)
if __name__ == '__main__':
App().mainloop()

How to retrieve value from selected radiobutton after root.mainloop()?

I am looking to write a pop-up window which asks the user to select a specific option, and if the option does not exist, to add it. However, I am having trouble retrieving the value of the selected option (that is, the key from the dict). My code --summarized-- so far:
import tkinter as tk
class Category():
def __init__(self):
self.categories = {1:"Coffee",2: "Tesco"}
def index(...):
# code ... #
root = tk.Tk()
v = tk.IntVar()
# I was thinking this would help:
def quit():
global irow
irow = v.get()
print("Irow is",irow)
root.quit()
tk.Label(root, text="Choose category:").pack()
for key, cat in self.categories.items():
tk.Radiobutton(root, text=cat, variable=v, value=key).pack()
tk.Radiobutton(root, text="Other", variable=v, value=key+1).pack()
# I want to add a text box here so user can add the "Other"
tk.Button(root, text="Close", command=quit)
irow = v.get()
print(irow)
root.mainloop()
print(irow)
# code which uses irow #
Executing this code yields:
0
Irow is 0
0
regardless of what button I select. I expect irow to be 2 is I were to select Tesco or 1 if I selected coffee (or 3 if I selected other). Any guidance would be very much appreciated.
Typically, mainloop only exits after all of the widgets have been destroyed. Therefore, you can't directly get values from the widgets at this point. The simplest solution is to save the value to a global variable, or an instance variable if you're using classes.
For example, in your case you could do this:
def quit():
self.irow = v.get()
root.quit()
Then, after mainloop exists you can access self.irow
...
root.mainloop()
print(self.irow)
Thank you to Bryan Oakley for the answer. I have made 4 changes so it runs on the Linux terminal: 1) I changed the class from Tk() to Toplevel();
2) I put the dialog into a class for cleanliness' sake; 3) I changed self.quit() to self.destroy(); & 4) I changed mainloop() to wait_window().
class AddCategory(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self)
self.v = tk.IntVar()
self.parent = parent
tk.Label(self, text="Choose category:").pack()
for key, cat in self.parent.categories.items():
tk.Radiobutton(self, text=cat, variable=self.v, value=key).pack()
tk.Radiobutton(self, text="Other", variable=self.v, value=key+1).pack()
self.e = tk.Entry(self,text="")
self.e.pack()
tk.Button(self, text="Close", command=self.quit).pack()
def quit(self):
self.parent.key = self.v.get()
self.parent.cat = self.e.get()
print(self.v.get())
print(self.e.get())
self.destroy()
Note that parent is the class from which I am executing the "AddCategory" dialogue. I invoke it as follows:
class Category():
def __init__(self):
self.cat = self.key = self.desc = []
self.categories = {1:"Coffee",2: "Tesco"}
self.descriptions = {}
def index(...):
# code ... #
dialog = AddCategory(self) # I pass the parent onto the dialog
dialog.wait_window()
print(self.key)
print(self.cat)
This works because self.parent inside of AddCategory is a soft copy of parent.

Returning a value from a tkinter form

I'm using code where I need to ask the user for input, using a tkinter window (I'm not using tkinter in other parts of the code).
My issue is that I simply need to use the tkinter window to return a value upon pressing the OK button on the form, which will also close down the form. The only way I can get it to work so far is by using a global variable. I've searched for other solutions to this but either they don't return a value (they simply print it) or don't allow passing text for the prompt.
Thanks in advance if you can help with this.
from tkinter import *
def input_text(prompt):
def ok():
global ret
ret = entry.get()
master.destroy()
master = Tk()
lbl = Label(master, text=prompt)
lbl.pack()
entry = Entry(master)
entry.pack()
entry.focus_set()
butt = Button(master, text = "OK", width = 10, command = ok)
butt.pack()
mainloop()
print("I am here!")
ret=""
input_text("Enter something")
print("ret is:", ret)
After a good night's sleep I've solved the problem :-)
The solution was to create a class and return the response via an attribute. Here's the code for the archive ... just in case anyone out there has a similar question.
from tkinter import *
class InputForm():
def __init__ (self, prompt):
self.prompt = prompt
self.response = ""
def ok():
self.response = entry.get()
master.destroy()
master = Tk()
lbl = Label(master, text=self.prompt)
lbl.pack()
entry = Entry(master)
entry.pack()
entry.focus_set()
butt = Button(master, text = "OK", width = 10, command = ok)
butt.pack()
mainloop()
abc = InputForm("Enter something").response
print("returned value is:", abc)

How to use multithreading with tkinter to update a label and simultaneously perform calculations in a thread controlled by button events?

I'm trying to start a counter that displays the value in a label on a separate display window.
The main window has a START, STOP and DISPLAY button.
The START must start the counter, STOP must stop it and the display window must open only when I click on DISPLAY button.
Here's what I have so far. The buttons seem to be unresponsive and the display window pops up without user intervention. How can I fix this?
import tkinter as tk
import time
import threading
import queue
def Run_Device(disp_q,flaq_q):
temp_q = queue.Queue()
temp_q.put(0)
while(flaq_q.empty()):
#time.sleep(0.2)
count = temp_q.get()
count += 1
temp_q.put(count)
disp_q.put(count)
else:
flaq_q.queue.clear()
def P_Window(disp_q):
pw = tk.Tk()
value_label = tk.Label(pw, text=disp_q.get(), relief='sunken', bg='lemon chiffon', font='Helvetica 16 bold')
value_label.pack()
def update_values():
value_label.config(text=disp_q.get())
value_label.after(1000,update_values)
update_values()
pw.mainloop()
def Stop_Dev(flaq_q):
flaq_q.put("Stop")
if __name__ == "__main__":
disp_q = queue.Queue()
flaq_q = queue.Queue()
t_device = threading.Thread(target=Run_Device, args=(disp_q, flaq_q), name="Device 1")
t_disp = threading.Thread(target=P_Window, args=(disp_q, ), name="Display 1")
window = tk.Tk()
start_button = tk.Button(window, text='Start', command=t_device.start(), bg='spring green', font='Helvetica 12 bold', width=20, state='normal', relief='raised')
start_button.pack()
stop_button = tk.Button(window, text='Stop', command=lambda: Stop_Dev(flaq_q), bg='OrangeRed2', font='Helvetica 12 bold', width=20, state='normal', relief='raised')
stop_button.pack()
disp_param_button = tk.Button(window, text='Display', command=t_disp.start(), bg='sky blue', font='Helvetica 12 bold', width=20, state='normal', relief='raised')
disp_param_button.pack()
window.mainloop()
I'm trying to learn how to use multithreading in tkinter so any feedback would be appreciated
There are two issues I see with your code. The first is just simple bugs, e.g.:
start_button = tk.Button(window, text='Start', command=t_device.start(), ...
here it should be command=t_device.start otherwise the command is the function returned by t_device.start()
The second is you've not dealt with various "What if ...?" scenarios, e.g. What if the user pushes 'Start' or 'Display' multiple times?
I've tried to address the above in my rework below:
import tkinter as tk
from time import sleep
from queue import Queue, Empty
from threading import Thread
FONT = 'Helvetica 16 bold'
def run_device():
count = 0
while flaq_q.empty():
count += 1
disp_q.put(count)
sleep(0.5)
while not flaq_q.empty(): # flaq_q.queue.clear() not documented
flaq_q.get(False)
def p_window():
global pw
if pw is None:
pw = tk.Toplevel()
value_label = tk.Label(pw, text=disp_q.get(), width=10, font=FONT)
value_label.pack()
def update_values():
if not disp_q.empty():
try:
value_label.config(text=disp_q.get(False))
except Empty:
pass
pw.after(250, update_values)
update_values()
elif pw.state() == 'normal':
pw.withdraw()
else:
pw.deiconify()
def stop_device():
if flaq_q.empty():
flaq_q.put("Stop")
def start_device():
global device
if device and device.is_alive():
return
while not disp_q.empty():
disp_q.get(False)
disp_q.put(0)
device = Thread(target=run_device)
device.start()
if __name__ == "__main__":
disp_q = Queue()
flaq_q = Queue()
root = tk.Tk()
pw = None
device = None
tk.Button(root, text='Start', command=start_device, width=20, font=FONT).pack()
tk.Button(root, text='Stop', command=stop_device, width=20, font=FONT).pack()
tk.Button(root, text='Display', command=p_window, width=20, font=FONT).pack()
root.mainloop()
I've left out some details to simplify the example. Even with its additional checks, it's still nowhere near perfect. (E.g. it hangs if you don't 'Stop' before you close the window, etc.)
You might be able to adapt something like the following:
import tkinter as tk
import threading
import queue
import time
def run_device():
for i in range(4):
print(i)
pass
def process_queue(MyQueue):
"""Check if we got a complete message from our thread.
Start the next operation if we are ready"""
try:
# when our thread completes its target function (task),
# the supplied message is added to the queue
msg = MyQueue.get(0)
# check message
print(msg)
except queue.Empty:
# .get failed, come back in 100 and check again
print('no message')
threading.Timer(0.001, lambda q=MyQueue: process_queue(q)).start()
class ThreadedTask(threading.Thread):
"""threaded task handler"""
def __init__(self, queue, target, msg):
threading.Thread.__init__(self)
# message to add to queue when the task (target function) completes
self.msg = msg
# function to run
self._target = target
# queue to store completion message
self.queue = queue
def run(self):
"""called when object is instantiated"""
# start users task
try:
self._target()
except Exception as e:
self.queue.put('Thread Fail')
return
# we are done, pass the completion message
self.queue.put(self.msg)
if __name__ == '__main__':
MyQueue = queue.Queue()
MyThread = ThreadedTask(MyQueue, run_device, 'Thread Task: Run')
MyThread.start()
process_queue(MyQueue)

tkinter, button returns variable

I am trying to make a GUI text based adventure game in python. I want to be able to take text from a textinput box and store it as string variable.
I have 2 problems:
Making the python wait for the submit button to be pressed, before
processing the input and updating the game.
Getting the text variable out of the command, I would like to not
use global if possible.
Here is some of my code to better understand:
root = tk.Tk()
root.geometry('800x600+100+100')
root.title("my game")
textbox = tk.StringVar()
textboxentry = tk.Entry(root, textvariable=textbox, bd=5, width = "40", font=("times", 20))
textboxentry.pack(in_=bgImageLabel, side = "bottom")
def getInput():
textboxInput = textbox.get() #gets entry
lengthEntry = len(textbox.get())
textboxentry.delete(0,lengthEntry) #removes entry from widget
return textboxInput # I would like this return to work
submit = tk.Button(root, text ="Submit", command = (textboxInput = getInput()))
##I want the command function to use command = getInput and store the return on getInput as textboxInput. This will update the wait_variable down below, and give the inputs(textboxInput) a string to work with.
submit.pack(in_=bgImageLabel, side = "bottom")
while game == True:
root.update_idletasks()
root.update()
submit.wait_variable(textboxentry)
## I need it to wait before proceeding to this next line because i need the textboxInput from the entry widget.
actionInput, extraInput, texts = inputs(textboxInput)
Currently I can't figure a way to use command = (textboxInput = getInput), using lambda or anything else. I just want to store the return which comes off of the Entry as a string variable that can be used by the main function.
All help is appreciated!
Below code processes entry widget's text when Submit button is pressed.
import tkinter as tk
root = tk.Tk()
aVarOutside = 'asd'
def btn_cmd(obj):
#use global variable
global aVarOutside
#print its unmodified value
print("aVarOutside: " + aVarOutside)
#modify it with what's written in Entry widget
aVarOutside = obj.get()
#modify lblTextVar, which is essentially modifying Label's text as lblTextVar is its textvariable
lblTextVar.set(obj.get())
#print what's inside Entry
print("Entry: " + obj.get())
txt = tk.Entry(root)
txt.pack()
lblTextVar = tk.StringVar()
lbl = tk.Label(root, textvariable=lblTextVar)
lbl.pack()
btn = tk.Button(text="Submit", command=lambda obj = txt : btn_cmd(obj))
btn.pack()
root.mainloop()
When the button is pressed:
Value of a global variable, aVarOutside is printed.
Value of aVarOutside is modified to the value of Entry box's
(txt's) content.
Value of a textvariable used by a label (lbl) is modified. Which
means that the text of lbl is updated and can be seen on the GUI.
Finally Entry box, txt's content is printed.
I think you should use inputs() inside getInputs() and then button doesn't have to return any variables - and then you can use root.mainloop() instead of while loop.
import tkinter as tk
# --- functions ---
def inputs(text):
# do something with text
print(text)
# and return something
return 'a', 'b', 'c'
def get_input():
global action_input, extra_input, texts
text = textbox.get()
if text: # check if text is not empty
textbox.set('') # remove text from entry
#textbox_entry.delete(0, 'end') # remove text from entry
action_input, extra_input, texts = inputs(text)
# --- main ---
root = tk.Tk()
textbox = tk.StringVar()
textbox_entry = tk.Entry(root, textvariable=textbox)
textbox_entry.pack()
submit = tk.Button(root, text="Submit", command=get_input)
submit.pack()
root.mainloop()
BTW: you could better organize code
all functions before main part (root = tk.Tk())
PEP8 suggests to use lower_case_names for functions and variables (instead of CamelCaseNames)
global is not prefered method but I think it is better solution than yours.
If you don't need global then you can use classes with self.
import tkinter as tk
# --- classes ---
class Game:
def __init__(self):
self.root = tk.Tk()
self.textbox = tk.StringVar()
self.textbox_entry = tk.Entry(self.root, textvariable=self.textbox)
self.textbox_entry.pack()
self.submit = tk.Button(self.root, text="Submit", command=self.get_input)
self.submit.pack()
def run(self):
self.root.mainloop()
def inputs(self, text):
# do something with text
print(text)
# and return something
return 'a', 'b', 'c'
def get_input(self):
text = self.textbox.get()
if text: # check if text is not empty
self.textbox.set('') # remove text from entry
#textbox_entry.delete(0, 'end') # remove text from entry
self.action_input, self.extra_input, self.texts = self.inputs(text)
# --- functions ---
# empty
# --- main ---
app = Game()
app.run()

Resources