Python pyplot: Handling double-click event catches also the first click event - python-3.x

I wrote a code that handles different events for both - mouse single-click and double click.
The problem is that each time that user double-click the mouse it triggers also single-click and after that trigger the double-click event.
I want that double-click will trigger only one event!! the double-click event.
Any suggestions?
Thanks

As linked by #ThomasKühn, the answer is to create a software debounce. There are several ways to go about it, and the solution probably depends on your application (are you using a GUI, what backend, etc.) To be as agnostic as possible, I've implemented my solution using a one-shot thread from the threading module.
import threading
import matplotlib.pyplot as plt
DEBOUNCE_DUR = 0.25
t = None
def on_press(event):
global t
if t is None:
t = threading.Timer(DEBOUNCE_DUR, on_singleclick, [event])
t.start()
if event.dblclick:
t.cancel()
on_dblclick(event)
def on_dblclick(event):
global t
print("You double-clicked", event.button, event.xdata, event.ydata)
t = None
def on_singleclick(event):
global t
print("You single-clicked", event.button, event.xdata, event.ydata)
t = None
fig, ax = plt.subplots()
cid = fig.canvas.mpl_connect('button_press_event', on_press)
plt.show()

As I was looking for a solution for a embedded Matplotlib canvas in Tkinter, I came up for another solution (since the accepted answer doesn't work quite well in that case) here is a codesnipped which will hopefully help others struggling with tkinter and matplotlib:
def _on_click_debounce(self,event):
if self._job is None:
self._job = root.after(self.DEBOUNCE_DUR, lambda: self._on_click(event))
if event.dblclick:
root.after_cancel(self._job)
self._job = None
self._on_dblclick(event)
def _on_dblclick(self,event):
print('dblclick!')
def _on_click(self,event):
print('singleclick!')
self._job = None
The function _on_click_debounce gets handed over to the matplotlib eventhandling (fig.canvas.mpl_connect('button_press_event', _on_click_debounce)) and root is just the global root = tkinter.Tk() of tkinter

Related

PyQT5 slot with Matplotlib has no effect

I'm trying to implement a slot method that will clear a matplotlib figure that is part of QtWidget. I have tried with both Python 3.6 and 3.5 on windows 7 and 10 and i get same behaviour.
My issue is that i can perfectly clear the figure when calling the wipe method from the main block of code or within the class drawing the figure. But whenever this very same method is called as a slot to a PyQT signal, slot is actually activated but the call to fig.clf() does not clear the figure.
The code below shows the issue :
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QApplication,QMainWindow, QSizePolicy,QWidget,QVBoxLayout, QPushButton
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
class GraphPopulate(FigureCanvas):
def __init__(self, parent=None):
self.fig = Figure(figsize=(6,4))
self.ax = self.fig.add_axes([0.07, 0.16, 0.95, 0.95]) # fraction of figure size #
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
x = [2000, 2001, 2002, 2003, 2004]
y = [10, 20, 30, 40, 50]
self.plt = self.ax.plot(x, y)
# self.wipe() # OK This one works as well
# self.plt.close(self.fig) # close the app, does not only clear figure
def wipe(self):
print('wipe start')
# del self.fig # close the app on second call, does not only clear figure !!
# self.plt.close(fig) # close the app, does not only clear figure
rc = self.fig.clf()
print('return code from clf() '+ str(rc))
print('wipe stop')
if __name__ == '__main__':
# create main window
app = QApplication(sys.argv)
MainWindow = QMainWindow()
MainWindow.resize(800, 600)
# create widget to be used by MathPlotlib
WidgetMatplot = QWidget(MainWindow)
WidgetMatplot.setGeometry(QRect(10, 40, 500, 500))
# create Push Button with clicked signal linked to GraphPopulate.wipe() slot
button = QPushButton(MainWindow)
button.setText("Push Me !")
# add widget to vertical box layout
hbox = QVBoxLayout(MainWindow)
hbox.addWidget(WidgetMatplot)
hbox.addWidget(button)
g = GraphPopulate(WidgetMatplot)
button.pyqtConfigure(clicked=g.wipe) # NOT OK !!!!!!! g.wipe is triggered as prints statements show
# on console but self.fig.clf() has no effect
MainWindow.show()
# g.wipe() # OK
sys.exit(app.exec_())
I've put comments on statements that if uncommented are actually clearing the figure or closing the whole app.
Looks like the "matplotlib context" is not the same when called from the signa-slot connect than from other calls.
May be i miss something trivial. If so sorry for that but i couldn't find any direction to investigate by myself...so i rely on you guys to spot it out.
Thanks - Thibault
In your code the figure is successfully cleared. You just don't see it because nobody told the figure that it needs to be redrawn.
If you redraw the figure, you'll see that it is indeed empty.
def wipe(self):
self.fig.clf()
self.fig.canvas.draw_idle()

"RuntimeError: Calling Tcl from different appartment" tkinter and threading

I want to implement GUI using threading and tkinter (python 3.6).
When I run GUIExecution.py, the following error occurs.
"RuntimeError: Calling Tcl from different appartment" on self.root.mainloop() in base_gui_class.py
I am implementing it on a class basis, and the three code files are as follows.
The executable file is GUIExecution.py.
I spent a lot of time trying to fix the error, but I have not been able to fix it yet.
Please give a lot of advice.
Additionally, if I run the following code in a python2 environment, it works fine without error.
GUIExecution.py
from base_gui_class import *
from base_class import *
speed = 1000
height = 500
width = 700
base_model = base_class()
gui = base_gui_class(base_model, speed, height, width)
base_model.visualize()
base_class.py
class base_class():
genes = []
dicLocations = {}
gui = ''
best = ''
time = 0
def __init__(self):
pass
def visualize(self):
if self.gui != '':
self.gui.start()
def registerGUI(self, gui):
self.gui = gui
base_gui_class.py
import threading
import tkinter as tk
import math
import threading
import time
class base_gui_class(threading.Thread):
root = ''
canvas = ''
speed = 0
base_model = ''
def __init__(self, base_model, speed, h, w):
threading.Thread.__init__(self)
self.base_model = base_model
base_model.registerGUI(self)
self.root = tk.Tk()
self.canvas = tk.Canvas(self.root, height=h, width=w)
self.canvas.pack()
self.root.title("Test")
self.speed = 1 / speed
def run(self):
self.root.mainloop()
def update(self):
time.sleep(self.speed)
width = int(self.canvas.cget("width"))
height = int(self.canvas.cget("height"))
self.canvas.create_rectangle(0, 0, width, height, fill='white')
def stop(self):
self.root.quit()
To a very good first and second approximation, the core of Tk is single threaded. It can be used from multiple threads, but only by initialising it separately in each of those threads. Internally, it uses thread-specific variables extensively to avoid the need for major locking (that is, it has nothing like a big Global Interpreter Lock) but that means you must not cheat. Whatever thread initialises a Tk context must be the only thread that interacts with that Tk context. This includes loading the Tkinter module so you are effectively restricted to using Tkinter from your main thread only; working around this is serious expert's-only stuff.
I recommend that you make your worker threads make changes to your GUI by posting events to it using a queue (or otherwise interlock with critical sections and condition variables, though I find queues easier in practice).
pip install tkthread
#call the function which shows error like this:
tkthread.call_nosync(yourfunction)
this tkthread library handles all the threading internally by itself.
I recommend you reading the documentation of this library:https://pypi.org/project/tkthread/ .

How to create GUI objects one by one with Tkinter

I like to create an object per second and make the show up one by one. However, the code below wait for 3 seconds and show them all at the same time.
from tkinter import *
import time
def create():
for i in range(3):
r4=Radiobutton(root, text="Option 1"+str(i), value=1)
r4.pack( anchor = W )
time.sleep(1)
root = Tk()
create()
root.mainloop()
Your code, as is, creates a one object per second as you desired it, but this objects need to be shown, and they're shown when code flow reaches the mainloop. Hence, for observer, it looks like there're no objects at all after one second.
Of course, you can use sleep and update, but beware - sleeping leads to unresponsive window, so it's OK option (to be honest - not OK at all), if your application isn't drawn and you're outside of mainloop, but if it's not - prepare for a "frozen" window, because GUI can redraw himself only in mainloop (an event loop, you can reach it with update as well) and sleep blocks this behaviour.
But there's a good alternative, the after method, take a look on it!
And there's a snippet, so you can see the difference:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import time
def create_with_sleep():
for _ in range(3):
tk.Radiobutton(frame_for_sleep, text="Sleep Option").pack(anchor='w')
time.sleep(int(time_entry.get()))
root.update()
def create_with_after(times=3):
if times != 0:
tk.Radiobutton(frame_for_after, text="After Option").pack(anchor='w')
times -= 1
root.after(int(time_entry.get()) * 1000, lambda: create_with_after(times))
root = tk.Tk()
test_yard_frame = tk.Frame(root)
frame_for_sleep = tk.Frame(test_yard_frame)
frame_for_after = tk.Frame(test_yard_frame)
test_yard_frame.pack()
frame_for_sleep.pack(side='left')
frame_for_after.pack(side='left')
button_frame = tk.Frame(root)
button_for_sleep = tk.Button(button_frame, text='Create 3 radiobuttons with sleep+update', command=create_with_sleep)
button_for_after = tk.Button(button_frame, text='Create 3 radiobuttons with after', command=create_with_after)
button_frame.pack()
button_for_sleep.pack(side='left')
button_for_after.pack(side='left')
time_label = tk.Label(root, text='Time delay in seconds:')
time_label.pack(fill='x')
time_entry = tk.Entry(root)
time_entry.insert(0, 1)
time_entry.pack(fill='x')
root.mainloop()
With 1 seconds delay there's no much difference, but you can try to increase delay to understand why after option is preferable in general.
You can use update to call a refresh on your objects.
In use, you would have to add the line root.update() in your for loop.

How can I receive a corresponding percentage when progressbar is clicked python3 gtk3

I am writing a very simple and lightweight remote control for Kodi in python3 and GTK3. I was already able to add the progress bar that gets updated as playback progresses, but I would like to be able to click on a specific part of the progress bar and seek to corresponding part of the video.
Can someone please help me or at least orient me a little how can I achieve it? For now I am working with percentage, as it is much more simple than working with time.
The only thing I need for now is to get 0.33 as output when I click on 1/3 of the progress bar (obviously on the whole length of the progress bar accordingly)
I guess I have to be missing something very simple, but I cannot find what it is. I found one example of using EventBox, but it only informed about a mouse movement gesture, and was not able to give the specific number.
The easiest solution probably is to switch to the Gtk.Scale, because it already supports everything you need. A simple scale that prints it value on change would look like this (in python 2.7):
class GtkWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
min_val = 0.0
max_val = 100.0
adjustment = Gtk.Adjustment.new(0.0, min_val, max_val, 1.0, 10.0, 10.0)
scale = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, adjustment)
scale.connect("value-changed", self.on_value_changed)
self.add(scale)
def on_value_changed(self, scale):
print scale.get_value()
win = GtkWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
Finally I came up with an answer for python3 and GTK3.
I posted the whole code here: https://github.com/elpraga/kodi-cli
I will post the relevant bits here:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
import subprocess
...
self.progressbar = Gtk.ProgressBar(text="Kodi is not playing at the moment")
self.progresseventbox = Gtk.EventBox()
self.progressbar.set_show_text(True)
...
grid.attach(self.youtube_entry, 0, 0, 4, 1)
grid.attach_next_to(self.progresseventbox, self.youtube_entry, Gtk.PositionType.BOTTOM, 4, 1)
self.progresseventbox.add(self.progressbar)
...
self.progresseventbox.connect("button-press-event", self.on_mouse_click)
...
def on_mouse_click(self, widget, event):
width = self.progresseventbox.get_allocated_width()
percentage = event.x / width * 100
percentage = str(percentage)
output = (subprocess.check_output(["kodi-cli", "-g", percentage]))
# Let's get some response from kodi-cli
output = (subprocess.check_output(["kodi-cli", "-g"]))
output = output.decode('ascii')
# http://stackoverflow.com/questions/1798465/python-remove-last-3-characters-of-a-string
self.output_text.set_text(output)
...
Screenshot of the clickable progress bar I used after all

PyQt: How to use a Qthread to make process stoppable if it´s running too long

I have read a lot on threads, but I really need help with this one:
I have a PyQt Main GUI that runs an optimization with scipy.minimize...
As I cannot not make an example of this I use a "placeholder" process
to show what my problem is.
I want to let the Main GUI remain stoppable by the User, if the Process takes too long to give
a result.
My working example is this one, where I use an integration with sympy
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt
import time, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class IntegrationRunner(QObject):
'Object managing the integration'
def __init__(self):
super(IntegrationRunner, self).__init__()
self._isRunning = True
def longRunning(self):
# reset
if not self._isRunning:
self._isRunning = True
print("preparing Integration")
#this is known to be time consuming and returning not the right integral
#this is a placeholder for a "time consuming" operation that sould
#be handled by a thread outside the main GUI
#imagine an optimization process or any other time consuming operation
#that would freeze the Main GUI
t=sp.symbols('t')
exp=sp.sqrt((3*t+1)/t)
sol=sp.integrate(exp,t)
print(sol)
print('finished...')
def stop(self):
self._isRunning = False
#this is displayed when the "stop button" is clicked
#but actually the integration process won´t stop
print("Integration too long - User terminated")
class SimulationUi(QDialog):
'PyQt interface'
def __init__(self):
super(SimulationUi, self).__init__()
self.goButton = QPushButton('Run Integration')
self.stopButton = QPushButton('Stop if too long')
self.layout = QHBoxLayout()
self.layout.addWidget(self.goButton)
self.layout.addWidget(self.stopButton)
self.setLayout(self.layout)
self.simulThread = QThread()
self.simulThread.start()
self.simulIntegration = IntegrationRunner()
self.simulIntegration.moveToThread(self.simulThread)
#self.simulIntegration.stepIncreased.connect(self.currentStep.setValue)
# call stop on Integr.Runner from this (main) thread on click
self.stopButton.clicked.connect(lambda: self.simulIntegration.stop())
self.goButton.clicked.connect(self.simulIntegration.longRunning)
if __name__ == '__main__':
app = QApplication(sys.argv)
simul = SimulationUi()
simul.show()
sys.exit(app.exec_())
After clicking the "start" button and the "stop" button before the integration stops automatically
i get this output:
>>preparing Integration
>>Integration too long - User terminated
>>Integral(sqrt((3*t + 1)/t), t)
>>finished...
This isn´t exactly my problem but I want to understand how I can use a thread where I can put
time consuming calculations and stop them in order to try other "parameters" or "initial guesses"
when using something like Scipy Minimize
Is it even possible to stop an iterative function from "outside" and restart it without getting into "non responding"?
Any help to improve here is appreciated.
I took these example here as a guideline
how-to-signal-from-a-running-qthread-back-to-...
and pastebin

Resources