Execute method not blocked in qgis plugin - python

I am developing a QGIS Plugins, complex plugin with various classes and methods that call other methods.
My plugin works fine, but I'm loking to run some class method in the background (something like jobs in JAVA) to avoid frozen GUI. I mean, I have a class, this class has a method and this method I would like run in background
I tried with QThread,QTask, threading, QProcess, etc, but I did not find what I want to do.
Some idea could help me? Some plugin does works with background process? Some plugin example?
Thanks

I have been using QTask in order to run heavy processes in the background.
The useful ressources I am using are these ones :
https://gis.stackexchange.com/questions/94477/how-to-use-threads-in-pyqgis-mainly-to-keep-ui-active
https://github.com/qgis/QGIS/pull/3004
You have to specifically create a new Class that inherits from QgsTask and call it in the run() method of your GUI Plugin.
In my case this is what I have :
class MyPlugin:
"""QGIS Plugin Implementation.
... __init__ method, tr method, initGui method, ...
"""
def run(self):
"""Run method that performs the work after clicking 'OK' in the Plugin GUI"""
task = MyTask('UserFriendlyTaskName', **props)
QgsApplication.taskManager().addTask( task )
class MyTask(QgsTask):
def __init__(self, task_name, **props):
QgsTask.__init__(self, desc)
# do w/e with the props
def run(self):
# call your heavy processes
See this .gif below for the UI rendering in QGIS3, there is no freezing and I can still move on the map and edit data while the plugin is running.
QGIS GUI Launch Background Process GIF
Hope this answer finds you in time !

If i understand correctly, you want to refresh the UI to avoid QGIS freezing.
This is what worked for me:
from PyQt5.QtWidgets import QApplication
QApplication.processEvents()
This will refresh the UI of QGIS.
You probably have to call it multiple time, for example after every iteration in a for loop.

thanks you for your answer
what I would to do is run a method in background without freezing QGIS GUI
a example of my code
class Example:
def __init__(self):
code
self.createBotton()
def createButton(self):
Here I create my button
self.okbutton.clicked.connect(self.pressOkButton)
def pressOkButton(self):
here I call other methods
self.methodA()
self.methodB()
def methodA(self):
code
def methodB(self):
code
my question is--> how can i run 'pressOkButton' method in background? When i executed this method i can not ineract with QGIS GUI until finish method
NOTE:consider that it is a class that belongs to my plugin so this class is instantiated when starting the plugin

Related

Updating PyQt5 QTextBrowser inside running function

I’ve been trying to make a log of some lengthy file transfer and copy tasks along with a progress bar so that the end user has some idea of how far along the process they are. I have tried using multiprocessing but as I am trying to pass in ‘self’ from a class into a function it is unable to pickle. I have attached a snippet of my code below. The function it calls (fileCopy) is all working and has ‘self.log.setText()’ in it to update the log. Is there anyway I can have a live log and progress bar working while the function is running?
Thank you in advance
class Processing(QMainWindow):
def __init__(self):
super(Processing, self).__init__()
loadUi("UIFiles/processing.ui", self)
self.options = self.getOptions()
self.next.hide()
self.pause.clicked.connect(self.pauseClicked)
fileCopy(self, options)

PyQt: updating GUI from a callback

Using Python3 and PyQt4 I have a function (run) that takes as an input a callable to provide status updates.
class Windows(QtGui.QWidget):
# Creates a widget containing:
# - a QLineEdit (status_widget)
# - a button, connected to on_run_clicked
def on_run_clicked(self):
def update(text):
self.widget.setText(text)
threading.Thread(target=run, args=(update, )).start()
This works ok (i.e. the text updates are displayed properly in the widget). However, when I replace QLineEdit by QTextEdit and use the append method to add text, I get:
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
It still works but point to the fact that I am doing something wrong, and I am not sure that I will keep working when more threads are active. Usually, I do this type of updates using signals and slots, but the run function is not PyQt specific. The questions are:
Why does it work without a warning for QLineEdit and not for
QTextEdit?
What is the right way to deal with a situation like this?
I don't know the specific reason why one class works and the other doesn't - nor do I really know the difference between using Python threading vs. Qt's threading...however, I can tell you that it is very tempremental if you don't set it up properly. Namely, you cannot (or at the very least, should not) modify GUI objects from a thread. Again, not sure the difference of a python vs. a Qt thread on that. But, the safe way to modify your interface from a GUI is by sending signals to your window...easiest way I know to do this is via the Qt threading.
class MyThread(QtCore.QThread):
updated = QtCore.pyqtSignal(str)
def run( self ):
# do some functionality
for i in range(10000):
self.updated.emit(str(i))
class Windows(QtGui.QWidget):
def __init__( self, parent = None ):
super(Windows, self).__init__(parent)
self._thread = MyThread(self)
self._thread.updated.connect(self.updateText)
# create a line edit and a button
self._button.clicked.connect(self._thread.start)
def updateText( self, text ):
self.widget.setText(text)

How is this method called? (Pyglet)

The following code is an alternative to the usual style (using decorators) pyglet makes use of.
Can anyone explain how the on_draw() method is called here?
import pyglet
class HelloWorldWindow(pyglet.window.Window):
def __init__(self):
super(HelloWorldWindow, self).__init__()
self.label = pyglet.text.Label('Hello, world!')
def on_draw(self):
self.clear()
self.label.draw()
if __name__ == '__main__':
window = HelloWorldWindow()
pyglet.app.run()
The code written using decorators can be found here.
You can just dig through the source to find the answer.
The EventLoop class (you use it by pyglet.app.run()) dispatches the on_draw event regulary.
From the source:
Calling run begins the application event loop, which processes
operating system events, calls pyglet.clock.tick to call scheduled
functions and calls pyglet.window.Window.on_draw and
pyglet.window.Window.flip to update window contents.
The Window class subscripes to this event:
BaseWindow.register_event_type('on_draw')
So by subclassing Window, you ensure your on_draw method gets called.
Look at the programming guide for an example of how the event system of pyglet works.

Threads, wxpython and statusbar

I'm doing a program in which I'm using a wxStatusBar, when a download starts I start a child thread like this:
def OnDownload(self, event):
child = threading.Thread(target=self.Download)
child.setDaemon(True)
child.start()
Download is another function without parameters (except self). I would like to update my statusbar from there with some information about the downloading progress, but when I try to do so I often get Xwindow, glib and segfaults errors. Any idea to solve this?
Solved: I just needed to include wx.MutexGuiEnter() before changing something in the GUI inside the thread and wx.MutexGuiLeave() when finished. For example
def Download(self):
#stuff that doesn't affect the GUI
wx.MutexGuiEnter()
self.SetStatusText("This is a thread")
wx.MutexGuiLeave()
And that's all :D
Most people get directed to the wxPython wiki:
http://wiki.wxpython.org/LongRunningTasks
I also wrote up a little piece on the subject here:
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
I don't think I've ever seen your solution before though.
How are you updating the status bar?
I think you should be fine if you create a custom event, and then post it via wx.PostEvent to notify the frame/status bar in the GUI thread.
For download progress in a status bar, you might want your event to look something like this:
DownloadProgressEvent, EVT_DL_PROGRESS = wx.lib.newevent.NewEvent()
# from the thread...
event = DownloadProgressEvent(current=100, total=1000, filename="foo.jpg")
wx.PostEvent(frame, event)
# from the frame:
def OnDownloadProgress(self, event):
self.statusbar.update_dl_msg(event.current, event.total, event.filename)
Here's some more detail from the wxPython wiki.

wxPython: Threading GUI --> Using Custom Event Handler

I am trying to learn how to run a thread off the main GUI app to do my serial port sending/receiving while keeping my GUI alive. My best Googling attempts have landed me at the wxpython wiki on: http://wiki.wxpython.org/LongRunningTasks which provides several examples. I have settled on learning the first example, involving starting a worker thread when the particular button is selected.
I am having trouble understanding the custom-event-definition:
def EVT_RESULT(win, func):
"""Define Result Event."""
win.Connect(-1, -1, EVT_RESULT_ID, func)
class ResultEvent(wx.PyEvent):
"""Simple event to carry arbitrary result data."""
def __init__(self, data):
"""Init Result Event."""
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
self.data = data
Primarily the
def EVT_RESULT(win, func):
"""Define Result Event."""
win.Connect(-1, -1, EVT_RESULT_ID, func)
I think EVT_RESULT is placed outside the classes so as to make it call-able by both classes (making it global?)
And.. the main GUI app monitors the thread's progress via:
# Set up event handler for any worker thread results
EVT_RESULT(self,self.OnResult)
I also notice that in a lot of examples, when the writer uses
from wx import *
they simply bind things by
EVT_SOME_NEW_EVENT(self, self.handler)
as opposed to
wx.Bind(EVT_SOME_NEW_EVENT, self.handler)
Which doesn't help me understand it any faster.
Thanks,
That's the old style of defining custom events. See the migration guide for more information.
Taken from the migration guide:
If you create your own custom event
types and EVT_* functions, and you
want to be able to use them with the
Bind method above then you should
change your EVT_* to be an instance of wx.PyEventBinder instead of a
function. For example, if you used to
have something like this:
myCustomEventType = wxNewEventType()
def EVT_MY_CUSTOM_EVENT(win, id, func):
win.Connect(id, -1, myCustomEventType, func)
Change it like so:
myCustomEventType = wx.NewEventType()
EVT_MY_CUSTOM_EVENT = wx.PyEventBinder(myCustomEventType, 1)
Here is another post that I made with a couple of example programs that do exactly what you are looking for.
You can define events like this:
from wx.lib.newevent import NewEvent
ResultEvent, EVT_RESULT = NewEvent()
You post the event like this:
wx.PostEvent(handler, ResultEvent(data=data))
Bind it like this:
def OnResult(event):
event.data
handler.Bind(EVT_RESULT, OnResult)
But if you just need to make a call from a non-main thread in the main thread you can use wx.CallAfter, here is an example.
Custom events are useful when you don't want to hard code who is responsible for what (see the observer design pattern). For example, lets say you have a main window and a couple of child windows. Suppose that some of the child windows need to be refreshed when a certain change occurs in the main window. The main window could directly refresh those child windows in such a case but a more elegant approach would be to define a custom event and have the main window post it to itself (and not bother who needs to react to it). Then the children that need to react to that event can do it them selves by binding to it (and if there is more than one it is important that they call event.Skip() so that all of the bound methods get called).
You may want to use Python threads and queues and not custom events. I have a wxPython program (OpenSTV) that loads large files that caused the gui to freeze during the loading. To prevent the freezing, I dispatch a thread to load the file and use a queue to communicate between the gui and the thread (e.g., to communicate an exception to the GUI).
def loadBallots(self):
self.dirtyBallots = Ballots()
self.dirtyBallots.exceptionQueue = Queue(1)
loadThread = Thread(target=self.dirtyBallots.loadUnknown, args=(self.filename,))
loadThread.start()
# Display a progress dialog
dlg = wx.ProgressDialog(\
"Loading ballots",
"Loading ballots from %s\nNumber of ballots: %d" %
(os.path.basename(self.filename), self.dirtyBallots.numBallots),
parent=self.frame, style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME
)
while loadThread.isAlive():
sleep(0.1)
dlg.Pulse("Loading ballots from %s\nNumber of ballots: %d" %
(os.path.basename(self.filename), self.dirtyBallots.numBallots))
dlg.Destroy()
if not self.dirtyBallots.exceptionQueue.empty():
raise RuntimeError(self.dirtyBallots.exceptionQueue.get())

Categories