0

Normally, when I perform a network request via Qt 4.8 I don't need to explicitly run QApplication.processEvents() (see this StackOverflow code example).

However when I issue the network request from JavaScript in a QWebView, this will not work unless I call that method until the request is finished, as seen below (syntax-highlighted gist here).

CLARIFICATION:
The request is not even sent when I omit the processEvents() call although the finished slots seem to be attached as I understand it.

from PyQt4 import QtCore, QtGui, QtNetwork, QtWebKit

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        QtNetwork.QNetworkProxyFactory.setUseSystemConfiguration(True)
        self.view = QtWebKit.QWebView(self)
        self.setCentralWidget(self.view)
        self.view.setPage(QtWebKit.QWebPage(self.view))

        self.view.page().mainFrame().javaScriptWindowObjectCleared.connect(self.refreshJS)

        self.view.setHtml(
            '''<html>
            <body>
                    LOADING...
                    <script>
                    <!--
                            APP.request();
                    //-->
                    </script>
            </body>
            </html>'''
        )

    @QtCore.pyqtSlot()
    def request(self):
        request = QtNetwork.QNetworkRequest(QtCore.QUrl('http://localhost/test.php'))

        manager = QtNetwork.QNetworkAccessManager()
        manager.finished.connect(self.managerFinished)

        reply = manager.post(request, b'a=A')
        reply.finished.connect(self.finished)

        ############################################################
        ### FIXME: Request never even *sent* if this is missing  ###
        ############################################################

        while not reply.isFinished():
          QtGui.QApplication.processEvents()

        ##########################################################

        print('request FINISHED? '+str(reply.isFinished())+', ERROR '+str(reply.error()))

    def finished(self):
        print('finished')

    def managerFinished(self):
        print('managerFinished')

    def refreshJS(self):
        print('refreshJS')
        self.view.page().mainFrame().addToJavaScriptWindowObject('APP', self)

if __name__ == '__main__':
    import os, sys
    app = QtGui.QApplication(sys.argv)
    MainWindow().show()
    sys.exit(app.exec_())
Community
  • 1
  • 1
Arc
  • 11,143
  • 4
  • 52
  • 75
  • "Why must QApplication.processEvents() be called" It mustn't - except that you otherwise would not spin the event loop in your `while not reply.isFinished()`. You need to write your code in asynchronous style - attach a slot (a callable) to the request's `finished` signal. – Kuba hasn't forgotten Monica Apr 28 '14 at 18:44
  • I thought that `reply.finished.connect(self.finished)` (or the `QNetworkAccessManager` slot `manager.finished.connect(self.managerFinished)`, respectively) was doing what you said. So how do I correctly attach a slot ? – Arc Apr 29 '14 at 08:14
  • 1
    @Archimedix It does, but you need to not block the event loop to get this notification, and first of all to send anything. – Bogdan Apr 29 '14 at 08:18

1 Answers1

3

There is one big difference between your usage of QNetworkAccessManager and one pointed in the link. Notice that your version is synchronous: you block the event loop in 'request' slot by waiting for reply, thus QNetworkAccessManager cannot work unless you push the events by hand. From the other side the pointed example is asynchronous: it creates request, connects 'finished' signal and lets event loop to work. This way event loop is not blocked and network manager works well. I understand that in your situation 'request' slot needs to be synchronous, thus processEvents is a must here.

EDIT

If you want to make the request asynchronous, then you need to be sure that QNetworkAccessManager object is persistent outside the request method. Initialize it in __init__(self) and use it to POST in the request. ProbablyQNetworkReply` needs to be persistent too, you need to check it.

Bogdan
  • 984
  • 8
  • 16
  • Yes, right, the point being that I **want** my version to be asynchronous, i.e. **without** that darn busy-waiting `processEvents()` loop. But leaving that out will just result in the POST **never being event sent**, which is pretty strange (I checked the server logs and did a netcat test as well). – Arc Apr 29 '14 at 08:12
  • @Archimedix There is nothing strange, you create `QNetworkAccessManager` in the scope of `request`, thus outside there is nothing what can send POST event as `QNetworkAccessManager` object was just deleted. – Bogdan Apr 29 '14 at 08:15
  • Well, the request is started by JavaScript, and the JavaScript-bound method then **returns to the main event loop**. At least this is what I was thinking it should be doing. Therefore it *should* be asynchronous. The WebView does respond to my interaction, e.g. I can select text, open a context menu etc. which means that the method has completed and we are in the event handling loop, otherwise the WebView should be unresponsive, shouldn't it ? – Arc Apr 29 '14 at 08:20
  • @ Bogdan, could you provide a working remake of my code without that busy-waiting loop ? This is still unclear to me. Off-topic: One more confusing thing... writing @ and Bogdan (without spaces) just shows nothing... – Arc Apr 29 '14 at 08:23
  • @Archimedix You're right about JS-bound method and WebView responsiveness. You forgot one thing: `QNetworkAccessManager` is asynchronous too and needs events pump as well. For the moment you are in `request` method the event loop is blocked and there is no way to pump event by hand if you want working `QNetworkAccessManager` (and generally whole GUI). – Bogdan Apr 29 '14 at 08:26
  • Any suggestions on how to solve this elegantly, i.e. other than what I've done ? – Arc Apr 29 '14 at 08:28
  • @Archimedix Create `QNetworkAccessManager` in `__init__(self)` (connect the `finished` too) and use it to POST in `request`. I'm not sure if the 'reply' should be stored in similar way, you need to check it (try `self.reply` to store it outside `request`). – Bogdan Apr 29 '14 at 08:28
  • Ahhh, ok, guess now I got it... so `QNetworkAccessManager` is cleaned up automatically when the method returns although it should in theory still be used. So just by making it persist outside of the method solves the issue. Thanks ! Please update your answer with your comment. – Arc Apr 29 '14 at 08:34