I'm writing a test program to detect mouse motion within a Tkinter Window using Python 2.*. I can create the necessary widgets and bind the appropriate event handler function to the root widget as needed:
import Tkinter as tk
class App:
def __init__(self, master=None):
self.root = master
self.frame = tk.Frame(master)
self.frame.pack()
self.create_widgets()
self.setup_handlers()
def create_widgets(self):
...
def setup_handlers(self):
self.root.bind('<Motion>', self.update) # This is the line I wish to look at
def update(self, event):
...
root = tk.Tk()
app = App(master=root)
root.mainloop()
What I want to do now is be able to activate the event handler with a combined input. I want to be able, for instance, to only activate the event handler when I move the mouse with the 'r' key held down. What event string would I need for this? Where can I find a full rundown on the event-string formatting for binding event handlers?
for the combined event handling, you can do something like:
class App:
holding = False
def __init__(self, master):
self.root = master
self.root.bind("<KeyPress>", self.holdkey)
self.root.bind("<KeyRelease>", self.releasekey)
def holdkey(self, e):
if e.char == "r" and not self.holding:
self.root.bind("<Motion>", self.combined_update)
self.holding = True
def releasekey(self, e):
if e.char == "r" and self.holding:
self.root.unbind("<Motion>")
self.holding = False
def combined_update(self, e):
# Event handling for the combined event.
print ("yo")
This will "probably" work.
Related
I was trying to make an auto clicker using Mouse and keyboard modules and tkinter as the gui and wrote this code
#Import
import tkinter as tk
import random as r8
import keyboard as kb
import mouse as ms
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
self.joesd()
def create_widgets(self):
self.joe = tk.Frame(self)#main frame
self.joe.pack(fill=tk.BOTH)
self.lbl = tk.Label(self.joe, text='Example text', height=5, width=30)
self.lbl.pack(side="top")# where the label will be located
self.lb = tk.Label(self.joe, text='Example Text', height=5, width=35)
self.lb.pack(side="top")# where the label will be located
def joesd(self):
while True:
if kb.is_pressed('q') == True:
ms.press('left')
ms.release('left')
root = tk.Tk()
app = Application(master=root)
app.mainloop()
Then I noticed that the gui never appears but will appear if I removed
def joesd(self):
while True:
if kb.is_pressed('q') == True:
ms.press('left')
ms.release('left')
What should I do?
The reason why GUI does not show up is that before the code hits mainloop() it goes into a infinite loop(while loop) and it cannot reach mainloop and hence window does not show up and events are not processed. So what you should do is get rid of while. One way is to use after() method to emulate while.
def joesd(self):
if kb.is_pressed('q'):
ms.press('left')
ms.release('left')
self.after(100,self.joesd)
This will repeat the function every 100 ms, you can reduce it to 1ms too. But make sure it is not too much for the system to handle.
You should not use while loop in a tkinter application. You can register a callback using kb.on_press_key() instead:
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
...
#self.joesd() # <- not necessary
kb.on_press_key("q", self.on_key_press)
...
def on_key_press(self, event):
#print("q pressed", event)
ms.press('left')
ms.release('left')
# above two lines can be replaced by ms.click('left')
I am trying to build a simple auto clicker program which has start/stop buttons and a hotkey (using Tkinter and Pynput). Whenever I start the auto clicker using the start button, it works perfectly and I am able to stop it. However, when I start the auto clicker using the hotkey, I cannot stop it with the stop button as it freezes the entire program.
This is my main class for the GUI:
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__()
self.parent = parent
self.parent.bind("<Destroy>", self.exit)
self.clicker = Clicker(Button.left, 1)
self.clicker.start()
self.kb = Keyboard("<shift>+s", self.start_click)
self.kb.start()
btn_box = ttk.Combobox(self, values=BUTTONS, state="readonly")
btn_box.current(0)
btn_box.pack()
start = tk.Button(self, text="Start", command=self.start_click)
start.pack()
stop = tk.Button(self, text="Stop", command=self.stop_click)
stop.pack()
exit = tk.Button(self, text="Exit", command=self.exit)
exit.pack()
def start_click(self):
self.clicker.start_click()
def stop_click(self):
print("e")
self.clicker.stop_click()
def exit(self, event=None):
self.clicker.exit()
self.parent.quit()
And these are my Clicker and Keyboard classes:
class Clicker(threading.Thread):
def __init__(self, button, delay):
super().__init__()
self.button = button
self.delay = delay
self.running = False
self.prog_running = True
self.mouse = Controller()
def start_click(self):
print("start")
self.running = True
def stop_click(self):
print("stop")
self.running = False
def exit(self):
self.running = False
self.prog_running = False
def run(self):
while self.prog_running:
while self.running:
self.mouse.click(self.button)
time.sleep(self.delay)
time.sleep(0.1)
class Keyboard(threading.Thread):
def __init__(self, keybind, command):
super().__init__()
self.daemon = True
self.hk = HotKey(HotKey.parse(keybind), command)
def for_canonical(self, f):
return lambda k: f(self.l.canonical(k))
def run(self):
with Listener(on_press=self.for_canonical(self.hk.press),
on_release=self.for_canonical(self.hk.release)) as self.l:
self.l.join()
Does anyone know why it freezes when pressing the stop button after using the hotkey?
I had issues when I used self.quit() instead of self.destroy() but most probably, that's not it with your case.
Adding the shortcut using self.bind() may be a solution,
For example:
#Importing the tkinter module
from tkinter import *
#Creating a window
root = Tk()
#Creating a label
txt = Label(root, text="Opened! But I'm closing, you know...").grid(row=1, column=1)
#Defining an action and binding it(root is the window's name)
def actionn(*args):
root.destroy()
root.bind('<Shift-S>', actionn)
#Showing the window and the label
root.mainloop()
You can't bind shift like that, bind it with self.parent.bind('<Shift-S>')
I guess it freezes because the shortcut assigned doesn't work...
In the following code, when I press the button to add some secondary windows, and then try to close a window by using "Command-w" it does not always close the active window. But if I disable the menu creation by commenting the line self.gerar_menu(), windows are opened and closed as expected (I mean, by clicking the red 'x' button or by pressing Command-W in OS X). Any idea about what is wrong here?
Here is my current test code:
#!/usr/bin/env python3.6
# encoding: utf-8
import tkinter as tk
import tkinter.font
from tkinter import ttk
class baseApp(ttk.Frame):
"""
Parent classe for main app window (will include some aditional methods and properties).
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
#self.gerar_menu() # This line breaks "Command-w" functionality
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self, number=0))
self.btn.pack()
self.newDetailsWindow = {}
self.windows_count=0
def gerar_menu(self):
""" generate the application menu """
self.menu = tk.Menu(root)
root.config(menu=self.menu)
self.fileMenu = tk.Menu(self.menu)
self.menu.add_cascade(label="File", menu=self.fileMenu)
self.fileMenu.add_command(label="New Document", command=None, accelerator="Command+n")
def create_detail_window(self, *event, number=None):
self.windows_count += 1
self.newDetailsWindow[self.windows_count]=tk.Toplevel()
self.newDetailsWindow[self.windows_count].geometry('900x600+80+130')
self.newDetailsWindow[self.windows_count].title(f'Detail: {self.windows_count}')
self.newDetailsWindow[self.windows_count].wm_protocol("WM_DELETE_WINDOW", self.newDetailsWindow[self.windows_count].destroy)
self.newDetailsWindow[self.windows_count].bind("Command-w", lambda event: self.newDetailsWindow[-1].destroy())
self.detail_window = detailWindow(self.newDetailsWindow[self.windows_count], self.windows_count)
self.newDetailsWindow[self.windows_count].focus()
print(self.newDetailsWindow)
class detailWindow(ttk.Frame):
""" Base class for secondary windows """
def __init__(self, master, rep_num, *args,**kwargs):
super().__init__(master,*args,**kwargs)
self.num_rep = rep_num
self.master.minsize(900, 600)
self.master.maxsize(900, 600)
print(f"Showing details about nr. {self.num_rep}")
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
self.lbl_text = ttk.Label(self.mainframe,
text=f"Showing details about nr. {self.num_rep}")
self.lbl_text.pack()
if __name__ == "__main__":
root = tk.Tk()
janela_principal = App(root)
root.title('Main Window')
root.bind_all("<Mod2-q>", exit)
root.mainloop()
If you want to create a binding for closing a window, you need the function to act upon the actual window that received the event. Your code is always deleting the last window that was opened no matter which window received the event.
The first step is to bind to a function rather than using lambda. While lambda has its uses, binding to a named function is much easier to debug and maintain.
Once the function is called, the event object can tell you which window got the event via the widget attribute of the event object. Given this window, you can get the toplevel window that this window is in (or itself, if it's a toplevel window) via the winfo_toplevel command.
For example:
window = tk.Toplevel(...)
...
window.bind("<Command-w>", self.destroy_window)
...
def destroy_window(self, event):
window = event.widget.winfo_toplevel()
window.destroy()
I have a custom Frame with an update method that I want to call every time the user switches tab in a notebook using python2.7 ttk.
My first thought was to intercept some "tab switch event", get the frame from the event and call the update method. However, googling this yields no good results. I can't seem to catch the event, nor get the frame from a "tab click event" in a good way. My current solution below works okay but I'm looking for a better alternative. (Also with my real update method I get some nasty alloc errors).
import ttk
import Tkinter as tk
class MyFrame(ttk.Frame):
def __init__(self, master, name):
ttk.Frame.__init__(self, master)
self.name = name
self.pack()
def update(self):
# Update the contents of the frame...
print "Updating %s" % self.name
class App():
def __init__(self):
root = tk.Tk()
nb = ttk.Notebook(root)
f1_name = "Tab1"
f2_name = "Tab2"
f1 = MyFrame(nb, f1_name)
f2 = MyFrame(nb, f2_name)
nb.add(f1, text=f1_name)
nb.add(f2, text=f2_name)
def tab_switch(event):
if event.widget.identify(event.x, event.y) == "label":
index = event.widget.index("#%d,%d" % (event.x, event.y))
title = event.widget.tab(index, "text")
if title == f1_name:
f1.update()
elif title == f2_name:
f2.update()
# and so on...
nb.bind("<Button-1>", tab_switch)
nb.pack(fill="both", expand=True)
f1.update() # Initial update of first displayed tab
root.geometry("200x200")
root.mainloop()
App()
As you can imagine this becomes very non-DRY as the amount of tabs increases...
You can bind to the <Visibility> or <Map> event of the frame. For example:
class MyFrame(ttk.Frame):
def __init__(self, master, name):
ttk.Frame.__init__(self, master)
self.name = name
self.pack()
self.bind("<Visibility>", self.on_visibility)
def on_visibility(self, event):
self.update()
Instead of
def tab_switch():
# see question...
nb.bind("<Button-1>", tab_switch)
you can do the following trick
b.bind("<<NotebookTabChanged>>",
lambda event: event.widget.winfo_children()[event.widget.index("current")].update())
This also removes the need to call
f1.update() # Initial update of first displayed tab
However, the solution proposed by #BryanOakley might work better when you have different types of frames where you cannot be sure if an .update() method exists.
I have a python-tkinter gui app that I've been trying to find some way to add in some functionality. I was hoping there would be a way to right-click on an item in the app's listbox area and bring up a context menu. Is tkinter able to accomplish this? Would I be better off looking into gtk or some other gui-toolkit?
You would create a Menu instance and write a function that calls
its post() or tk_popup() method.
The tkinter documentation doesn't currently have any information about tk_popup().
Read the Tk documentation for a description, or the source:
library/menu.tcl in the Tcl/Tk source:
::tk_popup --
This procedure pops up a menu and sets things up for traversing
the menu and its submenus.
Arguments:
menu - Name of the menu to be popped up.
x, y - Root coordinates at which to pop up the menu.
entry - Index of a menu entry to center over (x,y).
If omitted or specified as {}, then menu's
upper-left corner goes at (x,y).
tkinter/__init__.py in the Python source:
def tk_popup(self, x, y, entry=""):
"""Post the menu at position X,Y with entry ENTRY."""
self.tk.call('tk_popup', self._w, x, y, entry)
You associate your context menu invoking function with right-click via:
the_widget_clicked_on.bind("<Button-3>", your_function).
However, the number associated with right-click is not the same on every platform.
library/tk.tcl in the Tcl/Tk source:
On Darwin/Aqua, buttons from left to right are 1,3,2.
On Darwin/X11 with recent XQuartz as the X server, they are 1,2,3;
other X servers may differ.
Here is an example I wrote that adds a context menu to a Listbox:
import tkinter # Tkinter -> tkinter in Python 3
class FancyListbox(tkinter.Listbox):
def __init__(self, parent, *args, **kwargs):
tkinter.Listbox.__init__(self, parent, *args, **kwargs)
self.popup_menu = tkinter.Menu(self, tearoff=0)
self.popup_menu.add_command(label="Delete",
command=self.delete_selected)
self.popup_menu.add_command(label="Select All",
command=self.select_all)
self.bind("<Button-3>", self.popup) # Button-2 on Aqua
def popup(self, event):
try:
self.popup_menu.tk_popup(event.x_root, event.y_root, 0)
finally:
self.popup_menu.grab_release()
def delete_selected(self):
for i in self.curselection()[::-1]:
self.delete(i)
def select_all(self):
self.selection_set(0, 'end')
root = tkinter.Tk()
flb = FancyListbox(root, selectmode='multiple')
for n in range(10):
flb.insert('end', n)
flb.pack()
root.mainloop()
The use of grab_release() was observed in an example on effbot.
Its effect might not be the same on all systems.
I made some changes to the conext menu code above in order to adjust my demand and I think it would be useful to share:
Version 1:
import tkinter as tk
from tkinter import ttk
class Main(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
master.geometry('500x350')
self.master = master
self.tree = ttk.Treeview(self.master, height=15)
self.tree.pack(fill='x')
self.btn = tk.Button(master, text='click', command=self.clickbtn)
self.btn.pack()
self.aMenu = tk.Menu(master, tearoff=0)
self.aMenu.add_command(label='Delete', command=self.delete)
self.aMenu.add_command(label='Say Hello', command=self.hello)
self.num = 0
# attach popup to treeview widget
self.tree.bind("<Button-3>", self.popup)
def clickbtn(self):
text = 'Hello ' + str(self.num)
self.tree.insert('', 'end', text=text)
self.num += 1
def delete(self):
print(self.tree.focus())
if self.iid:
self.tree.delete(self.iid)
def hello(self):
print ('hello!')
def popup(self, event):
self.iid = self.tree.identify_row(event.y)
if self.iid:
# mouse pointer over item
self.tree.selection_set(self.iid)
self.aMenu.post(event.x_root, event.y_root)
else:
pass
root = tk.Tk()
app=Main(root)
root.mainloop()
Version 2:
import tkinter as tk
from tkinter import ttk
class Main(tk.Frame):
def __init__(self, master):
master.geometry('500x350')
self.master = master
tk.Frame.__init__(self, master)
self.tree = ttk.Treeview(self.master, height=15)
self.tree.pack(fill='x')
self.btn = tk.Button(master, text='click', command=self.clickbtn)
self.btn.pack()
self.rclick = RightClick(self.master)
self.num = 0
# attach popup to treeview widget
self.tree.bind('<Button-3>', self.rclick.popup)
def clickbtn(self):
text = 'Hello ' + str(self.num)
self.tree.insert('', 'end', text=text)
self.num += 1
class RightClick:
def __init__(self, master):
# create a popup menu
self.aMenu = tk.Menu(master, tearoff=0)
self.aMenu.add_command(label='Delete', command=self.delete)
self.aMenu.add_command(label='Say Hello', command=self.hello)
self.tree_item = ''
def delete(self):
if self.tree_item:
app.tree.delete(self.tree_item)
def hello(self):
print ('hello!')
def popup(self, event):
self.aMenu.post(event.x_root, event.y_root)
self.tree_item = app.tree.focus()
root = tk.Tk()
app=Main(root)
root.mainloop()
from tkinter import *
root=Tk()
root.geometry("500x400+200+100")
class Menu_Entry(Entry):
def __init__(self,perant,*args,**kwargs):
Entry.__init__(self,perant,*args,**kwargs)
self.popup_menu=Menu(self,tearoff=0,background='#1c1b1a',fg='white',
activebackground='#534c5c',
activeforeground='Yellow')
self.popup_menu.add_command(label="Cut ",command=self.Cut,
accelerator='Ctrl+V')
self.popup_menu.add_command(label="Copy ",command=self.Copy,compound=LEFT,
accelerator='Ctrl+C')
self.popup_menu.add_command(label="Paste ",command=self.Paste,accelerator='Ctrl+V')
self.popup_menu.add_separator()
self.popup_menu.add_command(label="Select all",command=self.select_all,accelerator="Ctrl+A")
self.popup_menu.add_command(label="Delete",command=self.delete_only,accelerator=" Delete")
self.popup_menu.add_command(label="Delete all",command=self.delete_selected,accelerator="Ctrl+D")
self.bind('<Button-3>',self.popup)
self.bind("<Control-d>",self.delete_selected_with_e1)
self.bind('<App>',self.popup)
self.context_menu = Menu(self, tearoff=0)
self.context_menu.add_command(label="Cut")
self.context_menu.add_command(label="Copy")
self.context_menu.add_command(label="Paste")
def popup(self, event):
try:
self.popup_menu.tk_popup(event.x_root, event.y_root, 0)
finally:
self.popup_menu.grab_release()
def Copy(self):
self.event_generate('<<Copy>>')
def Paste(self):
self.event_generate('<<Paste>>')
def Cut(self):
self.event_generate('<<Cut>>')
def delete_selected_with_e1(self,event):
self.select_range(0, END)
self.focus()
self.event_generate("<Delete>")
def delete_selected(self):
self.select_range(0, END)
self.focus()
self.event_generate("<Delete>")
def delete_only(self):
self.event_generate("<BackSpace>")
def select_all(self):
self.select_range(0, END)
self.focus()
ent=Menu_Entry(root)
ent.pack()
root.mainloop()
Important Caveat:
(Assuming the event argument that contains the coordinates is called "event"): Nothing will happen or be visible when you call tk_popup(...) unless you use "event.x_root" and "event.y_root" as arguments. If you do the obvious of using "event.x" and "event.y", it won't work, even though the names of the coordinates are "x" and "y" and there is no mention of "x_root" and "y_root" anywhere within it.
As for the grab_release(..), it's not necessary, anywhere. "tearoff=0" also isn't necessary, setting it to 1 (which is default), simply adds a dotted line entry to the context menu. If you click on it, it detaches the context menu and makes it its own top-level window with window decorators. tearoff=0 will hide this entry. Moreover, it doesn't matter if you set the menu's master to any specific widget or root, or anything at all.