Constantly checking a text box in tkinter - python

I want a Text label in tkinter to constantly check if its worth some value. I would like it to be in a while loop and exit it when the values match.
My code doesn't work.
while user_input != str(ans):
master = Tk()
master.title("Math")
master.geometry('400x400')
eq = generate_equation(stage=current_stage)
ans = calc(eq)
Label(master=master,text=f"score: {score}").grid(row=0,column=2,sticky=W)
Label(master=master, text=f"{q_num}: {eq}").grid(row=0,column=0 ,sticky=W)
inputtxt = tkinter.Text(master=master,height = 5, width = 20)
inputtxt.grid()
user_input =str(inputtxt.get(1.0,"end-1c"))
mainloop()

Try this:
import tkinter as tk
def check(event:tk.Event=None) -> None:
if text.get("0.0", "end").strip() == "answer":
# You can change this to something else:
text.insert("end", "\n\nCorrect")
text.config(state="disabled", bg="grey70")
root = tk.Tk()
text = tk.Text(root)
# Each time the user releases a key call `check`
text.bind("<KeyRelease>", check)
text.pack()
root.mainloop()
It binds to each KeyRelease and checks if the text in the text box is equal to "answer". If it is, it displays "Correct" and locks the text box, but you can change that to anything you like.
Please note that this is the simplest answer and doesn't account for things like your code adding things to the text box. For that you will need more sophisticated code like this.

Related

Make Tkinter run a command without clicking or pressing a button

I am making a simple tkinter popup where you can type a message.
In the textbox itself, I inserted the text "Type your message here" with a grey colour and when clicked, the inserted text is deleted so the user can type in their own message. In addition, the colour of the text typed by the user is set to black.
However, when I was testing I realised that this will only happen if they click the textbox with a mouse button. My question is, is there a way for tkinter to automatically run a command when a condition is changed? For example, if the textbox is empty, the font colour should be set to black.
I tried putting if-statements in the tk.mainloop, but sadly that didn't work.
Any ideas?
this is my (hopefully) simplified version of the code:
from tkinter import *
def changecolor(event):
if textbox.get("1.0", "end-1c") == "Type your message here":
textbox.delete("1.0", "end")
textbox.config(fg='black')
root = Tk()
canvas = Canvas(root, height=400, width=600)
canvas.pack()
textbox = Text(canvas, font=40, fg="grey")
textbox.insert(1.0, "Type your message here")
textbox.bind("<Button-1>", changecolor)
textbox.pack()
root.mainloop()
~finally found out how to format code here.
Take a look at this class I created that does similar to what your code does, do try it on.
from tkinter import *
class PlaceholderText(Text):
def __init__(self,master,placeholder,placeholdercolor='black',fg='grey',**kwargs):
Text.__init__(self,master,**kwargs) #init the text widget
self.placeholder = placeholder
self.fgcolor = fg
self.placeholdercolor = placeholdercolor
self.has_placeholder = False #make flag
self.add() #run the function to add placeholder
self.bind('<FocusIn>',self.clear) #binding to focusin and not button-1
self.bind('<FocusOut>',self.add) #function wil get triggered when widget loses focus
def clear(self,event=None):
if self.get('1.0','end-1c') == self.placeholder and self.has_placeholder: #condition to clear a placeholder
self.delete('1.0','end-1c') #delete the placeholder
self.config(fg=self.fgcolor) #change the color
self.has_placeholder = False #set flag to flase
def add(self,event=None):
if self.get('1.0','end-1c') == '' and not self.has_placeholder: #condition to add placeholder
self.insert('1.0',self.placeholder) #add placeholder
self.has_placeholder = True #set flag to true
self.config(fg=self.placeholdercolor) #change text color to what you specify?
def ret(self,index1,index2):
if self.get('1.0','end-1c') == self.placeholder and self.has_placeholder: #gives none if there is nothing in the widget
return 'None'
else:
return self.get(index1,index2) #else gives the text
root = Tk()
pl = PlaceholderText(root,placeholder='Type something here...')
pl.pack()
e = Entry(root) #dummy widget to switch focus and check
e.pack(padx=10,pady=10)
root.mainloop()
I've explained to through the comments. But keep in mind its not the best of classes yet, you have to do add a lot more methods in to make it more efficient.
Just if your wondering on how to do this without classes, then:
from tkinter import *
has_placeholder = False #make flag
placeholder = 'Type Something Here...' #the text to be inserted
def clear(event=None):
global has_placeholder
if a.get('1.0','end-1c') == placeholder and has_placeholder: #condition to clear a placeholder
a.delete('1.0','end-1c') #delete the placeholder
a.config(fg='grey') #change the color
has_placeholder = False #set flag to flase
def add(event=None):
global has_placeholder
if a.get('1.0','end-1c') == '' and not has_placeholder: #condition to add placeholder
a.insert('1.0',placeholder) #add placeholder
has_placeholder = True #set flag to true
a.config(fg='black') #change text color to normal
root = Tk()
a = Text(root)
a.pack()
add() #add the placeholder initially
a.bind('<FocusIn>',clear) #binding to focus and not button-1
a.bind('<FocusOut>',add)
e = Entry(root) #dummy widget to show focus loss
e.pack()
root.mainloop()
Why not to use classes if the latter method is more easier? This is not reusable, say you want to add one more Text widget, that cannot have such property, while using a custom class with custom class you can have as many as text widgets with same properties you like.
Do let me know if any doubts.
You can simply add a <Key> binding to your text widget and use your changecolor function to determine what state your textbox is in.
#Give a hoot. Don't pollute. :D
import tkinter as tk
txtmsg = "Type your message here"
def changecolor(event):
text = textbox.get("1.0", "end-1c")
#customize accordingly
if text:
if text == txtmsg:
print("text is txtmsg")
else:
print("text is user text")
else:
print("text is empty")
#FYI:
#whether this was a button press or key press DOES NOT have string equality
#if you need to create button vs key conditions
#use tk.EventType.ButtonPress and tk.EventType.KeyPress
#or learn the .value and compare that
print(event.type, type(event.type), event.type.value)
root = tk.Tk()
textbox = tk.Text(root, font=40, fg="grey")
textbox.insert(1.0, txtmsg)
textbox.pack()
#add events
for ev in ['<Key>', '<1>']:
textbox.bind(ev, changecolor)
root.mainloop()

Python/Tkinter dynamically update label

I am currently trying to make a GUI to an existing python program using Tkinter. The program gives the user two options from which the user must choose to either accept or decline. Before using Tkinter the options were placed in the terminal and awaited for a raw_input. (y/n). How can I make this so the canvas text updates with the new data and awaits for the users button click?
To make my question more specific: How can I run another programs code while the Tkinter mainloop is running and make these two interact?
Example code below.
from Tkinter import *
root = Tk()
root.resizable(width=False, height=False)
root.geometry('{}x{}'.format(500,550))
root.wm_title("Tkinter test")
BtnFrame = Frame (root)
BtnFrame.pack(side = BOTTOM)
BtnFrame.place(y=450, x=20)
canvas_1 = Canvas(root, width = "200", height ="300")
canvas_2 = Canvas(root, width = "250", height ="300")
canvas_1.pack(side = LEFT)
canvas_2.pack(side = RIGHT)
textfield_1 = canvas_1.create_text(100,50)
textfield_2 = canvas_2.create_text(100,50,)
def update_textfiel_1(text):
global textfield_1
canvas_1.delete(textfield_1)
textfield = canvas.create_text(100,50,text = text)
def update_textfiel_2(text):
global textfield_2
canvas_2.delete(textfield_2)
textfield1 = canvas1.create_text(100,50,text = text)
Accept = Button(BtnFrame, text="Accept", width=25)
Decline = Button(BtnFrame, text="Decline", width=25)
Accept.pack(side = LEFT)
Decline.pack(side = RIGHT)
root.mainloop()
First off you have some inconsistent variable names in your update_textfiel functions, you can greatly simplify it by using .itemconfigure (documentation for methods on canvas widget)
def update_textfiel_1(new_text):
canvas_1.itemconfigure(textfield_1, text=new_text)
def update_textfiel_2(new_text):
canvas_2.itemconfigure(textfield_2, text=new_text)
If I understand correctly you want a way to have a function that will simply wait for the user to press one of the buttons and then return the result, this is very easy with tkMessageBox:
question = """Do you accept {}?
if you say no you will instead get {}"""
#this message can GREATLY be improved
# But I really don't understand what you are using it for...
def user_confirmation(name1, name2):
response = tkMessageBox.askyesno("Accept or Decline",question.format(name1,name2))
print(response)
if response: # == True
return name1
else:
return name2
I have not yet found a way to make a blocking function that works with the window you have currently...

Trying to print the output from my function inside my GUI window

Im trying to make a little program that endlessly prints out numbers inside GUI window, I can not find a way to print the out put of the function in a text box inside the GUI window instead of the python shell, please help, here is my code so far...
import sys
from tkinter import *
root = Tk()
def number(event):
x = 420
while True:
x +=420
print(x^70)
button_1 = Button(root, text="Start...")
button_1.bind("<Button-1>", number)
button_1.pack()
root.mainloop()
Thanks Harvey
You'll find it hard to constantly insert a value into a widget. The widget does not update with each insert. You can think of it has having a temporary variable for it. It can be accessed during the loop (as shown with print). However you'll notice that the widget itself doesn't update until the loop is over. So if you have while True then your widget will never update, and so you won't have the numbers streaming into the widget.
import sys
from tkinter import *
root = Tk()
def number():
x = 420
while x < 8400: # Limit for example
x +=420
textbox.insert(END, str(x^70)+'\n')
print(textbox.get(1.0, END)) # Print current contents
button_1 = Button(root, text="Start...", command=number) #Changed bind to command, bind is not really needed with a button
button_1.pack()
textbox = Text(root)
textbox.pack()
root.mainloop()

How to set the text/value/content of an `Entry` widget using a button in tkinter

I am trying to set the text of an Entry widget using a button in a GUI using the tkinter module.
This GUI is to help me classify thousands of words into five categories. Each of the categories has a button. I was hoping that using a button would significantly speed me up and I want to double check the words every time otherwise I would just use the button and have the GUI process the current word and bring the next word.
The command buttons for some reason are not behaving like I want them to. This is an example:
import tkinter as tk
from tkinter import ttk
win = tk.Tk()
v = tk.StringVar()
def setText(word):
v.set(word)
a = ttk.Button(win, text="plant", command=setText("plant"))
a.pack()
b = ttk.Button(win, text="animal", command=setText("animal"))
b.pack()
c = ttk.Entry(win, textvariable=v)
c.pack()
win.mainloop()
So far, when I am able to compile, the click does nothing.
You might want to use insert method. You can find the documentation for the Tkinter Entry Widget here.
This script inserts a text into Entry. The inserted text can be changed in command parameter of the Button.
from tkinter import *
def set_text(text):
e.delete(0,END)
e.insert(0,text)
return
win = Tk()
e = Entry(win,width=10)
e.pack()
b1 = Button(win,text="animal",command=lambda:set_text("animal"))
b1.pack()
b2 = Button(win,text="plant",command=lambda:set_text("plant"))
b2.pack()
win.mainloop()
If you use a "text variable" tk.StringVar(), you can just set() that.
No need to use the Entry delete and insert. Moreover, those functions don't work when the Entry is disabled or readonly! The text variable method, however, does work under those conditions as well.
import Tkinter as tk
...
entry_text = tk.StringVar()
entry = tk.Entry( master, textvariable=entry_text )
entry_text.set( "Hello World" )
You can choose between the following two methods to set the text of an Entry widget. For the examples, assume imported library import tkinter as tk and root window root = tk.Tk().
Method A: Use delete and insert
Widget Entry provides methods delete and insert which can be used to set its text to a new value. First, you'll have to remove any former, old text from Entry with delete which needs the positions where to start and end the deletion. Since we want to remove the full old text, we start at 0 and end at wherever the end currently is. We can access that value via END. Afterwards the Entry is empty and we can insert new_text at position 0.
entry = tk.Entry(root)
new_text = "Example text"
entry.delete(0, tk.END)
entry.insert(0, new_text)
Method B: Use StringVar
You have to create a new StringVar object called entry_text in the example. Also, your Entry widget has to be created with keyword argument textvariable. Afterwards, every time you change entry_text with set, the text will automatically show up in the Entry widget.
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
new_text = "Example text"
entry_text.set(new_text)
Complete working example which contains both methods to set the text via Button:
This window
is generated by the following complete working example:
import tkinter as tk
def button_1_click():
# define new text (you can modify this to your needs!)
new_text = "Button 1 clicked!"
# delete content from position 0 to end
entry.delete(0, tk.END)
# insert new_text at position 0
entry.insert(0, new_text)
def button_2_click():
# define new text (you can modify this to your needs!)
new_text = "Button 2 clicked!"
# set connected text variable to new_text
entry_text.set(new_text)
root = tk.Tk()
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
button_1 = tk.Button(root, text="Button 1", command=button_1_click)
button_2 = tk.Button(root, text="Button 2", command=button_2_click)
entry.pack(side=tk.TOP)
button_1.pack(side=tk.LEFT)
button_2.pack(side=tk.LEFT)
root.mainloop()
Your problem is that when you do this:
a = Button(win, text="plant", command=setText("plant"))
it tries to evaluate what to set for the command. So when instantiating the Button object, it actually calls setText("plant"). This is wrong, because you don't want to call the setText method yet. Then it takes the return value of this call (which is None), and sets that to the command of the button. That's why clicking the button does nothing, because there is no command set for it.
If you do as Milan Skála suggested and use a lambda expression instead, then your code will work (assuming you fix the indentation and the parentheses).
Instead of command=setText("plant"), which actually calls the function, you can set command=lambda:setText("plant") which specifies something which will call the function later, when you want to call it.
If you don't like lambdas, another (slightly more cumbersome) way would be to define a pair of functions to do what you want:
def set_to_plant():
set_text("plant")
def set_to_animal():
set_text("animal")
and then you can use command=set_to_plant and command=set_to_animal - these will evaluate to the corresponding functions, but are definitely not the same as command=set_to_plant() which would of course evaluate to None again.
One way would be to inherit a new class,EntryWithSet, and defining set method that makes use of delete and insert methods of the Entry class objects:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
class EntryWithSet(tk.Entry):
"""
A subclass to Entry that has a set method for setting its text to
a given string, much like a Variable class.
"""
def __init__(self, master, *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
def set(self, text_string):
"""
Sets the object's text to text_string.
"""
self.delete('0', 'end')
self.insert('0', text_string)
def on_button_click():
import random, string
rand_str = ''.join(random.choice(string.ascii_letters) for _ in range(19))
entry.set(rand_str)
if __name__ == '__main__':
root = tk.Tk()
entry = EntryWithSet(root)
entry.pack()
tk.Button(root, text="Set", command=on_button_click).pack()
tk.mainloop()
e= StringVar()
def fileDialog():
filename = filedialog.askopenfilename(initialdir = "/",title = "Select A
File",filetype = (("jpeg","*.jpg"),("png","*.png"),("All Files","*.*")))
e.set(filename)
la = Entry(self,textvariable = e,width = 30).place(x=230,y=330)
butt=Button(self,text="Browse",width=7,command=fileDialog).place(x=430,y=328)

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