How to bind keyboard event to Python tkMessageBox? - python

I'm developing a small GUI application which is required to work with a wireless presenter pointer, which has only two keys: left arrow and right arrow. I can bind keyboard event "Left" and "Right" to the root (the main window) and call a function, so most part of my application works fine.
But when I need to pop up a message box with tkMessageBox to show some information, the only way to click "OK" button with keyboard is to press "space", which is not there on my presenter pointer. It means when such a message box is popped up, the presenter have to go to the computer to either click the "OK" button with mouse, or "space" key with keyboard.
Is there any way allowing me to temporarily bind "left arrow" or "right arrow" to the "OK" button when such a message box is popped up and then restore the binding of both the keys back to there original on_click function?

As the tkMessageBox is not an object but a tcl call, you cannot overload bindings that easy. Just subclass Tkinter.Frame to get an object where keys can be bound.
Subclassing could nevertheless follow the look and feel of a MessageBox.
e.g.
#!/usr/bin/python
import Tkinter
class MyBox(Tkinter.Toplevel):
def __init__(self, *args, **kwargs):
Tkinter.Toplevel.__init__(self, *args, **kwargs)
self.__text = Tkinter.StringVar()
self.__text.set("Initialized Text")
Tkinter.Label(self, textvariable = self.__text).grid(row=0, column=0, columnspan=3, sticky=Tkinter.NW+Tkinter.SE)
Tkinter.Button(self, text="OK", command=self.release_func).grid(row=1, column=1, sticky=Tkinter.NW+Tkinter.SE)
self.bind_all("&ltKeyRelease&gt", self.release_func)
self.grid()
self.focus_set()
def set_text(self, text="NoText"):
self.__text.set(text)
self.focus_set()
def release_func(self, event=None):
# event=None necessary as we also use button binding.
self.destroy()
root = Tkinter.Tk()
messagebox = MyBox()
messagebox.set_text("Show this message")
root.mainloop()

Related

I get the error _tkinter.TclError: bad window path name ".!button" when I destroy the button

from tkinter import *
master=Tk()
class check:
def __init__(self,root):
self.root=root
self.b1=Button(root,text="Click me",command=self.undo)
self.b2=Button(root,text="Again",command=self.click)
def click(self):
self.b1.place(relx=0.5,rely=0.5)
def undo(self):
self.b1.destroy()
self.b2.place(relx=0.2,rely=0.2)
c=check(master)
c.click()
master.mainloop()
This is my code. I get _tkinter.TclError: bad window path name ".!button" error only when I use destroy method. But I want to delete previous button when another button appears.What should I do?
What are you doing? When you click the "Click me" button (and call the self.undo method, where the self.b1 button is destroyed) and then click the "Again" button (and call the self.click method, which tries to place already destroyed self.b1 button), you get the error, that the button does not exist. Of course, it doesn't because you have destroyed it.
It looks like you meant to hide the button. If you intended to do this, then you could just use .place_forget() method (there are also .pack_forget() and .grid_forget() methods for pack and grid window managers, respectively), that hides the widget, but not destroys it, and hence you would be able to restore it again when you need.
Here is your fixed code:
from tkinter import *
master = Tk()
class check:
def __init__(self, root):
self.root = root
self.b1 = Button(root, text="Click me", command=self.undo)
self.b2 = Button(root, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
c = check(master)
c.click()
master.mainloop()
I can also give you a piece of advice about the implementation:
1) You should write the code according to the PEP8 style; classes should be named in the CamelCase.
2) You should inherit your Tkinter app class(es) either from Tk (usage is shown below) Toplevel(the same as Tk, but use ONLY for child windows), Frame class (almost the same as for Tk, but you need to pack/grid/place that Frame in a window).
3) It's better to create the widgets in a separate function (it helps while developing complex and big apps).
4) It's recommended to write if __name__ == "__main__": condition before creating the window (if you do like this, you will be able to import this code from other modules, and the window won't open in that case).
Here is an example:
from tkinter import *
class Check(Tk):
def __init__(self):
super().__init__()
self.create_widgets()
self.click()
def create_widgets(self):
self.b1 = Button(self, text="Click me", command=self.undo)
self.b2 = Button(self, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
if __name__ == "__main__":
Check().mainloop()
After you destroyed button b1 in the undo(self) function tkinter cannot access it anymore and will be confused when you try to place is somewhere in the click(self) function.
To make button b1 only disappear visually you could place it outside of the window instead of destroying it. To do so replace
self.b1.destroy()
with
self.b1.place(relx=-5, rely=0)
This will move the button b1 far to the left, where it cannot be seen.
When calling the click(self) function, the button will reappear, because it will be moved inside the window again.

How to press more than one tkinter button simultaneously using python

I have multiple buttons in tkinter window, but on a computer we only have one mouse pointer at one location at a time, how can I multi-press buttons simultaneously just like on a touch screen?
Here is a sample code of what I am trying to do,
#!/usr/bin/python
import Tkinter as Tk
def button_pressed_cb(event):
print("You pressed " + event.widget['text'])
def button_release_cb(event):
print("You released " + event.widget['text'])
root = Tk.Tk()
button1 = Tk.Button(root, text="button1")
button1.pack()
button2 = Tk.Button(root, text="button2")
button2.pack()
button1.bind("<ButtonPress>", button_pressed_cb)
button1.bind("<ButtonRelease>", button_release_cb)
button2.bind("<ButtonPress>", button_pressed_cb)
button2.bind("<ButtonRelease>", button_release_cb)
root.mainloop()
After executing this, I get a sample output of something like this,
You pressed button1
You released button1
You pressed button2
You released button2
What I want to achieve is to be able to generate events which occur in this sequence,
You pressed button1
You pressed button2
You released button2
You released button1
Does anybody know what's the best way to achieve this? Thank you so much.
If you just want to call the callbacks, you can do that—they're ordinary Python functions, after all.
If you're asking how to create an event and dispatch it, you can use the event_generate method. Unfortunately, I don't know of any tkinter documentation for this, so you have to look at the Tcl/Tk docs instead. But the basic idea is:
def hit_the_buttons():
button1.event_generate('<ButtonPress>', when='tail')
button2.event_generate('<ButtonPress>', when='tail')
button2.event_generate('<ButtonRelease>', when='tail')
button1.event_generate('<ButtonRelease>', when='tail')
The when argument puts the events at the end of the event queue rather than processing them immediately, so they won't interfere with things like redraws or actual mouse events. It isn't really necessary here; I included it mainly to show how to map one of those Tcl arguments (-when tail) to tkinter.
If you want this to go through the normal dispatch instead of directly to the buttons, you can call root.event_generate and pass x and y arguments that will hit-test as inside the buttons.
Ok, I found a solution. I put in extra checks that if user presses and holds ctrl before clicking a button, that button will latch on to that pressed state. Then user can go and click other button etc. User can click that button again to unlatch it.
I also added a hover message as well for user to tell the user they can use ctrl to latch a button and achieve multikey presses.
Here is the new code,
#!/usr/bin/python
import Tkinter as Tk
class HoverInfo(Tk.Menu):
def __init__(self, parent, text):
Tk.Menu.__init__(self, parent, tearoff=0,
background='light goldenrod yellow')
self.__displayed = False
for line in text.split('\n'):
self.add_command(label=line)
parent.bind("<Enter>", self.__display)
parent.bind("<Leave>", self.__remove)
def __display(self, event):
if event.widget['state'] == Tk.NORMAL:
if not self.__displayed:
self.__displayed = True
self.post(event.x_root+5, event.y_root+5)
def __remove(self, event):
if self.__displayed:
self.__displayed = False
self.unpost()
CONTROL = 4
def button_pressed_cb(event):
if event.widget['state'] != Tk.DISABLED:
print("You pressed " + event.widget['text'])
if (event.state & CONTROL) == CONTROL:
event.widget.config(state=Tk.DISABLED)
event.widget.config(relief=Tk.SUNKEN)
def button_release_cb(event):
if (event.state & CONTROL) != CONTROL:
print("You released " + event.widget['text'])
event.widget.config(state=Tk.NORMAL)
event.widget.config(relief=Tk.RAISED)
root = Tk.Tk()
button1 = Tk.Button(root, text="button1")
button1.pack()
button2 = Tk.Button(root, text="button2")
button2.pack()
button1.bind("<ButtonPress>", button_pressed_cb)
button1.bind("<ButtonRelease>", button_release_cb)
button2.bind("<ButtonPress>", button_pressed_cb)
button2.bind("<ButtonRelease>", button_release_cb)
HoverInfo(button1, "Hint:\nYou can hold Ctrl key before\nclicking a button to latch it.")
root.mainloop()
Now, this is how the hover message looks,
https://ibb.co/dEb6Bn
And when user holds ctrl key and clicks button 1 and then button 2, then this is how it looks,
https://ibb.co/eVzRBn
Now, I can easily generate this sequence of events,
You pressed button1
You pressed button2
You released button2
You released button1

Python Tkinter How use bindings correctly?

The idea was show in label where my cursor(line.column) is. Which work with .index(INSERT) well but if i bind the right mouse button with text it returns the previous cursor position not the current.
It seems that callback is executed after event?
from tkinter import Tk, Text, Frame, Label, StringVar, constants, END, INSERT
EXPL_TEXT = "I know that dress is karma. Perfume regret\nYou got me thinking bout"
class App(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.pack()
self.var = StringVar()
self.init_widgets()
def init_widgets(self):
self.text = Text(self)
self.text.bind('<Button-1>',self.callback_index)
self.text.pack()
self.text.insert(END,EXPL_TEXT)
self.label = Label(self, textvariable=self.var)
self.label.pack()
def callback_index(self,event):
x = self.text.index(INSERT)
self.var.set(x)
if __name__ == '__main__':
root = Tk()
app = App(root)
root.mainloop()
The issue I believe you are seeing is that when you click your mouse down the event fires to read INSERT. The problem is before you lift your mouse the location will still have the value of the previous INSERT. So in order for you to get the update after the event has completed we can use after() to wait for the event to finish and then set the value for self.var.
Change your callback_index method to:
def callback_index(self,event):
root.after(0, lambda: self.var.set(self.text.index(INSERT)))
What we are doing is telling python to schedule something to happen after a set time. I believe (Correct me if I am wrong) Because an event is in progress it waits until that event finishes to perform the action in the after() method.
We use lambda to create an anonymous function to update your self.var variable and all should work as intended.

Tkinter active state

What I'm wanting to do is detect if the mouse has moved over an object on the screen and if it has I want to display the data about the object in a separate frame. No mouse click allowed, only mouse movement, just like activewidth. I don't see and reference anywhere showing a built in feature that will allow what I'm trying to do. Am I wrong and if so what have I not seen yet. Can't research further that which I don't know about.
Edit: I did use the bad word Object when I was referring to drawn shapes on a canvas. These are lines, aka a mapping program, placed on a canvas. When I scroll over a line/road I want it pop up the name of the road on a separate frame. When I scroll over the symbol for a business I want it to bring up the name of the business and other pertinent information in the separate frame. Hence why I said activewidth is what I'm basically trying to mimic as its capturing the mouse location and then automatically recognizing something is on the screen underneath where the mouse pointer is located. If I could capture that same pointer reference...Right now as I'm editting this I'm thinking I would have to create each line with its own separate reference name...
z[0] = self.canvas.create_line()
z[1] = self.canvas.create_line()
z[2] = self.canvas.create_line()
etc. Am I wrong on this thought? Is there an easier way of doing this?
You can bind to the <Enter> and <Leave> events, which will fire whenever the mouse enters or leaves a widget.
Example:
import tkinter as tk
root = tk.Tk()
l1 = tk.Label(root, text="Hover over me",
width=40, bd=2, relief="groove",
background="lightblue")
l2 = tk.Label(root)
l1.pack(side="top", fill="x", padx=10, pady=10)
l2.pack(side="top", fill="both", expand=True)
def handle_enter(event):
event.widget.configure(background="pink")
l2.configure(text="you entered the widget")
def handle_leave(event):
event.widget.configure(background="lightblue")
l2.configure(text="")
l1.bind("<Enter>", handle_enter)
l1.bind("<Leave>", handle_leave)
root.mainloop()
If the object is a tkinter widget, bind to "<Motion>". It will only trigger when the mouse moves over the object that you bound to (or it's children). The event object will contain information about which object you were over and even the object itself.
import Tkinter as tk
class GUI(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
lbl = tk.Label(text='Label 1')
lbl.pack()
lbl.bind('<Motion>', self.motion_detected)
lbl = tk.Label(text='Thing B')
lbl.pack()
lbl.bind('<Motion>', self.motion_detected)
def motion_detected(self, event):
print('motion detected in {} at {},{}'.format(event.widget['text'], event.x, event.y))
def main():
root = tk.Tk()
root.geometry('200x200')
win = GUI(root)
win.pack()
root.mainloop()
if __name__ == '__main__':
main()
with the CURRENT tag, you can match the canvas element under the mouse
CURRENT (or “current”) matches the item under the mouse pointer, if
any. This can be used inside mouse event bindings to refer to the item
that triggered the callback.
source
so,
first you pass your information through the tags option
self.canvas.create_line(.... tags="road1")
then you bind to the <Motion> event and inside the handler you get the tags of the current ellement
ellement_id = canvas.find_withtag(CURRENT)
ellement_tags = canvas.gettags(ellement_id)

tkinter messagebox without buttons

In my program I just need to notify user to not press a physical button om a system with no keyboad or mouse,
want to popup a Wait message that disapears when the system is again ready
There are two reasons you don't want a message box here.
First, the whole point of a message box is that it's a modal dialog with some standardized buttons, and you don't want those buttons.
Second, the whole point of a modal dialog is that it's modal—it runs its own event loop, and doesn't return until the dialog is dismissed. This means (unless you're using background threads) your app can't do anything while displaying it.
The first problem is easy to solve. tkMessageBox is just a simple wrapper around tkCommonDialog.Dialog. It's worth looking at the source to see just how simple it is to construct a dialog box that does what you want. But tkSimpleDialog.Dialog is even simpler than tkCommonDialog (hence the name). For example:
class WaitDialog(tkSimpleDialog.Dialog):
def __init__(self, parent, title, message):
self.message = message
Dialog.__init__(self, parent, title=title, message=message)
def body(self, master):
Label(self, text=self.message).pack()
def buttonbox(self):
pass
def wait(message):
WaitDialog(root, title='Wait', message=message)
That's all it takes to create a modal dialog with no buttons. Dialog Windows and the source to tkSimpleDialog have more details.
The second problem is even easier to solve: If you don't want a modal dialog, then all you want is a plain old Toplevel. You may want it to be transient, so it stays on top of the master, hides with it, doesn't show up on the taskbar, etc., and you may want to configure all kinds of other things. But basically, it's this simple:
def wait(message):
win = Toplevel(root)
win.transient()
win.title('Wait')
Label(win, text=message).pack()
return win
Now you can call wait() and continue to run:
def wait_a_sec():
win = wait('Just one second...')
root.after(1000, win.destroy)
root = Tk()
button = Button(root, text='do something', command=wait_a_sec)
root.mainloop()

Categories

Resources