Updating PyQt5 QTextBrowser inside running function - python

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)

Related

Execute method not blocked in qgis plugin

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

How does one update a field from outside the __init__() function with pyqt5

I am reading a sensor and want to display its output as a decimal number in a GUI using PyQt5. I have found a number of tutorials that point out the label.setText('myStr') function. This does not work for my setup, however, because I need to update the field based on the input from another function. I'm not very familiar with PyQt5 yet, and I would appreciate any insight into how this problem ought to be approached.
Note: (I am using LCM to acquire data from a Raspberry Pi. I'm not sure that that is relevant to the problem, but it helps explain my code below.)
Here is what I am trying to do:
class Home_Win(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("sensor_interface.ui", self)
self.label_temp.setText('temperature') #Just to verify that I can change it from here
def acquire_sensors(self):
temp = 0 #Make variable available outside nested function
def listen(channel, data):
msg=sensor_data.decode(data)
temp = msg.temperature
lc = lcm.LCM()
subscription = lc.subscribe("sensor_data_0", listen)
while True:
lc.handle()
self.label_temp.setText(str(temp))
Any thoughts on how I can update the GUI to display the readings I am getting from my sensors?
Thanks!
You're almost there. All you need to do is to save the ui in an instance variable in __init__:
self.ui = loadUi("sensor_interface.ui", self)
Then, assuming label_temp is the name of your QLabel widget, just do:
self.ui.label_temp.setText(str(temp))
It turned out that I needed to add repaint(). I also switched to a QLineEdit as this seemed to work better for me. So inside the while loop I now have:
self.ui.lineEdit_temp.setText(str(temp))
self.ui.lineEdit_temp.repaint()
This now outputs live updates to the GUI while reading the data stream.

Tkinter progress bar with Excel

We have a Pyxll app (Excel app written in python) that makes a bunch of requests to get data when the workbook is opened. We would like to display a loading bar to the user while the requests are being made and update the loading bar after each request returns.
I'm trying to use Tkinter to do this, but have run into issues. I can get a progress bar to pop up, but it blocks Excel from running until you close the window for the progress bar. I can't put it in a different thread because I want to be able to update the progress based on when the HTTP requests return. Is there an easy way to do this?
Here is my code so far. I have made a basic loading bar class:
import Tkinter as tk
import ttk
class OrderingProgressBar(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# progress goes from 0-100 (for percentage)
self.progress = 0
self.max_progress = 100
self.progress_bar = ttk.Progressbar(self, orient="horizontal", length=200, mode="determinate", maximum=self.max_progress)
self.progress_bar.pack()
And then I have a macro that gets called to launch the app and start making requests.
def launch_ordering_terminal(ribbon):
"""Launch all of the apps required for the Ordering Terminal"""
ordering_progress_bar = OrderingProgressBar()
ordering_progress_bar.mainloop()
excel_utils.turn_off_excel_updates(xl_app())
launch_allocation_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_si_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_accounting_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_reports_builder(terminal_mode)
ordering_progress_bar.progress_bar.step(25)
excel_utils.turn_on_excel_updates(xl_app())
With this code, when I call the macro a loading bar pops up, but block Excel. If I close the window it continues. If I move the mainloop call to the end of launch_ordering_terminal, then the entire terminal loads, and then it pops up the loading bar. I can't get them to work at the same time.
Thanks in advance for the help!
Maintaining a GUI is a full time job that requires an endless loop, called the "mainloop". If you want to do something else in the meantime, you need to add it to the mainloop, or do it in a different thread. In your case I think a new thread would be easiest. Tkinter runs best in the main thread, so put your code in a child thread.
As a totally untested guess:
from threading import Thread
def do_stuff(ordering_progress_bar):
excel_utils.turn_off_excel_updates(xl_app())
launch_allocation_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_si_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_accounting_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_reports_builder(terminal_mode)
ordering_progress_bar.progress_bar.step(25)
excel_utils.turn_on_excel_updates(xl_app())
ordering_progress_bar.quit() # kill the GUI
def launch_ordering_terminal(ribbon):
"""Launch all of the apps required for the Ordering Terminal"""
ordering_progress_bar = OrderingProgressBar()
t = Thread(target=do_stuff, args=(ordering_progress_bar,))
t.start()
ordering_progress_bar.mainloop()

Update status bar in main window from another script pyqt

I'm creating a GUI to run a fatigue analysis program in Python using PyQt. When the user clicks 'Start' in the main window to start the analysis a function in another file is called, which then chugs away (potentially for hours) until complete.
I'd like to find a way to update the status bar in the main window from within the separate fatigue analysis file. I don't care how I achieve this, but I'd really rather not just copy and paste everything from the fatigue analysis file into the GUI file (in which case I'd know how to update the status bar).
So far I've tried starting the GUI using the code below which is global (MainWindow is a class with all the stuff to set up the GUI, and updateStatus is a method within that class to update the status bar of the GUI):
app = QtGui.QApplication(sys.argv)
TopWindow = MainWindow(LastData)
TopWindow.show()
sys.exit(app.exec_())
In the Fatigue Analysis file I have the following code, which is called within a loop:
TopWindow.updateStatus(PercentComplete)
This throws the error below:
NameError: global name 'TopWindow' is not defined
I don't understand why this doesn't work, but it definitely doesn't! Any ideas for a workaround?
Thanks,
Pete
I believe you misunderstood the scope of a variable. Let me explain.
When you write the following code,
def myfunction():
a = 5
print(a) # OK
b = a # This line fails
you will get a failure, because a is local to myfunction, that is to say it only exists inside that function, and the last statement refers to a variable that is not known to python. The same holds for modules. Variables defined in one module are not accessible to the other modules... Unless you really want to do crappy things.
A nice solution is to think your modules as a main one, calling functionalities from the other ones. These functionalities can be classes or functions, in other words, you will pass the reference to your main window as an argument.
Main module
import fatigue_analysis
class MainWindow(QtGui.Window):
# ....
def start_file_compute(self, path_to_file):
# Pass a reference to self (MainWindow instance) to the helper function
fatigue_analysis.start(path_to_file, self)
# ....
fatigue_analysis.py
def start(path_to_file, top_window):
# ....
top_window.updateStatus(PercentComplete)
# ....
That said, you may consider using QtCore.QThread to run your long computation while leaving the GUI responsive. In this case, beware that Qt do not like very much that one thread tries to modify a member of another thread (i.e. you main window). You should use signals to pass information from on thread to another.
You can do the following:
In the MainWindow class, you should first create a statusbar: self.statusbar = self.statusBar()
Then, from the analysis code, pass a reference to the MainWindow object and from there you can update the statusbar as you wish using this:
main_window_object.statusbar.showMessage(PercentComplete)
EDIT: I do not know what the LastData is, but you would need to inherit from the QtGui.QMainWindow ideally.

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)

Categories