How to have script wait until user interacts in Tkinter? - python

I'm learning Python on my own and now I'm trying to learn some GUI with Tkinter. If anyone can help, I'm having trouble having the program show a greeting message and then offering options that should be picked by clicking a button. Actually, I think I've sorted out how to build it, but there's a key piece missing: I don't know how to have the program wait until the user has interacted by clicking one of the buttons.
Let's say, for instance, I create a file with a 'Window' Class (for the interface) and some functions along (Window.py):
import time
from tkinter import *
# Here I'll set each one of the the buttons' commands.
# I've referenced it before to avoid triggering a reference problem, but when I tried, I actually broke the code in three parts and used imports to link all of them.
# For simplicity's sake, however, I'll present everything in a single file in this question.
Choice = ''
def Choose1():
Choice = 1
def Choose2():
Choice = 2
def Choose3():
Choice = 3
# Here I create the object 'Window':
class Window:
def __init__(self):
self.Window = Tk()
self.Window.title = 'Interact'
self.Window.minsize = (500, 300)
# Here I'll add a label to display the messages that I want to show the user:
self.Text = Label(self.Window, text = '')
self.Text.pack()
# Aqui uma série de botões:
self.B1 = Button(self.Window, text = 'Option 1', command = Choose1)
self.B1.pack()
self.B2 = Button(self.Window, text = 'Option 2', command = Choose2)
self.B2.pack()
self.B3 = Button(self.Window, text = 'Option 3', command = Choose3)
self.B3.pack()
# Here I'll create an instance of the 'Window' object:
Example = Window()
# Here I'll create a function so that certain messages will be displayed to the user:
def Say(X):
Example.Text.configure(text = X)
Example.Text.update()
time.sleep(3) # Please ignore this. I inserted this delay so there's time for the user to read the message. I actualy have a better way to do it, but to keep it simple, let's leave it like this.
# Finally, the main part of the program:
Say('Welcome!')
Say('Which option would you like to choose?')
WaitInput() # I haven't figured out how this function would work, and that's my issue. I'd like the program to wait for the option to be chosen and only then print the following message:
Say('You've chosen option {}!'.format(Choose))
Text.mainloop()
Can anybody by any chance tell me how can I create this 'WaitInput()' function, or if something of the sort already exists in Python?
Appreciate it!

In all GUIs (in all languages) you use button to wait for input and execute some function when there are input data.
In tkinter you have to use lambda to assing function with arguments to button
self.b1 = tk.Button(self.window, text='Option 1', command=lambda:self.choose('1'))
I used after() to change text with delay and also to add buttons with delay - I used callback in say_later() to execute it with delay.
import time
import tkinter as tk
# --- classes ---
class Window:
def __init__(self):
self.window = tk.Tk()
self.window.title = 'Interact'
self.window.geometry('500x300')
#self.window.minsize = (500, 300)
self.text = tk.Label(self.window, text='')
self.text.pack()
def add_buttons(self):
self.b1 = tk.Button(self.window, text='Option 1', command=lambda:self.choose('1'))
self.b1.pack()
self.b2 = tk.Button(self.window, text='Option 2', command=lambda:self.choose('2'))
self.b2.pack()
self.b3 = tk.Button(self.window, text='Option 3', command=lambda:self.choose('3'))
self.b3.pack()
def say(self, message, callback=None):
self.text.configure(text=message)
if callback:
callback()
def say_later(self, delay, message, callback=None):
self.window.after(delay, lambda:self.say(message, callback))
def choose(self, value):
self.say("You've chosen option {}!".format(value))
# --- functions ---
# empty
# --- main ---
example = Window()
example.say('Welcome!')
example.say_later(3000, 'Which option would you like to choose?', example.add_buttons)
example.window.mainloop()

Related

PYTHON TKINTER > e = Entry() > e.bind('<ENTER>', function)

I am not allowed to add images yet to question posts.
Question below:
My app currently uses a window that is coded in a class.
My ultimate goal is to press enter while entering letters and numbers into an entry widget and press enter, then the function would update text that correlates to a label in my main window.
Detailed description below:
I cannot figure out how to create and entry and then bind the enter key so that when I run my app, I can click in the entry, type a value and press enter.
I see plenty of button references and I can get the button to work, but I am trying to learn how to do things and do not want to rely on buttons in this instance.
I saw in some other posts that if you call .get with an entry object, that the python code will just execute it and move on. I tested with a print statement in the function I want to call upon pressing enter, and the print statement appeared in the terminal before I typed anything in the entry widget. I then tried to type and press enter, and nothing would occur.
Should I abandon binding the ENTER key and stick with buttons in tkinter as a rule, or is there a proper way to do this? In my code example, you will see up_R is the function I am trying to execute when pressing Enter. If I use up_R(), it executes immediately. If I use up_R, then I get a TCL Error.
Specific Partial code located below:
def up_R():
print('Makes it here')
self.R.update_disp(self.e.get())
self.e.bind('<ENTER>',up_R)
The full code is below if required for assistance:
#NOAA SPACE WEATHER CONDITIONS
from tkinter import *
class window:
def __init__(self):
#main window
self.window = Tk()
self.window.title('NOAA SPACE WEATHER CONDITIONS')
self.window.geometry('800x600')
#window organization
self.window.grid_rowconfigure(0, weight = 1)
self.window.grid_rowconfigure(1, weight = 1)
self.window.grid_columnconfigure(0, weight = 1)
self.window.grid_columnconfigure(1, weight = 1)
#temp entry frame
self.e = Entry(self.window)
self.e.grid(row = 1, column = 0, sticky=N)
self.e.insert(END, 'R entry')
#init class R
self.R = R()
#init class S
self.S = S()
#init class g
self.G = G()
#frame for RSG
self.frame = Frame(self.window)
self.frame.grid(row = 0, column = 0, columnspan = 2, padx=10, pady=10)
#disp class R
self.rf = Frame(self.frame, highlightbackground='black', highlightcolor='black', highlightthickness=1)
self.rf.pack(side = LEFT)
self.rl = Label(self.rf, text = self.R.dkey, bg='#caf57a')
self.rl.pack(side=TOP)
self.rl_lower = Label(self.rf, text= self.R.tile_text, bg='#caf57a')
self.rl.pack(side=BOTTOM)
#Value update methods
# self.R.update_disp(self.e.get())
# #action
def up_R():
print('Makes it here')
self.R.update_disp(self.e.get())
self.e.bind('<ENTER>',up_R())
#main window call - goes at end of class
self.window.mainloop()
class R:
def __init__(self):
d = {'R':'None','R1':'Minor','R2':'Moderate','R3':'Strong','R4':'Severe','R5':'Extreme'}
self.dkey = 'R'
self.tile_text = d[self.dkey]
print(d[self.dkey])
def update_disp(self, dkey):
self.dkey = dkey
class S:
d = {'S1':'Minor','S2':'Moderate','S3':'Strong','S4':'Severe','S5':'Extreme'}
pass
class G:
d = {'G1':'Minor','G2':'Moderate','G3':'Strong','G4':'Severe','G5':'Extreme'}
pass
t = window()
The ENTER should be changed with Return, and the function should accept an event
Also, don't forget in a 'class' to use self in the method and self.method to call it.
def up_R(self, event):
print('Makes it here')
self.R.update_disp(self.e.get())
self.rl.config(text=self.R.dkey)
self.e.bind('<Return>', self.up_R)

MessageBox pause - Python

i'm writing python script and in it i have users GUI with button of delete account. After the user press the delete account button there is a popup messagebox (i use tkinter messagebox but other options is good as well) that ask him for Approval for the task. I would like to add option to make a some seconds pause that the user will must wait few seconds bedore clicking OK.
If you know a way for Realize this possibility (not Necessarily tkinter) i really like to know it.
Thanks for all helpers.
By using the base class Dialog of tkinter.simpledialog module we can create any custom dialog boxes.
Here's how I did it.
from tkinter import *
import tkinter.simpledialog as sd
class WaitAlert(sd.Dialog):
"""An alert which will wait for a given time before user can interact.
Args:
parent: Takes the parent window instance.
title (str): Main heading of the alert.
message (str): Information to display.
pause (int): Time till inactive. (in seconds)
show_timer (boolean): Shows countdown."""
def __init__(self, parent, title=None, message=None, pause=None, show_timer=False):
self.message = message or ''
self.pause = pause
self.show_timer = show_timer
super().__init__(parent, title=title)
def body(self, master):
# For macOS, we can use the below command to achieve a window similar to an alert.
# Comment the below line if you are on windows.
self.tk.call("::tk::unsupported::MacWindowStyle", "style", self._w, "moveableAlert")
Label(master, text=self.message).pack()
def _timer(self, count, b1, b2):
"Timer function."
if count > 0:
if self.show_timer: b1['text'] = str(count)
self.after(1000, self._timer, count-1, b1, b2)
else:
if self.show_timer: b1['text'] = "OK"
b1['state'] = 'normal'
b2['state'] = 'normal'
def buttonbox(self):
box = Frame(self)
b1 = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE, state='disabled')
b1.pack(side=LEFT, padx=5, pady=5)
b2 = Button(box, text="Cancel", width=10, command=self.cancel, state='disabled')
b2.pack(side=LEFT, padx=5, pady=5)
if self.pause is not None:
self._timer(self.pause, b1, b2)
self.bind("<Return>", self.ok)
self.bind("<Escape>", self.cancel)
box.pack()
def apply(self):
self.result = True
return super().apply()
Now you can either save this class in a separate python file and use it by importing it. For example, I saved it as tkDialog.py and then import it in your main file (from tkDialog import WaitAlert) or you can keep it at the beginning of your main file.
Here is a small example on how to use it.
from tkinter import *
from tkDialog import WaitAlert
root = Tk()
# `wm_withdraw()` will hide the window from the screen.
root.wm_withdraw()
popup = WaitAlert(parent=root,
title='Alert!',
message='Do you want to delete this account?',
pause=5, # pauses for 5 secs.
show_timer=True) # show countdown.
print(popup.result) # get the result.
# If the user clicks "OK" the result will return "True" else return "None".
Hope this helped you.

Allow user to change default text in tkinter entry widget.

I'm writing a python script that requires the user to enter the name of a folder. For most cases, the default will suffice, but I want an entry box to appear that allows the user to over-ride the default. Here's what I have:
from Tkinter import *
import time
def main():
#some stuff
def getFolderName():
master = Tk()
folderName = Entry(master)
folderName.pack()
folderName.insert(END, 'dat' + time.strftime('%m%d%Y'))
folderName.focus_set()
createDirectoryName = folderName.get()
def callback():
global createDirectoryName
createDirectoryName = folderName.get()
return
b = Button(master, text="OK and Close", width=10, command=callback)
b.pack()
mainloop()
return createDirectoryName
getFolderName()
#other stuff happens....
return
if __name__ == '__main__':
main()
I know next to nothing about tkInter and have 2 questions.
Is over-riding the default entry using global createDirectoryName within the callback function the best way to do this?
How can I make the button close the window when you press it.
I've tried
def callback():
global createDirectoryName
createDirectoryName = folderName.get()
master.destroy
but that simply destroys the window upon running the script.
I don't know how experienced are you in Tkinter, but I suggest you use classes.
try:
from tkinter import * #3.x
except:
from Tkinter import * #2.x
class anynamehere(Tk): #you can make the class inherit from Tk directly,
def __init__(self): #__init__ is a special methoed that gets called anytime the class does
Tk.__init__(self) #it has to be called __init__
#further code here e.g.
self.frame = Frame()
self.frame.pack()
self.makeUI()
self.number = 0 # this will work in the class anywhere so you don't need global all the time
def makeUI(self):
#code to make the UI
self.number = 1 # no need for global
#answer to question No.2
Button(frame, command = self.destroy).pack()
anyname = anynamehere() #remember it alredy has Tk
anyname.mainloop()
Also why do you want to override the deafult Entry behavior ?
The solution would be to make another button and bind a command to it like this
self.enteredtext = StringVar()
self.entry = Entry(frame, textvariable = self.enteredtext)
self.entry.pack()
self.button = Button(frame, text = "Submit", command = self.getfolder, #someother options, check tkitner documentation for full list)
self.button.pack()
def getfolder(self): #make the UI in one method, command in other I suggest
text = self.enteredtext.get()
#text now has whats been entered to the entry, do what you need to with it

Creating a popup message box with an Entry field

I want to create a popup message box which prompts user to enter an input. I have this method inside a class. I am basing my code on this guide by java2s.
class MyDialog:
def __init__(self, parent):
top = self.top = Toplevel(parent)
Label(top, text="Value").pack()
self.e = Entry(top)
self.e.pack(padx=5)
b = Button(top, text="OK", command=self.ok)
b.pack(pady=5)
def ok(self):
print "value is", self.e.get()
self.top.destroy()
root = Tk()
d = MyDialog(root)
root.wait_window(d.top)
But in this, top = self.top = Toplevel(parent) doesn't work for me.
I have a mockup of what I am trying to accomplish.
My program structure looks something like this:
class MainUI:
def__int__(self):
...
self.initUI()
def initUI(self):
.......
Popup = Button(self, text="Enter Value", command=self.showPopup)
def showPopup(self):
#create the popup with an Entry here
How can I create a message box in Python which accepts user input?
I'm a little confused about your two different blocks of code. Just addressing the first block of code, nothing happens because you never enter the mainloop. To do that, you need to call root.mainloop(). The typical way of doing this is to add a button to root widget and bind a callback function to the Button (which includes d=MyDialog() and root.wait_window(d.top))
Here's some basic code which I hope does what you want ...
from Tkinter import *
import sys
class popupWindow(object):
def __init__(self,master):
top=self.top=Toplevel(master)
self.l=Label(top,text="Hello World")
self.l.pack()
self.e=Entry(top)
self.e.pack()
self.b=Button(top,text='Ok',command=self.cleanup)
self.b.pack()
def cleanup(self):
self.value=self.e.get()
self.top.destroy()
class mainWindow(object):
def __init__(self,master):
self.master=master
self.b=Button(master,text="click me!",command=self.popup)
self.b.pack()
self.b2=Button(master,text="print value",command=lambda: sys.stdout.write(self.entryValue()+'\n'))
self.b2.pack()
def popup(self):
self.w=popupWindow(self.master)
self.b["state"] = "disabled"
self.master.wait_window(self.w.top)
self.b["state"] = "normal"
def entryValue(self):
return self.w.value
if __name__ == "__main__":
root=Tk()
m=mainWindow(root)
root.mainloop()
I get the value from the popupWindow and use it in the main program (take a look at the lambda function associated with b2).
Main window:
"Click me" window:
Main window while "click me" is open:
import tkinter as tk
from tkinter import simpledialog
ROOT = tk.Tk()
ROOT.withdraw()
# the input dialog
USER_INP = simpledialog.askstring(title="Test",
prompt="What's your Name?:")
# check it out
print("Hello", USER_INP)
Enjoy ...
I did it in Tkinter without any classes. I created a function that starts a new window.
popup.Tk()
popup.mainloop()
In that window there is an Entry field from where I get the text with a variable which value is: entry.get()
Then you can use that variable for whatever you need and it will take the text from that Entry field.
I just tried this:
def get_me():
s = simpledialog.askstring("input string", "please input your added text")
Source: https://www.youtube.com/watch?v=43vzP1FyAF8

Python: Implementing a series of functions with each one calling the next

programming isn't my field, but I'm trying to learn.
I've been writing a program that works something like this:
from Tkinter import *
root=Tk()
def Secondwindow():
firstframe.destroy()
secondframe = Frame(root)
secondframe.pack()
secondcontent = Label(secondframe, text = 'second window content').pack()
def Thirdwindow():
secondframe.destroy()
thirdframe = Frame(root)
thirdframe.pack()
thirdcontent = Label(thirdframe, text = 'third window content').pack()
def Fourthwindow():
thirdframe.destroy()
fourthframe = Frame(root)
fourthframe.pack()
fourthcontent = Label(fourthframe, text = 'fourth window content').pack()
thirdbutton = Button(thirdframe, text = 'Next ->', command = Fourthwindow).pack()
secondbutton = Button(secondframe, text = 'Next ->', command = Thirdwindow).pack()
firstframe = Frame(root)
firstframe.pack()
firstcontent = Label(firstframe, text = 'first window content').pack()
firstbutton = Button(firstframe, text = 'Next ->', command = Secondwindow).pack()
root.mainloop()
Now, this works perfectly, but as my program gets larger and more complicated I am starting to see that this is neither elegant nor easy to maintain. I would like to simply write each function in (more or less) sequence, but that causes namerrors when the program reads a reference to a function that hasn't been defined yet (it seems like the program shouldn't worry about it until it has to run the function, by which time it would have already seen the function definition, but oh well).
What is the simplest way to have this functionality (functions called from within functions) without having to stick the next function definition in the middle of the first function definition? Thanks in advance!
I un-nested the functions to see what the error was. The problem you have is that the functions try to access variables defined in the scope of another function. That won't work. You either have to nest functions so that their scopes overlap, as you did -- which is awkward -- or you have to use global variables -- which is less awkward, but still awkward -- or you have to pass variable names from function to function.
However, because you're using callbacks here -- which are quite advanced! -- executing the third option is more complicated. If you really want to get this working, I would suggest an object-oriented approach. But frankly I would suggest starting with something simpler than this for a beginning programmer.
The most important thing is that you get used to scoping rules. That, at least, I can explain with your code. Here's an explanation of the NameErrors you were getting.
def Secondwindow():
firstframe.destroy()
secondframe = Frame(root)
secondframe.pack()
secondcontent = Label(secondframe, text = 'second window content').pack()
secondbutton = Button(secondframe, text = 'Next ->', command = Thirdwindow).pack()
def Thirdwindow():
secondframe.destroy()
thirdframe = Frame(root)
thirdframe.pack()
thirdcontent = Label(thirdframe, text = 'third window content').pack()
thirdbutton = Button(thirdframe, text = 'Next ->', command = Fourthwindow).pack()
These two functions look like they do almost the same thing. But they don't! Here's why:
def Secondwindow():
firstframe.destroy()
This line refers to firstframe, which was defined in the global scope (i.e. at the 'lowest level' of the program. That means it can be accessed from anywhere. So you're ok here.
secondframe = Frame(root)
secondframe.pack()
secondcontent = Label(secondframe, text = 'second window content').pack()
secondbutton = Button(secondframe, text = 'Next ->', command = Thirdwindow).pack()
These variables are all defined within the scope of Secondwindow. That means they only exist within Secondwindow. Once you leave Secondwindow, they cease to exist. There are good reasons for this!
def Thirdwindow():
secondframe.destroy()
Now you run into your problem. This tries to access secondframe, but secondframe is only defined within Secondwindow. So you get a NameError.
thirdframe = Frame(root)
thirdframe.pack()
thirdcontent = Label(thirdframe, text = 'third window content').pack()
thirdbutton = Button(thirdframe, text = 'Next ->', command = Fourthwindow).pack()
Again, these are all defined only within the scope of ThirdWindow.
Now, I can't explain everything you need to know to make this work, but here's a basic hint. You can create a global variable within a function's namespace by saying
global secondframe
secondframe = Frame(root)
Normally python assumes that variables defined in a function are local variables, so you have to tell it otherwise. That's what global secondframe does. Now you really shouldn't do this very often, because as the global scope fills up with more and more variables, it becomes harder and harder to work with them. Functions create smaller scopes (or 'namespaces' as they're called in some contexts) so that you don't have to keep track of all the names (to make sure you don't use the same name in two places, or make other even more disastrous mistakes).
Normally, to avoid creating a global variable, you would have each function return the frame it defines by calling return secondframe. Then you could add a function argument to each function containing the previous frame, as in def Thirdwindow(secondframe). But because you're using callbacks to call Secondwindow, etc., this method gets knotty. Here's some code that works around the problem by using lambda statements.
from Tkinter import *
root=Tk()
def Secondwindow(firstframe):
firstframe.destroy()
secondframe = Frame(root)
secondframe.pack()
secondcontent = Label(secondframe, text = 'second window content').pack()
secondbutton = Button(secondframe, text = 'Next ->', command = lambda: Thirdwindow(secondframe)).pack()
def Thirdwindow(secondframe):
secondframe.destroy()
thirdframe = Frame(root)
thirdframe.pack()
thirdcontent = Label(thirdframe, text = 'third window content').pack()
thirdbutton = Button(thirdframe, text = 'Next ->', command = lambda: Fourthwindow(thirdframe)).pack()
def Fourthwindow(thirdframe):
thirdframe.destroy()
fourthframe = Frame(root)
fourthframe.pack()
fourthcontent = Label(fourthframe, text = 'fourth window content').pack()
firstframe = Frame(root)
firstframe.pack()
firstcontent = Label(firstframe, text = 'first window content').pack()
firstbutton = Button(firstframe, text = 'Next ->', command = lambda: Secondwindow(firstframe)).pack()
root.mainloop()
But the best way to fix this is to use object-oriented code. Unfortunately that's just too complex a topic to get into; it would just add more verbiage to an already long post. I honestly think you should spend some time getting used to functions and scoping first.
That said, I found a moment to fiddle with an object-oriented variation. Here it is:
from Tkinter import *
root=Tk()
class FrameRepeater(object):
def __init__(self, start=0, end=4):
self.frame = None
self.number = start
self.end = end
def new_frame(self):
if self.frame:
self.frame.destroy()
self.frame = Frame(root)
self.frame.pack()
self.content = Label(self.frame, text = 'window ' + str(self.number) + ' content')
self.content.pack()
self.button = Button(self.frame, text = 'Next ->', command = self.replace)
self.button.pack()
self.number += 1
def replace(self):
if self.number < self.end:
self.new_frame()
elif self.number >= self.end:
self.content.config(text='Press button again to quit')
self.button.config(command=self.quit)
def quit(self):
self.frame.destroy()
root.destroy()
exit()
FrameRepeater().new_frame()
root.mainloop()
A couple of things to note. First, in those lines that read like this, there's a subtle error:
thirdcontent = Label(thirdframe, text = 'third window content').pack()
You were storing None in thirdcontent, because the pack() method has no return value. If you want to preserve a reference to the Label, you have to save the reference first, then pack() it separately, as I did in new_frame above.
Second, as you can see from my replace method, you don't actually have to destroy the frame to change the text of the label or the button command! The above still destroys the first three frames just to show how it would work.
Hope this gets you started! Good luck.
You can add a parent variable to each function, as that is more or less the only dynamic part of your recursion:
def RecursiveWindow(parent):
parent.destroy()
frame = Frame(root)
frame.pack()
framContent = Label(frame, text = 'second window content').pack()
if foo: # This won't go on forever, will it?
RecursiveWindow(self)
It looks like you're coding an application with frames and a forward button, like a Windows installer or a slideshow.
Instead of having many frames, each differing only by the text they contain, why not just have one master frame object and the text separate? I don't use Tk for my GUIs, but here's what I mean (might work):
from Tkinter import *
slides = ['Text one', 'Text two', 'Text three', 'cow']
number = 0
root = Tk()
frame = Frame(root).pack()
button = Button(frame, text = 'Next ->', command = NextFrame).pack()
def NextFrame(number):
frameContent = Label(frame, text = slides[number]).pack()
number += 1
If you can copy&paste code you can factor it out:
from Tkinter import *
root=Tk()
messages = ['first window content', 'second window content', 'third window content', 'fourth window content' ]
def nextframe(current, messages):
# what happens when you click the button
def command():
current.destroy()
makeframe(messages)
return command
def makeframe(messages):
frame = Frame(root)
frame.pack()
# take the first message
next_content = Label(frame, text=messages.pop(0)).pack()
if messages: # if there are more make the button
next_button = Button(frame, text = 'Next ->', command = nextframe(frame, messages)).pack()
makeframe(messages)
root.mainloop()

Categories

Resources