Passing arguments to a tkinter event callback bound to a key - python

I'm new to Python and this is my first time using tkinter.
I'm trying to create an event where if the Enter key is pressed, my function is called passing in arguments (in this case I want to pass in my entry widget).
Here is a simplified version of my code:
def checkPassword(event):
# do stuff
from tkinter import *
import tkinter.messagebox as box
window = Tk()
window.title('Booking System')
frame = Frame(window)
Entry = Entry(frame)
Entry.bind("<KeyRelease-Return>", checkPassword)
Entry.pack()
frame.pack()
window.mainloop()
The event here is working fine, however I'm having problems when I try to pass in arguments. I've tried
def checkPassword(self, event):
# do stuff
Entry.bind("<KeyRelease-Return>", lambda event: self.checkPassword(event, Entry))
and get the error NameError: global name 'self' is not defined
And I've also tried
def checkPassword(self,event,param):
# do stuff
import functools
Entry.bind("<KeyRelease-Return>", functools.partial(checkPassword, param=Entry))
but get TypeError: checkPassword() missing 1 required positional argument: 'event'
I can't seem to fix either of these, I've looked at the solutions to similar questions on here but can't get any of them to work with my code.

Your second attempt was close, but since checkPassword isn't a class method, you shouldn't be prefixing it with self..
def checkPassword(event, some_value_you_want):
# do stuff
Entry.bind("<KeyRelease-Return>", lambda event: checkPassword(event, name_of_your_entry_widget))

Kevin's answer is correct for the general problem of including data along with the event instance, but is redundant when the extra data is the widget causing the event. Events have up to 17 attributes, including .widget, the widget that was the source of the event. (I strongly recommend the tkinter reference that includes the above link.) The following prints the contents of the entry widget when return is hit.
from tkinter import *
def checkPassword(event):
print(event.widget.get())
window = Tk()
Entry = Entry(window)
Entry.bind("<KeyRelease-Return>", checkPassword)
Entry.pack()
window.mainloop()

Related

Why has the tkinter key-event <Tab> a higher priority than the tkinter key-event <key>?

I am writing an editor (using the tkinter text widget), which replaces tab-characters (inserted by the user) on the fly by 4 blanks.
The replacement is done by a binding to the tabulator-key-event ("Tab"), which inserts 4 blanks and returns with "break". Returning with "break" prevents the tabulator-character from being inserted. This works fine.
Additionally I need a second binding to any key-event ("Key", for syntax highlighting and similar stuff). So I implemented a second binding to "Key". This also works fine.
As I found, the binding of <Tab> has a higher priority as the binding of <key>:
Whenever the tab-key is pressed, only the tab-event gets active but never the key-event.
Why is that?
Is there any priority order defined for events?
This is my example code:
import tkinter as tk
def key_event():
print("Key")
def tab_event(text):
print("Tab")
text.insert("insert", " ")
return("break")
root = tk.Tk()
text = tk.Text(root, height=8, width=20)
text.grid()
text.bind("<Tab>", lambda event : tab_event(text))
text.bind("<Key>", lambda event : key_event())
root.mainloop()
According to the documentation, the more specific binding is chosen over the other. A simple but effective way around this is to use a broad binding like '<Key>' and delegate the event accordingly by it's keysym, that you can access by event.keysym.
As example:
import tkinter as tk
def key_event(event):
if event.keysym == 'Tab':
text.insert("insert", " "*4)
return 'break'
root = tk.Tk()
text = tk.Text(root, height=8, width=20)
text.grid()
text.bind("<Key>", key_event)
root.mainloop()

There is a way to wait on a user's answer in tkinter?

I'm developing an application what have its functions set in different files.
The main file have a tkinter interface and the buttons, entrys and labels are in other file, like this:
Mainfile.py
from tkinter import *
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
import Buttons
self.branch = Buttons.New_Button(self.main_frame)
#Here i wuold like to verify the hipotetic variable after the main_frame were destroyed
if self.branch.hipotetic_variable:
root.mainloop()
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame):
self.button_1 = Button(using_frame, text = 'Button 1', command=functools.partial(self.Func, using_frame))
self.button_1.pack()
def Func(self, to_destroy):
to_destroy.destroy()
#Here is the hipotetic variable what i would like to verify with if statment
self.hipotetic_variable = True
The problem is that I want to keep managing the program in the main file calling the other functions and implementing it, but I cannot verify if it's time to update the screen because mainloop makes impossible to verify it using a while loop and an hipotetic variable that's created after user pressed button.
I wold like to know if there is an way to update an variable contained in the Buttons.py file on Mainfile.py to keep implementing all other canvas in this file.
Your if self.branch.hipotetic_variable: check in the Program.__init__() method is only going to be executed when the Program class instance gets created initially, which is before the button that could change the value of the variable could have been pressed. You also don't want to make the hipotetic_variable an attribute of the Button because that will be destroyed along with the Frame it is in when that's destroyed in the button callback function.
Tkinter applications are user-event driven, meaning that they're "run" by responding to events (that's what mainloop is all about). This type of programming paradigm is different from the procedural or imperative one you're probably used to.
Therefore to do what you want requires setting things up so an event that the program can respond to will be generated, which in this case to when the frame is destroyed. One way to do that is by taking advantage of tkinter Variable classes to hold this hipotetic variable you're interested in. It looks like a boolean, so I used a tkinter BooleanVar to hold its value. One interesting thing about Variables is that you can have changes to their values "traced" by defining functions to be called whenever that happens. That's what I have done in the code below, and the callback function in this case — check_hipotetic_variable() — updates a Label to display the new value of the variable when it's called.
Below is your code with the modifications necessary to use a tkinter BooleanVar and trace changes to its value.
Mainfile.py
from tkinter import *
import Buttons
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
self.notice_lbl = Label(root, text='')
self.notice_lbl.pack(side=BOTTOM)
self.hipotetic_variable = BooleanVar(value=False)
# Set up a trace "write" callback for whenever its contents are changed.
self.hipotetic_variable.trace('w', self.check_hipotetic_variable)
self.branch = Buttons.New_Button(self.main_frame, self.hipotetic_variable)
root.mainloop()
def check_hipotetic_variable(self, *args):
"""Display value of the hipotetic variable."""
value = self.hipotetic_variable.get()
self.notice_lbl.config(text=f'hipotetic variable is: {value}')
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame, variable):
self.button_1 = Button(using_frame, text = 'Button 1',
command=functools.partial(self.Func, using_frame))
self.button_1.pack()
self.variable = variable # Save for use in callback.
def Func(self, to_destroy):
to_destroy.destroy()
self.variable.set(True) # # Change value of the variable.
P.S. I noticed you're not following the PEP 8 - Style Guide for Python Code, which makes reading your code harder to read and follow that if you're were following them — for that reason I strongly suggest you read the guide and start following the suggestions, especially the Naming Conventions which apply to functions and variable names, as well as the names of script files.

Python: How do you obtain variables from another script which are constantly being updated

I've made a script that uses a while True loop to constantly update a series of variables based on UDP packets that I am constantly recieving. I want to ultimately create a GUI that displays that data and updates the screen constantly, which I plan to do with tkinter (using my_label.after in a function which then calls itself, not sure if this is a good plan).
Here is some testing scripts that I can't get to work properly:
GUI2.py (my test looping script)
import time
var = 0
while True:
var += 1
time.sleep(0.1)
GUI Testing.py (the script that would be accessing those variables)
from GUI2 import *
import time
print('never')
print(var)
time.sleep(1)
The second script never reaches the print('never') line, I think because it gets stuck in the other script's while True loop and never returns.
How should I go about this? I have one script that I want in a constant loop to update my variables to the correct values based on incoming packets, and then another script updating a tkinter window. I went this way as most examples I could find using Tkinter didn't use any sort of while True loops. Could I just put my packet recieving code inside the Tkinter mainloop, and would that effectively act as a while True?
EDIT (added Tkinter loop that I can't get working):
This opens a Tkinter window, but the label stays at 99, then reopens a window when I close it with the new x value (ie. 98, 97, etc). I want the label to update every second.
import tkinter as tk
import time
x = 99
while True:
root = tk.Tk()
label = tk.Label(root, text=x)
label.pack()
x -= 1
time.sleep(1)
root.mainloop()
Below is a sample script to show you how you can update the value in the label widget at a certain time interval. I have provided you the hyperlinks to help you understand tkinter's methods. Best regards.
Key points:
use the textvariable option of the tk.Label widget.
use tkinter's control variable. I have shown you how to set and get it's value.
you can use tkinter's widget method called .after() without having to explicitly use a while-statement and time.sleep() method. Tkinter has it's own event loop that you can use.
writing your tkinter GUI as a class makes it easier to implement what you need.
Example Script:
import tkinter as tk
class App(tk.Frame):
def __init__( self, master, *args, **kw ):
super().__init__( master )
self.master = master
self.create_label()
self.update_label()
def create_label( self ):
self.var = tk.IntVar() # Holds an int; default value 0
self.label = tk.Label(self, textvariable=self.var ) # Use textvariable not text
self.label.pack()
def update_label( self ):
value = self.get_value()
self.var.set( value ) # Set the label widget textvariable value.
self.after(1000, self.update_label) # Call this method after 1000 ms.
def get_value( self ):
'''To simulate calling a function to return a value'''
value = self.var.get() + 1
return value
if __name__ == "__main__":
root = tk.Tk()
root.geometry('100x100+0+24')
app = App( root )
app.pack()
root.mainloop() #This command activates tkinter's event loop
Edit:
As a clarification, this answer shows how to utilize the .after() and .mainloop() methods in GUI Testing.py, i.e. using tkinter event loop and not use two while-loops, to achieve what you wanted to do. This is a way to simplify your GUI script.
For more sophisticated algorithms, e.g. more than one while-loop is involved, you have to look into using threads(note it has its issues) or more recently I found a way of using python's Asyncio approach to do it. The learning curve for these two approaches is a lot steeper. To use the asyncio approach, you can explore modifying my answer to do what you want.
Best solution is to use threads however If you plan to do in simplest possible manner then implement the main loop inside your Tkinter GUI and once you read the packet simply update it on your GUI in same loop. Here is the Updated and working Code.
import tkinter as tk
import time
def setvalue(self, x):
self.label.config(text=x, )
root.update()
time.sleep(1)
def changevalues(self):
x = 99
self.label = tk.Label(root, text=x)
self.label.pack()
while x >0:
x -= 1
setvalue(root,x)
root = tk.Tk()
changevalues(root)
root.mainloop()

WASD input in python

In python, how can I receive keyboard input. I'm well aware of console input with input("...") but I'm more concerned with receiving keyboard input while the console window is not active. For example if I created an instance of a Tkinter screen how could I check to see if let's say "w" was pressed. Then if the statement returned true i could move an object accordingly.
The way you do this with a GUI toolkit like tkinter is to create a binding. Bindings say "when this widget has focus and the user presses key X, call this function".
There are many ways to accomplish this. You can, for example, create a distinct binding for each character. Or, you could create a single binding that fires for any character. With these bindings, you can have them each call a unique function, or you can have all the bindings call a single function. And finally, you can put the binding on a single widget, or you can put the binding on all widgets. It all depends on exactly what you are trying to accomplish.
In a simple case where you only want to detect four keys, four bindings (one for each key) calling a single function makes perhaps the most sense. For example, in python 2.x it would look something like this:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, width=400, height=400)
self.label = tk.Label(self, text="last key pressed: ", width=20)
self.label.pack(fill="both", padx=100, pady=100)
self.label.bind("<w>", self.on_wasd)
self.label.bind("<a>", self.on_wasd)
self.label.bind("<s>", self.on_wasd)
self.label.bind("<d>", self.on_wasd)
# give keyboard focus to the label by default, and whenever
# the user clicks on it
self.label.focus_set()
self.label.bind("<1>", lambda event: self.label.focus_set())
def on_wasd(self, event):
self.label.configure(text="last key pressed: " + event.keysym);
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Removing Tkinter Objects (created in a function) in a separate function

I need to be able to clear my tkinter window of all objects (with a function), and create the objects again with a function. However, I cannot access the objects created with my first function with the second function. I recreated my problem below.
import tkinter
window = tkinter.Tk()
def create():
test = tkinter.Button(window, text="Example", command=delete)
test.place(x=75, y=100)
def delete():
test.place_forget()
create()
window.mainloop()
This returns the error - NameError: name 'test' is not defined
Here's a quick sample of how your code might look, using an object oriented structure:
import tkinter as tk
class MyApp: # No need to inherit 'object' in Python 3
def __init__(self, root):
self.root = root
def create_button(self):
self.test_button = tk.Button(self.root,
text="Example",
command=self.delete_button)
self.test_button.place(x=75, y=100)
def delete_button(self):
self.test_button.place_forget()
def run(self):
self.create_button()
self.root.mainloop()
if __name__=='__main__':
root = tk.Tk()
app = MyApp(root)
app.run()
You create a MyApp object that 'owns' the button, and has methods that explicitly act on the things that it owns. Any method of the MyApp object has a reference to various widgets, via the self argument that automatically gets sent in.
This is a lot more code than you had before, and to be honest, for what your code does right now, it's an overkill. Malik's solution of using global is probably fine. However, if you want to add more widgets, layer them out, have them interact in more complex ways etc, then using global can introduce hard-to-find bugs, and makes it incredibly hard to wrap your head around what's going on.
Any non-trivial use of Tkinter that I have seen has used an object-oriented style similar to the above example.
As an aside, I wouldn't create the delete function - using the .config method to set the command after you create the button would be better:
def create_button(self):
self.test_button = tk.Button(self.root, text="Example")
self.test_button.config(command=self.test_button.place_forget)
self.test_button.place(x=75, y=100)
Using .config allows you to set commands that are methods of the button you just created, which you can't do when you set the command as a part of the button instantiation.
Well if you're using two different functions, you're going to need global variables:
import tkinter
window = tkinter.Tk()
test = None
def create():
global test
test = tkinter.Button(window, text="Example", command=delete)
test.place(x=75, y=100)
def delete():
global test
test.destroy() # or place_forget if you want
window.after(5000, create) # button reappears after 5 seconds
create()
window.mainloop()
Your delete function could not destroy the button as it was only defined in the create function. The workaround is to create a global variable that can be accessed by both.

Categories

Resources