I have an application using tkinter where different actions are required on mouse click, double click and when the mouse is being moved. The following class works however I wonder if there is no easier- or tkinter builtin way of doing this?
from tkinter import Tk, Canvas
class MouseControl:
''' Class for mouse control to establish if there is a 'click',
'double click' or mouse is being moved '''
def __init__(self, aw):
self.double_click_flag = False
self.button_released_flag = False
self.aw = aw
self.aw.bind('<Button-1>', self.clicked) # bind left mouse click
self.aw.bind('<Double-1>', self.double_click) # bind double left clicks
self.aw.bind('<ButtonRelease-1>', self.button_released) # bind button release
self.aw.bind('<B1-Motion>', self.moved) # bring when mouse is moved
def clicked(self, event):
''' add a little delay before calling action to allow for double click
and button released to have occurred '''
self.double_click_flag, self.button_released_flag = False, False
self.aw.after(300, self.action, event)
def double_click(self, event):
''' set flag when there is a double click '''
self.double_click_flag = True
def button_released(self, event):
''' set flag when button is released '''
self.button_released_flag = True
def moved(self, event):
''' define action on when mouse is moved in this case just printing
the coordinates'''
print('mouse position is at ({:03}. {:03})'.
format(event.x, event.y), end='\r')
def action(self, event):
''' define action on click and double click in this case just printing
the event '''
if self.button_released_flag:
if self.double_click_flag:
print('double mouse click event')
else:
print('single mouse click event')
root = Tk()
window = Canvas(root, width=400, height=400, bg='grey')
mouse = MouseControl(window)
window.place(x=0, y=0)
window.mainloop()
1 Answer 1
Avoid wildcard imports
You should use import tkinter as tk instead
of from tkinter import Tk, Canvas
. This is a PEP8 recommendation where you can find the reason of this. Of course, in this case, you have to prefix your widgets with tk
. For instance: my_button = tk.Button(...)
Remove useless comments
To be honest, most of your comments (docstrings, to be exact) can be removed. Let us take an example:
def double_click(self, event):
''' set flag when there is a double click '''
self.double_click_flag = True
The docstring is just noisy. It does not bring any additional information when I read self.double_click_flag = True
. That comment just repeats what the instruction already tells me. Avoid repeating yourself, even through comments.
The same is true when it comes to inline comments. Example:
self.aw.bind('<Button-1>', self.clicked) # bind left mouse click
Use the main guard
Sometimes we want to run some code only if the program was used by itself and not when it was imported from another module, for this reason it is good to use if __name__ == "__main__"
:
def main():
root = tk.Tk()
window = tk.Canvas(root, width=400, height=400, bg='grey')
mouse = MouseControl(window)
window.place(x=0, y=0)
window.mainloop()
if __name__ == "__main__":
main()
Simplify the handlers
I find your handlers having unnecessary code. Simply take advantage of the button events themselves and get rid of all those unnecessary flags which are rather cumbersome in case you want to add more code in the future (because they play the role of global variables, which thing we do not like in programming).
Taking in consideration what has been said so far, here is your code cleaned:
import tkinter as tk
class MouseControl:
def __init__(self, canvas):
self.canvas = canvas
self.canvas.bind('<Button-1>', self.clicked)
self.canvas.bind('<Double-1>', self.double_click)
self.canvas.bind('<ButtonRelease-1>', self.button_released)
self.canvas.bind('<B1-Motion>', self.moved)
def clicked(self, event):
print('single mouse click event at ({}, {})'.format(event.x, event.y))
def double_click(self, event):
print('double mouse click event')
def button_released(self, event):
print('button released')
def moved(self, event):
print('mouse position is at ({:03}. {:03})'.format(event.x, event.y), end='\r')
def main():
root = tk.Tk()
window = tk.Canvas(root, width=400, height=400, bg='grey')
mouse = MouseControl(window)
window.place(x=0, y=0)
window.mainloop()
if __name__ == "__main__":
main()
-
\$\begingroup\$ Thanks for the input, will take it onboard. Still wondered if there is an easier way to makes the distinctions between 'click', 'double click' and 'mouse moving' in tkinter? It took me a while to figure it out especially the trick with the little delay and I couldn't find any explicit guidelines on the matter. \$\endgroup\$Bruno Vermeulen– Bruno Vermeulen2018年07月08日 06:18:02 +00:00Commented Jul 8, 2018 at 6:18
-
\$\begingroup\$ Check my edit @BrunoVermeulen \$\endgroup\$Billal BEGUERADJ– Billal BEGUERADJ2018年07月08日 08:09:25 +00:00Commented Jul 8, 2018 at 8:09
-
\$\begingroup\$ The issue I have with this version is that single_click is called every time, on double click and when mouse is moved, so if I want to do an action on single click it will do this action action also on double click and when mouse is moved. That is the reason I have to little delay to test is double click was occurring or mouse is being moved. \$\endgroup\$Bruno Vermeulen– Bruno Vermeulen2018年07月08日 08:15:30 +00:00Commented Jul 8, 2018 at 8:15
-
\$\begingroup\$ Yes, that is the caveat of implementing both single and double click (the corresponding bindings will both activate on double click, and their handlers execute) \$\endgroup\$Billal BEGUERADJ– Billal BEGUERADJ2018年07月08日 08:18:52 +00:00Commented Jul 8, 2018 at 8:18
-
\$\begingroup\$ Wildcard imports are
from ... import *
, notfrom ... import name1, name2
. There's nothing wrong with the latter (except perhaps not knowing where the name originally came from). \$\endgroup\$Daniel– Daniel2018年07月09日 15:48:46 +00:00Commented Jul 9, 2018 at 15:48