8

I have a sample pyside demo which I created to see the webkit browser communication with python... I have two buttons in webkit

  • button 1 - when clicked it sleeps for 10 seconds and then prints a message

  • button2 - when clicked it prints a message immediately.

When I clicked on button 1, the whole apps freezes and waits for python to finish sleeping, this means I cannot click on button 2 to do some other stuff. How can I implement an asynchronous method between function calls?

My python codes are below

import sys,json
from time import sleep
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtWebKit import QWebView, QWebSettings
from PySide.QtNetwork import QNetworkRequest
from PySide.QtCore import QObject, Slot, Signal

    html_str="""<!doctype>
        <html>

        <body>hello world
        <button id="button" >button1</button>
        <button id="button2" >button2</button>
        </body>
    </html>
    <script type="text/javascript">
    document.getElementById("button").onclick=function(){
    object.reply(" hello ");
    }
    document.getElementById("button2").onclick=function(){
    object.reply2(" hello ");
    }
    function data_from_js(msg){
        var tag=document.createElement('div');
        tag.innerHTML="message from python";
        document.body.appendChild(tag);
        alert(msg['name']);
    }
    </script>
    <style>
    body{
    border:solid black 1px;
    }
    </style>
    </doctype>"""
class Qbutton(QObject):
    from time import sleep
    def __init__(self):
        super(Qbutton,self).__init__()
    @Slot(str)
    def reply(self,recd):
        #r=QMessageBox.information(self,"Info",msg)
        msgBox = QMessageBox()
        sleep(10)
        msgBox.setText("python just said"+recd)
        msgBox.exec_()
        return "I am recieving pythonic data"
        #r=QMessageBox.question(self,title,recd,QMessageBox.Yes | QMessageBox.No)
    @Slot(str)
    def reply2(self,recd):
        msgBox = QMessageBox()
        msgBox.setText("python just said"+recd+ " another time")
        msgBox.exec_()
        return "I am recieving pythonic data"        
    @Slot(str)
    def send_tojs(self):
        pass


class adstar_gui(QWidget):
    def __init__(self):        
        super(adstar_gui,self).__init__()
        self.setWindowTitle("Adstar Wordlist Generator")
        self.setMaximumWidth(5000)
        self.setMaximumHeight(5000)
        self.setMinimumWidth(500)
        self.setMinimumHeight(500)
        self.show()
        print "Sample window"

    def closeEvent(self,event):
        self.closeEvent()
if __name__=="__main__":
    Qapp=QApplication(sys.argv)
    t=QWebView()
    t.setHtml(html_str)
    button=Qbutton()
    t.page().mainFrame().addToJavaScriptWindowObject("object",button)
    t.show()
    #t.page().mainFrame().evaluateJavaScript("data_from_js(%s);" % (json.dumps({'name':"My name is Junior"}) ))
    QCoreApplication.processEvents()
    #sys.exit(Qapp.exec_())
    Qapp.exec_()

QUESTION

How can I click on button 1 in webkit and let python do something in the background when button 1 is clicked? (so that button 2 function does not need to wait for button 1 function to finish)

Kindly use this demo and improve on it...much appreciated

repzero
  • 8,254
  • 2
  • 18
  • 40

2 Answers2

3

There are a couple of issues here. First, it's worth pointing out why the app freezes when you click on button1: the click causes Qt to call the event handler, reply, and Qt can't handle another event until this handler returns (in my experience, all windowing systems work this way). So if you put any long running routine inside an event handler, your application will freeze until the routine finishes. Any time an event handler takes longer than about 0.05s, the user will notice.

As titusjan points out in his answer, it's pretty easy to get Qt to execute a function after a time interval. But I think your question isn't about how to handle a simple time delay, but rather how to handle a long-running process. In my example code, I replaced your ten second delay with a loop that counts ten one-second delays, which I think is a better model for what you are trying to achieve.

The solution is to do the long process in another thread. You have two options: QThreads, which are a part of the Qt environment, and Python threads. Both of them work, but I always use Python threads wherever possible. They're better documented and a little bit more lightweight. The ability to designate threads as daemons sometimes makes application shutdown a little simpler. Also, it's easier to convert a multithreaded program to one that uses multiprocesses. I used a Python thread in the example code below.

The problem then arises, how does the application know when the secondary thread is finished? For that purpose, you must create a custom Qt Signal. Your secondary thread emits this signal when it's done working, and the main app connects up a Slot to do something when that happens. If you're going to make a custom Qt Signal you must declare it in a subclass of QObject, as I did in the example.

Needless to say, all the standard multithreading issues have to be dealt with.

import sys
import json
import threading
from time import sleep
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtWebKit import QWebView, QWebSettings
from PySide.QtNetwork import QNetworkRequest
from PySide.QtCore import QObject, Slot, Signal

html_str="""<!doctype>
        <html>

        <body>hello world
        <button id="button" >button1</button>
        <button id="button2" >button2</button>
        </body>
    </html>
    <script type="text/javascript">
    document.getElementById("button").onclick=function(){
    object.reply(" hello ");
    }
    document.getElementById("button2").onclick=function(){
    object.reply2(" hello ");
    }
    function data_from_js(msg){
        var tag=document.createElement('div');
        tag.innerHTML="message from python";
        document.body.appendChild(tag);
        alert(msg['name']);
    }
    </script>
    <style>
    body{
    border:solid black 1px;
    }
    </style>
    </doctype>"""

class Qbutton(QObject):
    def __init__(self):
        super(Qbutton,self).__init__()
        self.long_thread = LongPythonThread()
        self.long_thread.thread_finished.connect(self.reply2_finished)

    @Slot(str)
    def reply(self,recd):
        print("reply")
        t = threading.Thread(target=self.long_thread.long_thread, args=(recd,))
        t.daemon = True
        t.start()

    @Slot(str)
    def reply2(self,recd):
        print("reply2")
        msgBox = QMessageBox()
        msgBox.setText("python just said"+recd)
        msgBox.exec_()
        return "I am receiving pythonic data"

    @Slot(str)
    def reply2_finished(self, recd):
        print("reply2 finished")
        msgBox = QMessageBox()
        msgBox.setText("python just said"+recd+ " another time")
        msgBox.exec_()

    @Slot(str)
    def send_tojs(self):
        pass

class LongPythonThread(QObject):    
    thread_finished = Signal(str)

    def __init__(self):
        super(LongPythonThread,self).__init__()

    def long_thread(self, recd):
        for n in range(10):
            sleep(1.0)
            print("Delayed for {:d}s".format(n+1))
        self.thread_finished.emit(recd)

if __name__=="__main__":
    Qapp=QApplication(sys.argv)
    t=QWebView()
    t.setHtml(html_str)
    button=Qbutton()
    t.page().mainFrame().addToJavaScriptWindowObject("object",button)
    t.show()
    #t.page().mainFrame().evaluateJavaScript("data_from_js(%s);" % (json.dumps({'name':"My name is Junior"}) ))
    QCoreApplication.processEvents()
    #sys.exit(Qapp.exec_())
    Qapp.exec_()
repzero
  • 8,254
  • 2
  • 18
  • 40
Paul Cornelius
  • 9,245
  • 1
  • 15
  • 24
  • I had experimented with QThreads after posting my question and it worked..I was waiting for someone to post such an answer..Excellent work! – repzero Aug 30 '16 at 02:50
  • Just a little bit of an issue..you haven't passed parameters to super in the class LongPythonThread which will throw an exception..but i added it in my sample..thanks – repzero Aug 30 '16 at 03:13
  • That's a difference between the super() function in Py2 and Py3. The code I wrote works with Python3 but your modification lets it work with both. – Paul Cornelius Aug 30 '16 at 20:43
1

Use a QTimer to execute a signal after a certain time period. Like this:

import sys,json
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtWebKit import QWebView, QWebSettings
from PySide.QtNetwork import QNetworkRequest
from PySide.QtCore import QObject, Slot, Signal, QTimer

html_str="""<!doctype>
        <html>

        <body>hello world
        <button id="button" >button1</button>
        <button id="button2" >button2</button>
        </body>
    </html>
    <script type="text/javascript">
    document.getElementById("button").onclick=function(){
    object.replyAfter10Seconds(" hello ");
    }
    document.getElementById("button2").onclick=function(){
    object.reply2(" hello ");
    }
    function data_from_js(msg){
        var tag=document.createElement('div');
        tag.innerHTML="message from python";
        document.body.appendChild(tag);
        alert(msg['name']);
    }
    </script>
    <style>
    body{
    border:solid black 1px;
    }
    </style>
    </doctype>"""


class Qbutton(QObject):
    def __init__(self):
        super(Qbutton,self).__init__()
        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.setInterval(10 * 1000)
        self.timer.timeout.connect(self.reply)
    @Slot(str)
    def replyAfter10Seconds(self,recd):
        self._replyText = recd
        print "Started timer"
        self.timer.start()
    @Slot()
    def reply(self):
        #r=QMessageBox.information(self,"Info",msg)
        msgBox = QMessageBox()
        msgBox.setText("python just said"+self._replyText)
        msgBox.exec_()
        return "I am recieving pythonic data"
        #r=QMessageBox.question(self,title,recd,QMessageBox.Yes | QMessageBox.No)
    @Slot(str)
    def reply2(self,recd):
        msgBox = QMessageBox()
        msgBox.setText("python just said"+recd+ " another time")
        msgBox.exec_()
        return "I am recieving pythonic data"        
    @Slot(str)
    def send_tojs(self):
        pass


class adstar_gui(QWidget):
    def __init__(self):        
        super(adstar_gui,self).__init__()
        self.setWindowTitle("Adstar Wordlist Generator")
        self.setMaximumWidth(5000)
        self.setMaximumHeight(5000)
        self.setMinimumWidth(500)
        self.setMinimumHeight(500)
        self.show()
        print "Sample window"

    def closeEvent(self,event):
        self.closeEvent()
if __name__=="__main__":
    Qapp=QApplication(sys.argv)
    t=QWebView()
    t.setHtml(html_str)
    button=Qbutton()
    t.page().mainFrame().addToJavaScriptWindowObject("object",button)
    t.show()
    t.raise_()
    #t.page().mainFrame().evaluateJavaScript("data_from_js(%s);" % (json.dumps({'name':"My name is Junior"}) ))
    QCoreApplication.processEvents() # does nothing as long as App.exec_() hasn't statred.
    #sys.exit(Qapp.exec_())
    Qapp.exec_()
titusjan
  • 5,376
  • 2
  • 24
  • 43