Replacing input() portions of a Python program in a GUI - python

I am pretty new to python and have been working with a program that was originally meant for the command line. As such, it uses the input() function quite a bit, especially in the middle of loops. Mostly, the original code would do something like this:
for item in list:
# some logic here
user_input - input("prompt")
# uses user_input
I've decided I want to make a GUI for it, but I also want to make it optional. I have a class called Viewer that decorates the original program, and I am struggling to figure out how to best to handle the calls to input().
My first thought was just to inject a new input function altogether so that way it looks for input in my GUI text box instead the sys.stdout. I found many Stack Overflow answers on how to print sys.stdout to a GUI element (like this), but not how to take input from one. All of my attempts either ended in freezing the GUI by creating a loop, or the program not pausing for input and simply grabbing what was in the box when the prompt was put forward.
Secondly, I tried to break apart my functions to better match the code examples for buttons. So, button 1 would trigger a function_part_1, which would go until it required an input. If the GUI flag was turned off, it would automatically go to an input function, otherwise it would return and a button press would trigger function_part_2:
def function_part_1(list):
item = list[0]
# do some logic on item 1
if GUI:
print("prompt")
return
# After this, the GUI waits for a button press, and then calls function_part_2
else:
function_input(list)
def function_input(list):
user_input = input("prompt")
function_part_2(user_input, list)
def function_part_2(user_input, list):
# uses user_input on item
list.remove(list[0])
if list:
function_part_1(list)
else:
return
However, this turned ugly really quickly. First, it broke apart many loops which would require a lot of refactoring to keep the logic in tact (Especially for the nested loops). It also requires that I pass all my local variables from function-part to function-part, which exploded the function headers. In order to have only a single input box for the user, the GUI has to have logic to know which function is executing and what function-part comes next. It made my functions harder to follow and hurt readability.
Is there a nicer way to do this?
I am using appJar for the GUI, which is based around Tkinter. I am willing to switch to pure Tkinter if that would make things easier.

The easiest way is to overwrite the input function. Here's a working example using tkinter:
import tkinter as tk
def input(prompt=''):
win= tk.Tk()
label= tk.Label(win, text=prompt)
label.pack()
userinput= tk.StringVar(win)
entry= tk.Entry(win, textvariable=userinput)
entry.pack()
# pressing the button should stop the mainloop
button= tk.Button(win, text="ok", command=win.quit)
button.pack()
# block execution until the user presses the OK button
win.mainloop()
# mainloop has ended. Read the value of the Entry, then destroy the GUI.
userinput= userinput.get()
win.destroy()
return userinput
print(input('foobar'))

Related

tkinter button only visible when a word is in a textbox python

I'm trying to create a button in Tkinter that only appears (becoming visible) when it finds the word "hello" in a textbox.
I could imagine using Threads, and Global variables but i do not know how to code it,
I've imagined something like :
import Tkinter as *
from threading import Thread
window = Tk()
active = True
def check_for_word():
global active
textbox1 = textbox.get("1,0", "end")
while active == True:
if "hello" in textbox1:
button.pack()
else:
button.pack_forget()
save_button = Button(window)
textbox = scrolledtext.ScrolledText(window)
textbox.pack()
threading = Thread (target=check_for_word)
threading.start()
window.mainloop()
this is something I would suspect to work but ends up not, the button either doesn't show at all like the code isn't even running, or the thread doesn't work properly. So am I doing something wrong, if so, can you help me, please? Thank you!
You don't need to make use of threads to do this, you can use tkinter event bindings instead.
def check_for_word():
if "hello" in textbox.get("1.0", "end"):
save_button.pack()
else:
save_button.pack_forget()
save_button = Button(window)
textbox = scrolledtext.ScrolledText(window)
textbox.bind("<KeyRelease>", lambda event:check_for_word())
textbox.pack()
To make a binding, you use widget.bind. In this case, the widget is textbox and it's binding to <KeyRelease>, which is when the user releases a key. It then calls check_for_word when a key is released. (The lambda part is to ignore the event parameter). check_for_word then does what it did before.
You have to put the textbox1 assignment inside the while loop and before the if condition, otherwise it will check the value one time before entering the loop and will keep checking always the same value.
I also want to point out that the in operator is case sensitive and return True if it find even just a substring inside the variable you are checking and not just the precise single word (but I'm not shure if this is intensional).
For the while loop you don't necessarily need a global variable, you could just use while True: if you want it to continuously check the condition (if you want the button to disappear after the user cancel the word).

Controlling flow of application using GUI interface

(Thanks in advance if you decide to answer. Sorry If I am not able to describe the content of the Application clearly.)
I am currently making Desktop Application using Tkinter Python for my gui interface. I am having a problem related to the flow of the Application.
My Application
Main Page of my application has form designed with some textboxes and checkboxes. Check button work as True or False Condition, where each check button refers to whether or not a specific function has to be executed or not.
To store the values of the check button, the dictionary is maintained with keys as LabelName and values as True/False value.
Checkbox code
f1=tk.BoolVar() #People tend to use IntVar() but i prefer BoolVar()
ttk.Label(text="func1")
ttk.Checkbutton(parent, variable=f1)
f2=tk.BoolVar()
ttk.Label(text="func2")
ttk.Checkbutton(parent, variable=f2)
-------other such CheckButtons------------------
There's a submit button in the form on pressing which all the data entered into textbox along with these check buttons. Based on true-false values, functions are called which is handled by if-else conditions.
#submit button
ttk.Button(parent,text="Submit",command=onsubmit)
###########
def onsubmit():
----------statements to read data--------------
dict['func1']=f1
dict['func2']=f2
#other statements
-----------------------------------------------
if dict['func1']:
func1()
if dict['func2']:
func2()
---other if-else conditions---
Each function is individual module which consists of either form, or frame with data or matplotlib graphs for visualization of data and to do other operations on data and graphs which are placed on root window frame.
My problem
I want the user to control the flow by giving them the next button after every function is executed and then move onto the execution of the next function based on his input of the check button. The program should wait until the User presses the next button and after they press the next button, it should execute the next function and then wait again for the next button.
One of the solution:
Using fig.waitforbuttonpress() was the solution. But I didn't find it reliable. Because even mouse click could skip the function execution. But I need to specifically assign a button through which the user can select when to proceed to the next function.
I am not sure if I understood what your code does, but you could do it something like that I guess:
next_button = ttk.Button(parent,text="Next",command=func1)
...
def func1():
#do your stuff here
next_button.configure(command=func2)
Then you would have to add the last line of code to all the functions to always reassign the button.
Another way could be:
process = 0
next_button = ttk.Button(parent,text="Next",command=next)
def next():
global process
process += 1
if process == 1:
func1()
elif process == 2:
func2()
...
elif *last_function_reached*:
process = 0

Opening a new Tkinter window while continuing to run a loop in another

I am working on a large program that opens new windows from a desktop widget. The desktop widget has a 'ticker' style label that displays a piece of text representing an iteration through a list. My problem is when I first wrote the program I called mainloop() with each new window I opened. The result was the new window and program would run as designed, but the ticker would freeze. Even upon closing the newly created window, the ticker would not restart. So I removed the mainloop() line. The result of this is the ticker continues to run and I can work within the new window, but everything is soooo laggy. I suspect this has something to do with the after() method?
Attached is a test code that I am using to try to sort this out before applying the correct code to my program. And I'm sure you can tell by reading the code, but I am self taught and an absolute newb, so please dumb down the explanations if possible. Thank so much!
from tkinter import *
def new_window():
nw = Tk()
item = Text(nw)
item.grid()
L = [1, 2, 3, 4, 5]
root = Tk()
Button(root, text = 'Open', command = new_window).grid(row = 1)
while True:
for i in L:
num = Label(root, text = i)
num.grid(row = 0)
root.after(2500)
num.update()
root.mainloop()
A tkinter application should always have exactly one instance ofTk, and you should call mainloop exactly once. If you have more than one instance the program will not likely work the way you expect. It's possible to make it work, but unless you understand exactly what is happening under the hood you should stick to this rule of thumb.
If you need more windows, create instances of Toplevel. You should not call mainloop for each extra window.
Also, you shouldn't have an infinite loop where you call after the way that you do. mainloop is already an infinite loop, you don't need another. There are several examples on this website of using after to call a function at regular intervals without creating a separate loop.

Issue with differentiating between two of the same items in a tkinter menu

The program I created allows for any letter on the keyboard the user types to be written on the turtle graphics canvas. In my program, I have also created a Python menu to which a Point object (for each letter function) is written to every time a user executes a function/draws a letter. However, because of the nature of my program, the user can also attach two of the same functions to the menu. If two of the same things get attached to the menu, for example two functions, I need a way to differentiate between them somehow. To do this, I have created a counter in another function and called that counter function in the function where the menu is written to, like so:
Counter function:
def increase():
if not hasattr(increase, "counter"):
increase.counter = 0
increase.counter += 1
Code block when menu is written to:
global loki
kli.config(state = NORMAL)
loki = ("{}".format(po.getfunction()))
increase() #<-- Counter function
undo1.add_command(label = str(increase.counter) + Point.__str__(po), command = lambda: selectundo(undo1.index(po)))
Point.__str__ is this method in the Point class:
def __str__(self):
return "({})".format(self.function)
However, I keep getting this error whenever I select something from the menu:
undo1.add_command(label = str(increase.counter) + Point.__str__(po), command = lambda: selectundo(undo1.index(po)))
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/tkinter/__init__.py", line 2782, in index
i = self.tk.call(self._w, 'index', index)
tkinter.TclError: bad menu entry index "(<function draw_O at 0x105834d90>)"
I am thinking it has something to do with the following function, which undoes the function that is selected from the menu, but I am not sure:
def selectundo(x):
# This function undoes the function selected from the menu
for ty in range(x, len(function)):
undoHandler()
update()
listen()
Although, before I concatenated str(increase.counter) to Point.__str__(po), it worked perfectly.
So, what am I doing wrong here? Any help at all is much appreciated! :)
EDIT: Just to clear up what I am trying to do and why, I am trying to differentiate between two (or more) of the same functions if they are written to the menu and I am doing this because of the selectundo function (shown above) since, for example, if the user draws two (or more) of the same letter, I need to be able to differentiate between them, because right now, when I can't, the selectundo function undoes ALL instances of that letter, NOT just the first instance of what is pressed on the menu, which is what I actually want the program to do. If what I am trying to do to accomplish the task is not possible or if there is a better way to accomplish the task, please tell be about any/the other way that I can use to accomplish the task. I hope this helps alleviate any confusion! :)

Tkinter, Entry widget, is detecting input text possible?

I have an Entry widget on a simple calculator. The user can choose to enter an equation via the keypad. I was wondering if there was a way to detect a character(from the keypad in my case) being typed into the Entry widget. So, focus is on the widget, user presses '4', it comes up on the widget... can I detect this act, for basic purposes of logging the input?
Every time you press a key inside a Tkinter window, a Tkinter.Event instance is created. All you need to do is access that instance. Here is a simple script that demonstrates just how:
from Tkinter import Tk, Entry
root = Tk()
def click(key):
# print the key that was pressed
print key.char
entry = Entry()
entry.grid()
# Bind entry to any keypress
entry.bind("<Key>", click)
root.mainloop()
key (being a Tkinter.Event instance) contains many different attributes that can be used to get almost any type of data you want on the key that was pressed. I chose to use the .char attribute here, which will have the script print what each keypress is.
Yes. There are a few different ways to do this, in fact.
You can create a StringVar, attach it to the Entry, and trace it for changes; you can bind all of the relevant events; or you can add a validation command that fires at any of several different points in the sequence. They all do slightly different things.
When a user types 4, there's a key event with just the 4 in it (which doesn't let you distinguish whether the user was adding 4 to the end, or in the middle, or replacing a whole selected word, or…), and then a modification event is fired with the old text,* and then the "key" or "all" validation function is called with the (proposed) new text, and the variable is updated with the (accepted) new text (unless the validation function returned false, in which case the invalidcommand is called instead).
I don't know which one of those you want, so let's show all of them, and you can play around with them and pick the one you want.
import Tkinter as tk
root = tk.Tk()
def validate(newtext):
print('validate: {}'.format(newtext))
return True
vcmd = root.register(validate)
def key(event):
print('key: {}'.format(event.char))
def var(*args):
print('var: {} (args {})'.format(svar.get(), args))
svar = tk.StringVar()
svar.trace('w', var)
entry = tk.Entry(root,
textvariable=svar,
validate="key", validatecommand=(vcmd, '%P'))
entry.bind('<Key>', key)
entry.pack()
root.mainloop()
The syntax for variable trace callbacks is a bit complicated, and not that well documented in Tkinter; if you want to know what the first two arguments mean, you need to read the Tcl/Tk docs, and understand how Tkinter maps your particular StringVar to the Tcl name 'PY_VAR0'… Really, it's a lot easier to just build a separate function for each variable and mode you want to trace, and ignore the args.
The syntax for validation functions is even more complicated, and a lot more flexible than I've shown. For example, you can get the inserted text (which can be more than one character, in case of a paste operation), its position, and all kinds of other things… but none of this is described anywhere in the Tkinter docs, so you will need to go the Tcl/Tk docs. The most common thing you want is the proposed new text as the argument, and for that, use (vcmd, '%P').
Anyway, you should definitely play with doing a variety of different things and see what each mechanism gives you. Move the cursor around or select part of the string before typing, paste with the keyboard and with the mouse, drag and drop the selection, hit a variety of special keys, etc.
* I'm going to ignore this step, because it's different in different versions of Tk, and not very useful anyway. In cases where you really need a modified event, it's probably better to use a Text widget and bind <<Modified>>.
If you just need to do simple things without using trace module you can try
def objchangetext(self, textwidget):
print(textwidget.get()) #print text out to terminal
text1 = tk.Entry(tk.Tk())
text1.bind("<KeyRelease>", lambda event, arg=(0): objchangetext(text1))

Categories