I'm using the QtWaitingSpinner found here: https://github.com/snowwlex/QtWaitingSpinner. You can create and start a spinner like so: spinner = QtWaitingSpinner(self); spinner.start(). Unfortunately when I try to make a POST request from my GUI, the spinner halts until a response has been returned. Consequently I don't see the spinner at all, or if I start the spinner prematurely it stops spinning while it waits for the response. I think I'll have to use some sort of asynchronous method like QThread or asyncio but it's unclear what the best way of getting around this is. If anyone can show me the best way to handle it I'd be grateful. Here is a simplified version of what I'm doing:
class Obj(QDialog):
# some button calls this function when pressed
def submit(self):
#start spinner
spinner = QtWaitingSpinner(self)
spinner.start()
# post some data to some url, spinner should spin
r = requests.post('some_url.com', json=some_data)
# stop spinner
spinner.stop()
The problem you are requests is blocking the Qt loop, so elements like QTimer do not work. One solution is to run that task on another thread, a simple way to do it is using QRunnable and QThreadPool.
class RequestRunnable(QRunnable):
def __init__(self, url, json, dialog):
QRunnable.__init__(self)
self.mUrl = url
self.mJson = json
self.w = dialog
def run(self):
r = requests.post(self.mUrl, json=self.mJson)
QMetaObject.invokeMethod(self.w, "setData",
Qt.QueuedConnection,
Q_ARG(str, r.text))
class Dialog(QDialog):
def __init__(self, *args, **kwargs):
QDialog.__init__(self, *args, **kwargs)
self.setLayout(QVBoxLayout())
btn = QPushButton("Submit", self)
btn.clicked.connect(self.submit)
self.spinner = QtWaitingSpinner(self)
self.layout().addWidget(btn)
self.layout().addWidget(self.spinner)
def submit(self):
self.spinner.start()
runnable = RequestRunnable("https://api.github.com/some/endpoint",
{'some': 'data'},
self)
QThreadPool.globalInstance().start(runnable)
#pyqtSlot(str)
def setData(self, data):
print(data)
self.spinner.stop()
self.adjustSize()
A complete example can be found in the following link
Related
I have some QDialog with some initial widgets. I try to run a QThread to add others widgets after my api request but I cant.
main.py
class Lobby(QDialog):
signal_api_response = pyqtSignal(dict)
def __init__(self, _token, parent=None):
super().__init__()
self.initUI() # build initial ui
self.mythread= MyThread('arg test')
self.mythread.new_signal.connect(self.response_my_api)
self.mythread.start()
self.showFullScreen()
#pyqtSlot(dict)
def response_worker_mychar(self, response):
print(response)
# build dynamic widget (not creating new widgets here)
self.some_slot = QLabel('zzzzz', self)
self.some_slot.move(10, 10)
self.some_slot.setCursor(Qt.PointingHandCursor)
self.some_slot.setStyleSheet(f"border: 2px solid red;color:white;")
Thread Class
class MyThread(QThread):
new_signal = pyqtSignal(dict)
def __init__(self, token=None, parent=None):
super(QThread, self).__init__(parent)
self.token = token
def run(self):
response = get_blabla_api(self.token)
self.new_signal.emit(response)
QThread is working but it doesn't create new widgets (QLabel, ...). I tried to change an existing widget and it worked, but creating others didn't work.
It doesn't give an error but it doesn't create the widgets.
Note: The function where I create these dynamic widgets after the signal emit works, it just doesn't work using QThread. I'm using QThread because as I make a request in the api, PyQt freezes.
First of all, I want to figure out how to check database status every second. so that the user will able to tell if the database is up or not without even clicking or triggering anything. I've read that this will create a problem as mentioned in the comments here
so here's my minimal reproducible example:
import sys
import os
import shiboken2
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog, QProxyStyle
from sqlalchemy import create_engine, inspect
class MyWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(200, 200)
self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
self.button = QtWidgets.QPushButton("Open File")
self.labelFile = QtWidgets.QLabel("empty")
self.labelData = QtWidgets.QLabel("None")
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.button)
self.layout.addWidget(self.labelFile)
self.layout.addWidget(self.labelData)
self.setLayout(self.layout)
self.button.clicked.connect(self.open_file)
self.process = None
self.CreateEngine = CreateEngine(self)
self.CreateEngine.result.connect(self.start_timer)
self.CreateEngine.start()
def open_file(self):
x = QFileDialog.getOpenFileName(self,"Just To Spice This Code",self.path,"CSV Files (*.csv)")
self.labelFile.setText(x[0]) #just to check that GUI doesn't freeze
def start_timer(self,engine): #callback from CreateEngine
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(lambda: self.continuously_check(engine))
self.timer.start(1000) #check connetion every second, as real-time as possible
def continuously_check(self,engine): #this gonna get called every second, yes it isn't effective i know
self.process = CheckConnection(self,engine)
self.process.result.connect(self.update_connection_label)
self.process.start()
def update_connection_label(self,x): #update connection status on GUI
self.labelData.setText("DB Status: "+str(x))
def closeEvent(self,event): #to handle QThread: Destroyed while thread is still running
print("begin close event")
if(self.process is not None):
if(shiboken2.isValid(self.process)): #to check whether the object is deleted. ->
self.process.wait() #-> this will get messy when the DB connection is down
self.process.quit() #-> (IMO):since i stack so many CheckConnection objects maybe?
print("end close event")
class CreateEngine(QtCore.QThread): #creating engine on seperate thread so that it wont block GUI
result = QtCore.Signal(object)
def __init__(self, parent):
QtCore.QThread.__init__(self, parent)
self.engine = None
def run(self):
self.engine = create_engine('mysql+pymysql://{}:{}#{}:{}/{}'.format("root","","localhost","3306","adex_admin"))
self.result.emit(self.engine)
class CheckConnection(QtCore.QThread): #constantly called every second, yes its not a good approach ->
result = QtCore.Signal(str) #-> i wonder how to replace all this with something appropriate
def __init__(self, parent,engine):
QtCore.QThread.__init__(self, parent)
self.engine = engine
def run(self):
try:
self.engine.execute('SELECT 1').fetchall()
self.result.emit("Connected")
except:
self.result.emit("Not Connected")
self.deleteLater() #somehow this doesn't do it job very well. maybe blocked?
#-> especially when the connection is busted. this thread gets stuck quite long to finish
if __name__ == "__main__":
#idk why when you start this without connection it's running really slow on showing the status of DB
#you must wait like 4 seconds until the connection status is showed up, which is really bad
#but once it's live. it could read database status really fast
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
I've created this example just to reproduce the same problem I'm facing in my real app. so the problem is that closeEvent takes too long to terminate the checking process and also blocking the GUI. The reason why I create 'closeEvent' is that I had this problem which produce [QThread: Destroyed while thread is still running] when the app is closed.
also, whenever the database isn't reachable it makes the QThread finishes way longer than it should unlike when the database is reachable. but we can retrieve the status pretty much like we want (every second of live DB Status). I also tried a silly approach like this
...
def continuously_check(self,engine):
self.process = CheckConnection(self,engine)
self.process.result.connect(self.update_connection_label)
self.process.finished.connect(lambda: QtCore.QTimer.singleShot(1000,self.continuously_check))
self.process.start()
...
hoping that it won't keep creating objects before the thread even finished (ps: obviously this won't work). so what's the best approach when it comes to this? sorry for multiple problems at a time.
I've recently started using kivy to design GUI for my python app. In the app one of the actions is sending requests to server using provided API. After a couple of tests I've noticed one annoying thing that happens only when I want to make a request - the button doesn't change color on press (I'm talking about default action). However, it is changing when simple print() is used.
How I can fix it? Any idea?
This is the code of my test app:
class TestApp(App):
def build(self):
parent = Widget()
btn = Button(text='Add')
btn.bind(on_press=self.add)
parent.add_widget(btn)
return parent
def add(self, obj):
print("Adding")
request = Request(url, urlencode(post_fields).encode())
urlopen(request)
That happened most likely because the UI froze. Theself.add is called, but right after that the UI waits until the request is done, which for you might result in that.
Try to do it like this:
import threading
class TestApp(App):
def build(self):
parent = Widget()
btn = Button(text='Add')
btn.bind(on_press=self.add)
parent.add_widget(btn)
return parent
def add(self, obj):
print("Adding")
#self.printer() # freezing
threading.Thread(target=self.printer).start()
def printer(self, *args):
while True:
print 'blob'
TestApp().run()
Also, instead of on_press use rather on_release. Prevents accidental events if I remember correctly (checks for collision touch↔button area).
So, I'm not sure if the title is the best description, but it's what I came up with.
Here's the deal. I'm working on a PyQt app that has a sort of plugin system where you can just add some sub classes to a folder and the app finds them. These commands have the option of being able to create little uis for themselves. Basically, they look like this:
class Command(object):
def do(self):
self.setupUi()
self.pre()
self.run()
self.post()
def pre(self):
# do setup stuff for run method
def run(self):
# do actual work
def post(self):
# clean up after run
def setupUi(self):
# create a ui for this command
diag = QDialog()
diag.exec_()
Now, the issue I'm running into is, I have one Command that creates a dialog, and waits for the user to accept it. Then, I need to switch the dialog to non-modal while the command is running, and up date the dialog. This all seems to work fine. But, the problem is I can't get the dialog to redraw until after the pre, run, and post methods have finished. So, if I have the setupUi like this:
def setupUi(self):
# create a ui for this command
diag = QDialog()
if diag.exec_():
diag.setModal(False)
diag.show()
I tried processEvents but that didn't seem to do it. Has anyone else run into this issue, or know of any work arounds?
Thanks
Using diag.exec_() will block until the dialog returns (is closed). So, if you will need to call show() on it's own. There are a few ways to proceed from here.
You can have the dialog accept slot run a reference to the rest of the commands
You can poll the dialog to see if the user has accepted
You can move the pre, run, and post commands to the dialog
Assuming you want to keep the meat of the code out of the dialog class, and since periodically polling is best to avoid if possible, here is an example of the first strategy:
import sys, time
from PyQt4 import QtCore, QtGui
class MyDialog(QtGui.QDialog):
def __init__(self,parent=None):
QtGui.QDialog.__init__(self,parent)
layout = QtGui.QVBoxLayout()
self.msg = QtGui.QLabel('some sort of status')
self.buttonbox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok, QtCore.Qt.Horizontal, self)
self.buttonbox.accepted.connect(self.accept)
layout.addWidget(self.msg)
layout.addWidget(self.buttonbox)
self.setLayout(layout)
def set_msg(self, new_msg):
self.msg.setText(new_msg)
def set_function_on_accept(self,fcn):
self.function = fcn
def accept(self):
self.function()
class Command(object):
def do(self):
self.setupUi()
def do_work(self):
self.pre()
self.run()
self.post()
def pre(self):
# do setup stuff for run method
time.sleep(1)
self.diag.set_msg("stuff setup")
QtGui.QApplication.processEvents()
def run(self):
# do actual work
time.sleep(1)
self.diag.set_msg("work done")
QtGui.QApplication.processEvents()
def post(self):
# clean up after run
time.sleep(1)
self.diag.set_msg("cleaned up")
QtGui.QApplication.processEvents()
def setupUi(self):
# create a ui for this command
diag = MyDialog()
self.diag = diag
diag.set_function_on_accept(self.do_work)
diag.show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
command = Command()
command.do()
sys.exit(app.exec_())
Here is a mocked up version of what I'm trying to do in my GUI. I have a MessageDialog which is created somewhere during the execution of a callback method. My problem is the MessageDialog won't close until the callback method finishes its execution.
I have a "dialog.destroy()" which I would expect to destroy the dialog. I click on "Yes/No" and the button depresses, but the dialog doesn't go away until "_go" finishes.
The "time.sleep(4)" is in there to simulate other stuff happening in my "_go" method after my MessageDialog interaction is over.
from gi.repository import Gtk, GObject
import time
class Gui(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.connect("delete_event", Gtk.main_quit)
self.set_size_request(700, 600)
notebook = Gtk.Notebook()
notebook.set_tab_pos(Gtk.PositionType.TOP)
notebook.append_page(MyTab(), Gtk.Label("A tab"))
self.add(notebook)
notebook.show_all()
self.show()
class MyTab(Gtk.VBox):
def __init__(self):
super(MyTab, self).__init__()
self.go_button = Gtk.Button()
self.go_button.add(Gtk.Image().new_from_stock(Gtk.STOCK_APPLY,
Gtk.IconSize.BUTTON))
top_box = Gtk.HBox()
top_box.pack_start(self.go_button, False, True, 5)
self.pack_start(top_box, False, True, 5)
# setup callbacks
self.go_button.connect("clicked", self._go)
def _go(self, _):
dialog = Gtk.MessageDialog(Gtk.Window(),
Gtk.DialogFlags.MODAL,
Gtk.MessageType.QUESTION,
Gtk.ButtonsType.YES_NO,
"RESPONSE REQUIRED")
dialog.format_secondary_text("are you having fun?")
response = dialog.run()
dialog.destroy()
print "your response is: " + str(response)
time.sleep(4)
print "left _go"
def main():
"""
Main entry point.
"""
Gui()
Gtk.main()
if __name__ == "__main__":
main()
This problem is not specific to dialogs. Any GUI change is invisible until you return to the main loop and give the system a chance to process the events accumulated by modifying the widgets.
If you really want to update the GUI immediately in the callback, you can manually spin the accumulated events with a loop like this after the call to dialog.destroy():
while Gtk.events_pending():
Gtk.main_iteration()
However, be aware that this will not only update the screen, but also run other accumulated events, including idle and timeout handlers and button click callbacks (if any are pending). That can have unexpected consequences.
This is the correct behaviour. The window only disappears when control is given back to Gtk's main loop which only happens at the end of your _go callback.
As per the comments on user4815162342's answer I came up with a solution that uses a nested main loop. This class takes in a dialog and provides a run method.
class NestedDialog(object):
def __init__(self, dialog):
self.dialog = dialog
self.response_var = None
def run(self):
self._run()
return self.response_var
def _run(self):
self.dialog.show()
self.dialog.connect("response", self._response)
Gtk.main()
def _response(self, dialog, response):
self.response_var = response
self.dialog.destroy()
Gtk.main_quit()
The dialog is then run as follows:
def _go(self, _):
dialog = Gtk.MessageDialog(Gtk.Window(),
Gtk.DialogFlags.MODAL,
Gtk.MessageType.QUESTION,
Gtk.ButtonsType.YES_NO,
"RESPONSE REQUIRED")
dialog.format_secondary_text("are you having fun?")
nested_dialog = NestedDialog(dialog)
response = nested_dialog.run()
print "your response is: " + str(response)
time.sleep(4)
print "left _go"