3
\$\begingroup\$

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()
asked Jul 8, 2018 at 5:25
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

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()
Daniel
4,6122 gold badges18 silver badges40 bronze badges
answered Jul 8, 2018 at 5:58
\$\endgroup\$
5
  • \$\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\$ Commented Jul 8, 2018 at 6:18
  • \$\begingroup\$ Check my edit @BrunoVermeulen \$\endgroup\$ Commented 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\$ Commented 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\$ Commented Jul 8, 2018 at 8:18
  • \$\begingroup\$ Wildcard imports are from ... import *, not from ... import name1, name2. There's nothing wrong with the latter (except perhaps not knowing where the name originally came from). \$\endgroup\$ Commented Jul 9, 2018 at 15:48

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.