Handling GTK Objects on Threads in Python - python

i'm creating a application in Python, that uses GTK to build the UI, and i'm little confuse about handle GTK Objects on Threads, for example, the GtkProgressBar object.
Here is the context:
I try to do a download on a Main Thread, and i add a GObject.timeout_add to pulse the bar until the Download ends. But after the first pulse the UI froze.
Until there OK, the Thread froze until the download is complete, so any component will be updated. Solution: Create a new Thread.
I've created this new thread to make the Download, and other things. On this thread, i receive the progress bar to do the updates. But while the download is running and i add the GObject.timeout_add to pulse the bar, the UI froze again. New Solution: Create a third thread.
So my threads is looking lke this:
Main-Thread
'---- Thread 1
'------Thread 2
So, on Thread 1, i make another things and update the UI, while in Thread 2 i make the download and add the GObject.timeout_add and in there i can update the progress bar. And in Thread 1 i join the Thread 2
I handle the Gtk Objects using GObject.idle_add function.
But i'm very confuse about why the download and the update of the progress bar works well on the Thread 2 and not on Thread 1
Someone can explain to me why that's happen, or if i miss a something about handle GTK objects.
Thank you

If you block the Gtk main loop, the window and the widgets become unresponsive. That's why you cannot make the download in the main thread. Updating the Gtk.ProgressBar from the download thread could work depending on how you implemented it.
This is one way to download something and have a working ProgressBar:
from gi.repository import Gtk, GLib
import threading
import urllib.request
class Window(Gtk.Window):
def __init__(self):
super().__init__()
self.connect('delete-event', Gtk.main_quit)
self.progress = Gtk.ProgressBar()
self.add(self.progress)
self.show_all()
# Start download
self.thread = threading.Thread(target=self.download)
self.thread.start()
GLib.timeout_add_seconds(1, self.download_pulse)
def download(self):
urllib.request.urlretrieve('<link>')
def download_pulse(self):
if self.thread.is_alive():
self.progress.pulse()
return True
return False
win = Window()
Gtk.main()

Related

Qthread is still working when i close gui on python pyqt

my code has thread, but when i close the gui, it still works on background. how can i stop threads? is there something stop(), close()?
i dont use signal, slots? Must i use this?
from PyQt4 import QtGui, QtCore
import sys
import time
import threading
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.kac_ders=QtGui.QComboBox()
self.bilgi_cek=QtGui.QPushButton("Save")
self.text=QtGui.QLineEdit()
self.widgetlayout=QtGui.QFormLayout()
self.widgetlar=QtGui.QWidget()
self.widgetlar.setLayout(self.widgetlayout)
self.bilgiler=QtGui.QTextBrowser()
self.bilgi_cek.clicked.connect(self.on_testLoop)
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.widgetlar)
self.analayout=QtGui.QVBoxLayout()
self.analayout.addWidget(self.text)
self.analayout.addWidget(self.bilgi_cek)
self.analayout.addWidget(self.bilgiler)
self.centralWidget=QtGui.QWidget()
self.centralWidget.setLayout(self.analayout)
self.setCentralWidget(self.centralWidget)
def on_testLoop(self):
self.c_thread=threading.Thread(target=self.kontenjan_ara)
self.c_thread.start()
def kontenjan_ara(self):
while(1):
self.bilgiler.append(self.text.text())
time.sleep(10)
app = QtGui.QApplication(sys.argv)
myWidget = Main()
myWidget.show()
app.exec_()
A few things:
You shouldn't be calling GUI code from outside the main thread. GUI elements are not thread-safe. self.kontenjan_ara updates and reads from GUI elements, it shouldn't be the target of your thread.
In almost all cases, you should use QThreads instead of python threads. They integrate nicely with the event and signal system in Qt.
If you just want to run something every few seconds, you can use a QTimer
def __init__(self, parent=None):
...
self.timer = QTimer(self)
self.timer.timeout.connect(self.kontenjan_ara)
self.timer.start(10000)
def kontenjan_ara(self):
self.bilgiler.append(self.text.text())
If your thread operations are more computationally complex you can create a worker thread and pass data between the worker thread and the main GUI thread using signals.
class Worker(QObject):
work_finished = QtCore.pyqtSignal(object)
#QtCore.pyqtSlot()
def do_work(self):
data = 'Text'
while True:
# Do something with data and pass back to main thread
data = data + 'text'
self.work_finished.emit(data)
time.sleep(10)
class MyWidget(QtGui.QWidget):
def __init__(self, ...)
...
self.worker = Worker()
self.thread = QtCore.QThread(self)
self.worker.work_finished.connect(self.on_finished)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.do_work)
self.thread.start()
#QtCore.pyqtSlot(object)
def on_finished(self, data):
self.bilgiler.append(data)
...
Qt will automatically kill all the subthreads when the main thread exits the event loop.
I chose to rewrite a bit this answer, because I had failed to properly look at the problem's context. As the other answers and comments tell, you code lacks thread-safety.
The best way to fix this is to try to really think "in threads", to restrict yourself to only use objects living in the same thread, or functions that are known as "threadsafe".
Throwing in some signals and slots will help, but maybe you want to think back a bit to your original problem. In your current code, each time a button is pressed, a new thread in launched, that will, every 10 seconds, do 2 things :
- Read some text from self.text
- Append it to self.bilgiler
Both of these operations are non-threadsafe, and must be called from the thread that owns these objects (the main thread). You want to make the worker threads "schedule & wait" the read & append oeprations, instead of simply "executing" them.
I recommend using the other answer (the thread halting problem is automatically fixed by using proper QThreads that integrate well with Qt's event loop), which would make you use a cleaner approach, more integrated with Qt.
You may also want to rethink your problem, because maybe there is a simpler approach to your problem, for example : not spawning threads each time bilgi_cek is clicked, or using Queue objects so that your worker is completely agnostic of your GUI, and only interact with it using threadsafe objects.
Good luck, sorry if I caused any confusion. My original answer is still available here. I think it would be wise to mark the other answer as the valid answer for this question.

How to make QtGui window process events whenever it is brought forward on the screen?

I'm using PyQt for Python, and am building a gui. I had a problem a few weeks ago where I had a function outside of the gui module modifying widgets within the gui (advanced progress bar, updating strings, etc.), and those modifications were not reflected in the gui until the function that had made the changes finished running.
The solution to this was to simply call app.processEvents() after doing whatever modifications I wanted, which would immediately update the graphics of the window.
But now I am wondering, is there a way to do this everytime the window is brought forward?
Let's say I have called a function that will be modifying the progress bar, but this function takes quite a while to run. Inbetween calling the function, and the progress bar modification, the app processes no events. So, it during this time, I pull up a Chrome window (or anything else), and then close it, my gui window is blank, just gray, until app.processEvents() is called again.
Is ther functionality in PyQt that allows me to detect whenever the window is brought to the front of all current windows?
You should look into QThread.
Threads allow you to run long, complicated tasks in a worker thread while a background thread keeps the GUI responsive, such as updating a QProgressBar, ensuring it responds to motion events.
The basic idea is this:
# load modules
import time
from PySide import QtCore, QtGui
# APPLICATION STUFF
# -----------------
APP = QtGui.QApplication([])
# THREADS
# -------
class WorkerThread(QtCore.QThread):
'''Does the work'''
def __init__(self):
super(WorkerThread, self).__init__()
self.running = True
def run(self):
'''This starts the thread on the start() call'''
# this goes over 1000 numbers, at 10 a second, will take
# 100 seconds to complete, over a minute
for i in range(1000):
print(i)
time.sleep(0.1)
self.running = False
class BackgroundThread(QtCore.QThread):
'''Keeps the main loop responsive'''
def __init__(self, worker):
super(BackgroundThread, self).__init__()
self.worker = worker
def run(self):
'''This starts the thread on the start() call'''
while self.worker.running:
APP.processEvents()
print("Updating the main loop")
time.sleep(0.1)
# MAIN
# ----
def main():
# make threads
worker = WorkerThread()
background = BackgroundThread(worker)
# start the threads
worker.start()
background.start()
# wait until done
worker.wait()
if __name__ == '__main__':
main()
The output you get is something like this, showing how it takes turns at doing the long calculation and updating the main loop:
0
Updating the main loop
1
Updating the main loop
2
Updating the main loop
3
Updating the main loop
4
Updating the main loop
5
Updating the main loop
6
Updating the main loop
Updating the main loop7
8
Updating the main loop
9
This along with a QFocusEvent override should allow you to do whatever you wish. But it's better to separate updating the GUI and running your desired long thread.
As for overriding the QFocusEvent you can do something as follows:
def focusInEvent(self, event):
event.accept()
# insert your code here
And if you choose to implement threads to avoid GUI blocking, you should read about the basics of threading (as threads have a lot of nuances unless you know about their potential pitfalls).

Cannot send posted events for objects in another thread

When I try to use one QDialog object from threads I get this error.
Here is the code I'm using:
import threading
import test_qdialog
from PyQt4 import QtGui, QtCore
class MyThread(threading.Thread):
def __init__(self, id, window, mutex):
self.id = id
self.window = window
self.mutex = mutex
super(MyThread, self).__init__()
def run(self):
with self.mutex:
result = self.window.exec_()
if result == QtGui.QDialog.Accepted:
print "Thread %d: %s" % (self.id, self.window.message_input.text())
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
mutex = threading.Lock()
threads = []
window = test_qdialog.MyDialog()
for i in range(5):
thread = MyThread(i, window, mutex)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
sys.exit(app.exec_())
As written in this answer, if I get it right, I can't do it this way. But how can I do it then?
You can only create and use GUI widgets on main thread (every UI library that I know is like that). However, you can easily pass signals from threads to main using QtCore.QtThread. See for example the answer to PyQt threads and signals - how to properly retrieve values (even if the answer is not what the OP was looking for, it is relevant to your situation). May also find this SO post useful.
So instead of creating or accessing the dialog from thread, you would emit a signal from thread, and have your main window connected to it create the dialog when it receives the signal. Qt takes care of transfering data between threads. Will work like a charm.
Definitely take a close look at Qt Threading Basics, if you haven't already (if you have, may want to post questions about parts you don't understand, there is tons of important info there).
The QT Widgets can't be accessed from a thread which is not the main thread. For example, if you call mainwindow.show(), the program will crash.
However, this can be easily addressed using QThread. The principle is that instead of controlling the e.g., mainwindow directly, we can send a signal to the main thread, letting the main thread to call the show() method. A signal can carry anything, such as a String or Integer.
I'd strongly suggest you to watch this video. It will solve your problem in 10 minutes.

Multithreading in pygtk

I am trying to learn pygtk and I am developing a GUI for axel.
My problem is I need to show progress of download in my main window.
I used two threads one for downloading and second for progress calculation. Download is done by axel
but my second thread is not running until first thread stops. and second thread is not updating the gui
I used gobject.idel_add it stucks the main window upon download start
tried to use Gtk.gdk.thread_init()/thread_enter()/thread_leave() it says no module gdk in Gtk. On top of the page Gtk is imported from gi.repository
By the way I am using quickly to develop the app
Is there any way to solve the problem. or any similiar examples.
Check this thread separate threads in pygtk application may be usefully for you.
Here left a pygtk manage multithreading example to you.
import threading
import random, time
import gtk
#Initializing the gtk's thread engine
gtk.threads_init()
class FractionSetter(threading.Thread):
"""This class sets the fraction of the progressbar"""
#Thread event, stops the thread if it is set.
stopthread = threading.Event()
def run(self):
"""Run method, this is the code that runs while thread is alive."""
#Importing the progressbar widget from the global scope
global progressbar
#While the stopthread event isn't setted, the thread keeps going on
while not self.stopthread.isSet() :
# Acquiring the gtk global mutex
gtk.threads_enter()
#Setting a random value for the fraction
progressbar.set_fraction(random.random())
# Releasing the gtk global mutex
gtk.threads_leave()
#Delaying 100ms until the next iteration
time.sleep(0.1)
def stop(self):
"""Stop method, sets the event to terminate the thread's main loop"""
self.stopthread.set()
def main_quit(obj):
"""main_quit function, it stops the thread and the gtk's main loop"""
#Importing the fs object from the global scope
global fs
#Stopping the thread and the gtk's main loop
fs.stop()
gtk.main_quit()
#Gui bootstrap: window and progressbar
window = gtk.Window()
progressbar = gtk.ProgressBar()
window.add(progressbar)
window.show_all()
#Connecting the 'destroy' event to the main_quit function
window.connect('destroy', main_quit)
#Creating and starting the thread
fs = FractionSetter()
fs.start()
gtk.main()

PySide and QProgressBar update in a different thread

This may be a little long post, so, thanks in advance to be with me till the end. Here is the problem, (i think a fairly basic one, just my inexperience with PiSide and Qt making it harder for me.) I have a main window with one menu item, suppose "Process". the code is following -
from PySide import QtCore, QtGui
class Ui_MainWindow(object):
def setupUi(self, MainWindow, AppObj):
.
.
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
.
.
.
self.actionProcess = QtGui.QAction(MainWindow)
self.actionProcess.setObjectName("actionProcess")
self.actionProcess.triggered.connect(self.myappObj.menuActionProcess)
.
Here self.myappobj refers to a app class I have created which acts as the main logic controller for my app. The code -
from PySide import QtCore, QtGui
from MainWindow import Ui_MainWindow
class App(QtGui.QDialog):
def __init__(self, parent=None):
self.__mainWindow = QtGui.QMainWindow()
self.__mainWindowDesignContext = Ui_MainWindow()
self.__mainWindowDesignContext.setupUi(self.__mainWindow, self)
self.__mainWindow.show()
def menuActionProcess(self):
self.processThread = BatchProcesser()
self.progressBar = QtGui.QProgressBar()
statusBar.addWidget(self.progressBar)
self.progressBar.show()
self.progressBar.setMinimum(0)
self.progressBar.setMaximum(100)
QtCore.QObject.connect(self.processThread, QtCore.SIGNAL("progress(int)"),self.progressBar, QtCore.SLOT("setValue(int)"), QtCore.Qt.DirectConnection)
if not self.processThread.isRunning():
self.processThread.exiting = False
self.processThread.start()
So, it is easy to see that what i am trying to do here is to create a main window. Add a menu there called "Process", clicking on which should trigger the callback menuActionProcess method in the app class, where I am making a progress bar and attaching it with the Status Bar of my main window (The actual code has many other things, what i give here is the necessary parts rearranged as an pseudo example)
and finally in the BatchProcesser class mentioned in the above code, I am doing this -
from PySide.QtGui import *
from PySide.QtCore import *
class BatchProcesser(QThread):
__errorHappened = False
def __init__(self, parent=None):
QThread.__init__(self, parent)
self.exiting = False
def run(self):
for a in range(101):
print a
QThread.msleep(100)
self.emit(SIGNAL("progress(int)"), a)
print a
In my understanding this should update the Progress Bar attached to the Status Bar in a different thread than the main thread. This will allow the user to interact with the GUI freely.
Right now, If i try to rin this, everything is fine until i hit the Process menu. Then, the progress bar appeases but does not update and the console is full with error -
0 QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
0
1
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has
not been called
[xcb] Aborting, sorry about that.
python: ../../src/xcb_io.c:178: dequeue_pending_request: Assertion
`!xcb_xlib_unknown_req_in_deq' failed.
Aborted (core dumped)
Any help is very much required for me. I am unable to find out and/ot point the original reason of the error and fix the same.
The problem lies in how you connect the signal to the slot:
QtCore.QObject.connect(self.processThread, QtCore.SIGNAL("progress(int)"),self.progressBar, QtCore.SLOT("setValue(int)"), QtCore.Qt.DirectConnection)
Here, you explicitely enforce a DirectConnection. This is wrong! It makes the slot be processed in the caller's thread. This means that the GUI update of the progressBar happens in the process thread, not in the GUI thread. However, drawing is only allowed in the GUI thread.
It is perfectly fine to connect a signal from one thread to a slot from another. However, you need either AutoConnection (the default, which will recognize the threads and use QueuedConnection) or QueuedConnection.
This line should fix your problem:
QtCore.QObject.connect(self.processThread, QtCore.SIGNAL("progress(int)"),self.progressBar, QtCore.SLOT("setValue(int)"), QtCore.Qt.QueuedConnection)
See http://doc.qt.io/archives/qt-4.7/qt.html#ConnectionType-enum.

Categories