I'm using QSlider in my GUI application in order to perform a heavy task after value changed in the QSlider. I'm doing that as follows.
self.slider.valueChanged.connect(self.value_changed) # Inside __init__() function
def value_changed(self): # Inside the class
# Do the heavy task
But I can't change the value of the slider smoothly because the heavy task is running every time I change the value.
What I need is to run the heavy task after the value changed but only if the value of the slider is not changing for a while.
I can't figure out how to do this in python. Any help..?
I am no expert, and I arrive long after the battle, but i needed a thing like that too, and i don't understand a thing about the timers. It worked fine for one slider, but i needed 3. So I came up with this solution : when the slider is pressed, I disconnect the valueChanged slot, and when the slider is released, I reconnect it and I throw a valueChanged signal, like this :
self.sldAZap.valueChanged.connect(self.sliderChanged)
self.sldAZap.sliderPressed.connect(self.sldDisconnect)
self.sldAZap.sliderReleased.connect(self.sldReconnect)
def sldDisconnect(self):
self.sender().valueChanged.disconnect()
def sldReconnect(self):
self.sender().valueChanged.connect(self.sliderChanged)
self.sender().valueChanged.emit(self.sender().value())
def sliderChanged(self):
print(self.sender().objectName() + " : " + str(self.sender().value())
With this solution, there is no delay between the moment the mouse is released and the execution of the code, and the code is executed just one time.
I hope I am clear enough and it may help someone.
You can use startTimer/killTimer to delay your task:
class Foo(QWidget):
def __init__(self):
super().__init__()
self.timer_id = -1
self.slider = QSlider(self)
self.slider.setMinimum(0)
self.slider.setMaximum(100)
self.slider.valueChanged.connect(self.value_changed)
def timerEvent(self, event):
self.killTimer(self.timer_id)
self.timer_id = -1
heavy_task()
def value_changed(self):
if self.timer_id != -1:
self.killTimer(self.timer_id)
self.timer_id = self.startTimer(3000)
so, as can you see we restart timer every time when
user something change, so if 3000 millseconds not expires
since last change heavy_task not run,
but any way it will be running in main thread, so for some
time interface freeze for user, so you should use
QThread in timerEvent to not have interface that not freeze during
heavy_task execution.
Related
I'm making a GUI to play videos using the python bindings for VLC, and I've run into a snag with the progress slider. Due to VLC's extreme inaccuracy and inconsistency when it comes to reporting a video's progress, I've been faking the QSlider by simply incrementing it + updating the rest of the UI (which includes multiple QLabels, QLineEdits, and QSpinBoxes) once per frame in a separate thread (not a QThread).
However, something strange has been happening: eventually, the entire UI "freezes"... except the video continues playing, the console continues outputting, and the UI instantly jumps back to life after manually interacting with it (such as by pressing the pause button). Qt is running and keeping track of the current state of the UI, just without painting anything.
I've tried manually updating the UI, manually repainting the UI (which always crashes...?), auto-resizing the UI, running processEvents(), running QTest.qWait(1), but nothing has worked. Using a QTimer to increment the progress bar, however, DOES prevent the UI from freezing, which confirms I'm not crazy. Of course, I can't actually use a QTimer though, since even a Qt.PreciseTimer results in... a very imprecise timer.
I've read that this is likely caused by Qt mistakenly trying to optimize my code by clumping the rapid updates into one, which would explain why the UI seemingly stops being painted despite clearly being active under the hood.
Though I have a feeling that the solution is probably something obvious (maybe even a different implementation of something I mentioned trying above) that doesn't require a code sample, here's a significantly stripped down version of what I'm currently working with:
def setup(self):
self.progress_thread = Thread(target=self.update_slider_thread, daemon=True)
self.progress_thread.start()
def update_slider_thread(self):
current_frame = self.progress_slider.value
is_playing = self.vlc.player.is_playing
update_progress = self.update_progress
while True:
start = time.time()
while not is_playing():
sleep(0.01)
start = time.time()
while is_playing() and not self.lock_progress_updates:
frame_multiplier = self.frame_rate
next_frame = current_frame() + 1
if next_frame <= self.frame_count: # don't update if we're at the end
update_progress(next_frame)
delay = self.delay # frame rate / total frames
try:
sleep(0.0001) # forces time.time() to update (otherwise we get the same "time" for several loops)
sleep(delay - (time.time() - start) - 0.00075) # 0.00075 to account for executing this line of code
except: pass
finally:
start = time.time()
continue
def update_progress(self, frame):
if self.get_player_state() == vlc.State.Ended:
if self.ready_to_restart and self.vlc.is_paused:
self.restart_video(frame=frame, pause=True)
self.current_time = round(self.duration * (frame / self.frame_count), 2)
h, m, s, ms = get_hms(self.current_time)
current_time_string = f'{m:02}:{s:02}.{ms:02}'
# these are just aliases for things like setValue() for each widget (for performance)
self.set_progress_slider(frame)
if not self.current_time_text_has_focus(): self.set_current_time_text(current_time_string)
self.set_hour_spin(h)
self.set_minute_spin(m)
self.set_second_spin(s)
self.set_frame_spin(frame)
Uhhhhhhhhhhhhhhhhh Ok so some basics. 1st of all, any interaction with widgets have to happen in main thread. So if you are changing slider value/etc. from daemon/worker thread. Then you are messing up Qt insides.
I would suggest that you use signals/slots. Here is a small example of it
class sliderUpdate(QObject):
handleUpdate = Signal(int)
def __init__(self):
print "Current thread (Should be main thread ) : " QThread.currentThread()
# Lets conect our signal to function/slot.
self.handleUpdate.connect(self.doUpdate,Qt.QueuedConnection) # We force it in to QueuedConnection so that Qt pass the data from worker thread to Main thread.
self.slider = QSlider()
def doUpdate(self,val):
print "Current thread (Should be main thread ) : " QThread.currentThread()
self.slider.setValue(val)
def processInThread(self):
print "Current thread (Should be worker thread ) : " QThread.currentThread()
self.handleUpdate.emit(10)
2nd. If VLC is sending updates A LOT per second. Then you may be bombarding Qt with refresh requests & lagging app. I would suggest implementing some sort of... delayed report... A sudo example :
timer = QElapsedTimer()
timer.start()
...
...
if timer.elapsed()>500:
emit new Value:
timer.restart()
else:
skip, emit on next call if timeout over 500 ms.
I'm currently trying to build a security system on my raspberry pi. I have 9 buttons to enter a code. Once the code is entered u can press "arm" to arm the system. Then a function checks a PIR sensor for movement. When movement is detected an alarm should go off, for which I need time.sleep.
So my actual problem is, with time.sleep I block the programm for the time its sleeping, thus i cant disarm the system during alarm mode.
My idea so far was just to put everything into threads. But without success so far. Are there any better solutions to time.sleep?
You don't need to use threads. With tkinter you can easily schedule a function to run every couple of seconds in the main thread to check the sensor or do any other work that you want.
Here's a short contrived example, which will work just fine assuming that the sensor check doesn't take more than a couple hundred milliseconds. It's not exactly how I would do it, but it illustrates how you can have some function run periodically without having to put the UI to sleep.
import tkinter as tk
class App():
def __init__(self):
self._job_id = None
self.init_gui()
def init_gui(self):
self.root = tk.Tk()
self.button = tk.Button(self.root, width=6, text="Arm", command=self.arm)
self.button.pack(padx=20, pady=20)
def start(self):
self.root.mainloop()
def arm(self):
self.button.configure(text="Disarm", command=self.disarm)
self.poll()
def disarm(self):
self.button.configure(text="Arm", command=self.arm)
if self._job_id:
self.root.after_cancel(self._job_id)
def poll(self):
# ... check the sensor here ...
movement = True
if movement:
print("beep!")
self._job_id = self.root.after(2000, self.poll)
app = App()
app.start()
If your problem is simply looking for a better method to use then time.sleep, you could consider using time.time rather than time.sleep and then use checks to see what actions should occur. This would avoid using time.sleep which blocks all events (including your GUI). As a quickly written example to demonstrate this idea:
from time import time
millis = lambda: int(time() * 1000)
def updateAlarm(lastTime, beepRate, currentState):
now = millis()
if now > (lastTime + beepRate):
return (not(currentState), now)
return (currentState, now)
last = millis() #set the first time for the alarm
rate = 2000 # change state every two seconds
state = False # the alarm is currently off
while True: # just for demonstration purposes, while True: won't work inside of a tkinter GUI
change = updateAlarm(last, rate, state)
if change[0] != state: # if the state has changed, update it and print
state = change[0]
last = change[1]
print(state)
Depending on your implementation, this may make more sense, especially if you are not using tkinter. Personally, I think Bryan's solution is much more elegant, especially since it doesn't require constantly checking the alarm to see if it needs to update.
The code below is a stripped down version (for clarity reasons) of a small application I am working on; an application for spelling words for children.
The problem
The problem I am having is in the function flash_correct(); its purpose is to show a word for 5 seconds, then hide again.
I must have a silly blind spot, but no matter where I put the time.sleep(5), the function starts with the break of 5 seconds, while the entry: self.entry2 never shows up:
Without the time.sleep(5) however, it shows up correctly:
Where is my blind spot?
The (stripped down) code:
#!/usr/bin/env python3
from gi.repository import Gtk, Pango, Gdk
import subprocess
import time
class InterFace(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Woorden raden")
maingrid = Gtk.Grid()
self.add(maingrid)
maingrid.set_border_width(10)
self.entry2 = Gtk.Entry()
self.entry2.set_size_request(500,60)
self.entry2.set_child_visible(False)
self.entry2.modify_font(Pango.FontDescription('Ubuntu 30'))
maingrid.attach(self.entry2, 0, 4, 4, 1)
quitbutton = Gtk.Button("Stop", use_underline=True)
quitbutton.modify_font(Pango.FontDescription('Ubuntu 20'))
quitbutton.connect("clicked", self.on_close_clicked)
maingrid.attach(quitbutton, 3, 7, 1, 1)
showword_button = Gtk.Button("↺", use_underline=True)
showword_button.modify_font(Pango.FontDescription('Ubuntu 25'))
showword_button.connect("clicked", self.flash_correct)
showword_button.set_size_request(60,20)
maingrid.attach(showword_button, 0, 6, 1, 1)
def flash_correct(self, button):
# the time.sleep(5) seems to take place at the beginning
# no matter in which order I set the commands
self.entry2.set_text("Monkey")
self.entry2.set_child_visible(True)
time.sleep(5)
self.entry2.set_child_visible(False)
def on_close_clicked(self, button):
Gtk.main_quit()
window = InterFace()
window.connect("delete-event", Gtk.main_quit)
window.set_default_size(330, 330)
window.set_resizable(False)
window.show_all()
Gtk.main()
You can use time.time to hide for roughly 5 seconds calling Gtk.main_iteration() in the loop to avoid your app becoming unresponsive.
def hide(self, time_lapse):
start = time.time()
end = start + time_lapse
while end > time.time():
Gtk.main_iteration()
def flash_correct(self, button):
# the time.sleep(5) seems to take place at the beginning
# no matter in which order I set the commands
self.entry2.set_text("Monkey")
self.entry2.set_child_visible(True)
self.hide(5)
self.entry2.set_child_visible(False)
There is a good explanation in the pygtk faq 7. How can I force updates to the application windows during a long callback or other internal operation?
If you have a long-running callback or internal operation that tries to modify the application windows incrementally during its execution, you will notice that this doesn't happen; the windows of your app freeze for the duration.
This is by design: all gtk events (including window refreshing and updates) are handled in the mainloop, and while your application or callback code is running the mainloop can't handle window update events. Therefore nothing will happen in the application windows.
The trick here is to realize where your operation can take a while to return, or where it is dynamically changing the window contents, and add a code fragment like this wherever you want an update forced out:
while gtk.events_pending():
gtk.main_iteration(False)
This tells gtk to process any window events that have been left pending. If your handler has a long loop, for instance, inserting this snippet as part of the loop will avoid it hanging the window till the callback has finished.
More eloquently, in the words of the great Malcolm Tredinnick, 'this requires using what should be called "Secret Technique #1 For Making Your Application Look Responsive"(tm):
Adding while gtk.events_pending(): may be no harm also.
It would be better to use a timer that integrates with the main loop, rather than busy-waiting until the time has elapsed. Luckily there is just such a facility in GLib:
def flash_correct(self, button):
self.entry2.set_text("Monkey")
self.entry2.set_child_visible(True)
GLib.timeout_add_seconds(5, self.flash_end)
def flash_end(self):
self.entry2.set_child_visible(False)
return GLib.SOURCE_REMOVE
So I am using wxpython to make a GUI for a program. I have also embedded matplotlib graphs in this program.
My problem is when I try to use draw() to update the plot continuously, my program becomes unresponsible, however the matplotlib graph is still updating.
Here is part of my code, and how I execute all this,
def update(self, e):
if self.liveMode:
self.tune.plot(); # new plot
self.canvas.draw();
self.startLive(e);
def startLive(self, e):
self.liveMode = False;
refFreq = 500e6 / 80;
qx = self.pv1.get();
qy = self.pv2.get();
self.tune.newWorkingpoint(refFreq/qx, refFreq/qy);
self.tune.liveUpdate();
time.sleep(0.3);
self.update(e);
When the 'live mode' button is pressed in my program the 'update' method gets called.
I plot all the background lines, and then repeatedly get new point values from elsewhere and plot this as a circle on the graph and update with the draw() method. Of course since I am essentially in a continuous loop, nothing else will work until this process is finished. My goal was to just put a 'stop' button to stop this live plotting loop, but since everything becomes unresponsive when matplotlib is drawing, no buttons are clickable, and I have to essentially stop compiling to stop the program.
Is there anyway around this? I have looked into threading, but I get the same problems with this.
EDIT: ----------------------------------------------------------------------------------------------------------------------------I have got it working using this page as a guide. However, after a few seconds I get this fatal error and my program crashes.
'python2.7: Fatal IO error 11 (Resource temporarily unavailable) on X server :0.0.'
Here is the method I implemented. When a start button is pressed, 'startlive' starts, and when a stop button is pressed, 'stoplive' starts. Everything works fine, and my matplotlib graphs are updated properly without my wxpython GUI becoming unresponsive.
The problem is after a few seconds I get the fatal error and my program crashes. I believe this is most likely tied to the canvas.draw() method updating matplotlib inside a thread. Any ideas on how to get around this problem?
def startLive(self, e):
self.livemode.change(True);
self.worker = CountingThread(self.panel,self.tune,self.canvas,self.livemode, self.pv1, self.pv2);
self.worker.start();
def stopLive(self, evt):
self.livemode.change(False);
class CountingThread(threading.Thread):
def __init__(self, parent, tune, canvas, livemode, pv1, pv2):
threading.Thread.__init__(self)
self._parent = parent;
self._tune = tune;
self._canvas = canvas;
self._livemode = livemode;
self._pv1 = pv1;
self._pv2 = pv2;
def run(self):
refFreq = 500e6 / 80;
i=0;
while self._livemode.get():
self._tune.newWorkingpoint(refFreq / self._pv1.get(), refFreq / self._pv2.get());
self._tune.liveUpdate();
self._canvas.draw();
time.sleep(0.2);
i+=1;
print(i)
class livemode:
livemode=False;
def __init__(self):
self.livemode = False;
def change(self, livemode):
self.livemode = livemode;
def get(self):
return self.livemode;
--------------------------------EDIT---------------------------------
I solved the problem if anyone is interested,
In the threading run method, I removed the loop and simply had one instance of 'updating'. After the update, wx.PostEvent is called which calls a method back in the main class containing the wx.App which updates the plot by calling draw(). After this, the threading is restarted again and the same process repeats. My GUI continues to work and the plotting speed is still really fast. The important methods are below,
Threading.start() is called and the below exectures
def run(self):
refFreq = 500e6 / 80;
self._tune.newWorkingpoint(refFreq / self._pv1.get(), refFreq /self._pv2.get());
self._tune.liveUpdate();
evt = CountEvent(myEVT_PLOT, -1);
wx.PostEvent(self._parent, evt);
wx.PostEvent calls the following new method in main wx.App class,
def continueLive(self, evt):
if self.livemode.get():
self.canvas.draw();
self.startLive(evt)
The plot is updated and the whole process starts over.
This methods keeps the GUI responsive and the plotting speed does not slow down.
I think it might be a stack issue. Your update loop is recursive and the stack has a limited size (every function call gets pushed to the stack and removed once the function returns).
You should change it to a iterative version.
def update(self, e):
if self.liveMode:
self.tune.plot(); # new plot
self.canvas.draw();
#self.startLive(e); # causes endless recursion
def startLive(self, e):
while (self.isInLiveMode): # some flag here that gets set to False once the mode is deactivated
self.liveMode = False;
refFreq = 500e6 / 80;
qx = self.pv1.get();
qy = self.pv2.get();
self.tune.newWorkingpoint(refFreq/qx, refFreq/qy);
self.tune.liveUpdate();
time.sleep(0.3);
self.update(e);
Well i assume self.liveMode should be that flag.
Hope this helps, LG
Daniel
So I've created a class, which contains GUI using wxPython.
How do you make it so that it refreshes itself say every minute?
For things that happen on intervals, use a Timer. From WxPyWiki:
def on_timer(event):
pass # do whatever
TIMER_ID = 100 # pick a number
timer = wx.Timer(panel, TIMER_ID) # message will be sent to the panel
timer.Start(100) # x100 milliseconds
wx.EVT_TIMER(panel, TIMER_ID, on_timer) # call the on_timer function
For some reason, this code didn't work when I tried it. The reason was the timer had to be a class member. If you put that code into the init() method and add self. before timer, it should work. If it doesn't, try making on_timer() a class member too. -- PabloAntonio
I've had problems closing my frame, when there was a Timer running.
Here's how I handled it:
def on_close(event):
timer.Stop()
frame.Destroy()
wx.EVT_CLOSE(frame, on_close)
I don't work with wxPython, but if there is a method called refresh or something alike, you could start a Thread calling that method every minute.
from threading import Thread
from time import sleep
def refreshApp(app, timespan):
while app.isRunning:
app.refresh()
sleep(timespan)
refresher = Thread(target=worker, args=(myAppInstance, 60))
refresher.start()
EDIT: fixed code so it fits into PEP8
As Niklas suggested I think you're looking for the Refresh() method: http://wxpython.org/docs/api/wx.Window-class.html#Refresh .