How to press more than one tkinter button simultaneously using python - 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

Related

How to make tkinter right click menu work on one widget only?

Is there a simple way to get the right click menu to open on texty only and not the whole window?
This was a quick mashup to illustrate my question. Inheriting from texty on line 25 was a shot in the dark, which didnt work, but it's close to a simple solution, like I am seeking. I was hoping to avoid programming a whole class each time I want to set a right click menu.
from tkinter import *
from tkinter import ttk
def menu_popup(event):
try:
popup.tk_popup(event.x_root, event.y_root, 0)
finally:
popup.grab_release()
win = Tk()
win.geometry("600x550+125+125")
e = Entry(win, width=50, font=('Helvetica', 11))
e.pack()
e.insert(0, "Some text....")
label = Label(win, text="Right-click to see a menu", font= ('Helvetica 18'))
label.pack(pady= 40)
texty=Text(win, height=10)
texty.pack()
popup = Menu(texty, tearoff=0)
popup.add_command(label="New")
popup.add_separator()
popup.add_command(label="Open")
popup.add_separator()
popup.add_command(label="Close")
win.bind("<Button-3>", menu_popup)
button = ttk.Button(win, text="Quit", command=win.destroy)
button.pack()
mainloop()
The widget on which the callback should be executed for the respective event is determined by the widget you call bind on(and the level of bind too*). So if you want the event to be identified within texty, then apply binding to it.
texty.bind("<Button-3>", menu_popup)
* There is bind_all which executes no matter which widget has focus or is called upon. Read 54.1. Levels of binding for more info.

How to bind keyboard event to Python tkMessageBox?

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()

Tkinter Button Function Control by MessageBox

The code below shows part of my program and the issue im facing.
def checkAnswer():
mainAnswer = answer01.get()
if len(mainAnswer) == 0:
messagebox.showwarning(message='Please answer the question!')
return
if int(mainAnswer) != answer:
messagebox.showwarning(message='Incorrect! The correct answer is: ' + str(answer))
else:
nxtquest.config(state=NORMAL)
messagebox.showinfo(message='Correct! :)')question01 = Label(easy)
question01.grid(row=2, column=0)
answer01 = Entry(easy)
answer01.grid(row=3, column=2)
answer01.bind('<Return>', func=lambda e:checkAnswer())
start = Button(easy, text = "Start!", command=ask, bg='green', fg='white')
start.grid(row=3, column=3)
nxtquest = Button(easy, text='Next Question', command=ask)
nxtquest.grid(row=5, column=2)
checkbut = Button(easy, text='Check', command=checkAnswer)
checkbut.grid(row=4, column=2)
#check button and answer01 enabled after start pressed
launch = 1
if launch == 1:
answer01.config(state=DISABLED)
checkbut.config(state=DISABLED)
nxtquest.config(state=DISABLED)
The issue which im struggling here is that whenever i run the program everything is okay. When the window is displayed checkbut, nxtquest and label answer01 are greyed out (disabled).
The start button enables only checkbut and answer01 and then is destroyed. (So far so good)
So nxtquest will enable once the input is correct as seen in the
else:
nxtquest.config(state=NORMAL)
But when I reach another question the nxtquest button is already enabled, this is the problem!
How could I make it so the button will enable itself only after the warning message box is displayed?
Could I ask for some help with this and possibly suggestions if you see any rookie mistakes ?
Whilst I don't know of any way you could do this with a messagebox widget (although I'm sure there's an event you could use as the trigger) you can most certainly do this by substituting the messagebox with a Toplevel widget and using .protocol("WM_DELETE_WINDOW", callback()) on the widget.
This would mean that whenever the Toplevel widget was "closed" we would actually be overwriting the action taken when the event was raised and would manually handle the closing of the widget as well as whatever else we wanted it to do.
This would look something like the below:
from tkinter import *
root = Tk()
button = Button(root, text="Ok", state="disabled")
button.pack()
top = Toplevel(root)
def close():
top.destroy()
button.configure(state="active")
top.protocol("WM_DELETE_WINDOW", close)
root.mainloop()
If you close the Toplevel widget you will see that the button is now active instead. This would equally work if we added a Button to the Toplevel widget which called the function close().

Python 2.7 Tkinter: how do I click & release on two widgets and get responses from both?

I hope you can help me with a problem I have in python 2.7. I couldn't find a solution online, but I'm honestly unsure what keywords to search for, so I'm sorry if this is redundant.
The code below is an example of my problem.
import Tkinter as tk
root = tk.Tk()
#Widgets.
btn1 = tk.Label(root, text="btn1", bg="gray80")
btn2 = tk.Label(root, text="btn2", bg="gray80")
btn1.pack(side=tk.TOP, fill=tk.X)
btn2.pack(side=tk.TOP, fill=tk.X)
#Widget events.
def onClick1(event):
print "Clicked button 1."
def onRelease1(event):
print "Released button 1."
def onClick2(event):
print "Clicked button 2."
def onRelease2(event):
print "Released button 2."
#Bindings.
btn1.bind("<Button-1>", onClick1, add="+")
btn1.bind("<ButtonRelease-1>", onRelease1, add="+")
btn2.bind("<Button-1>", onClick2, add="+")
btn2.bind("<ButtonRelease-1>", onRelease2, add="+")
root.mainloop()
Whenever I click one button (technically a label) and hold it, the onClick event for it fires, but if I drag the mouse over to the other and release it, I get the same onRelease as the one I clicked, and not the one for the label I have my mouse over currently. This has held me back some time now, and I'd hate to scrap the whole feature in my program I need this for, so any help would be greatly appreciated.
The release event always fires on the same widget that got the press event. Within your handler you can ask tkinter what widget is under the cursor.
Example:
import Tkinter as tk
root = tk.Tk()
btn1 = tk.Label(root, text="btn1", bg="gray80")
btn2 = tk.Label(root, text="btn2", bg="gray80")
btn1.pack(side=tk.TOP, fill=tk.X)
btn2.pack(side=tk.TOP, fill=tk.X)
def onRelease(event):
x,y = event.widget.winfo_pointerxy()
widget = event.widget.winfo_containing(x, y)
print("widget:", widget.cget("text"))
btn1.bind("<ButtonRelease-1>", onRelease)
btn2.bind("<ButtonRelease-1>", onRelease)
root.mainloop()

Testing a Tkinter Button object with an if statement

I am working on a program that needs a GUI with buttons to do certain things, as is usually the case when having questions about Buttons, but I have ran into difficulties because while you can activate functions with buttons, you cannot test wether they are currently being pressed with an if statement. I know how to use check buttons and radio buttons, but I have not found anything else remotely useful. I need to be able to tell how long they are pressed, and to do things as they are being pressed that stop when they are released. I need a way of assigning a variable that will be true while you are still holding click down over the button, and false any other time, with a normal button, not one that toggles each time you press.
It's not clear to me what you're having trouble with, so I took the liberty of coding up a little GUI that times how long a button is pressed.
import tkinter as tk
import time
class ButtonTimer:
def __init__(self, root):
self.master = root
self.button = tk.Button(self.master, text="press me") # Notice I haven't assigned the button a command - we're going to bind mouse events instead of using the built in command callback.
self.button.bind('<ButtonPress>', self.press) # call 'press' method when the button is pressed
self.button.bind('<ButtonRelease>', self.release) # call 'release' method when the button is released
self.label = tk.Label(self.master)
self.startTime = time.time()
self.endTime = self.startTime
self.button.grid(row=1, column=1)
self.label.grid(row=2, column=1)
def press(self, *args):
self.startTime = time.time()
def release(self, *args):
self.endTime = time.time()
self.label.config(text="Time pressed: "+str(round(self.endTime - self.startTime, 2))+" seconds")
root = tk.Tk()
b = ButtonTimer(root)
root.mainloop()
Note: I tested this in python 2.7 then changed the import from Tkinter to tkinter. It will probably work in 3.x, but I haven't tested it with that version.

Categories

Resources