初始评估JavaScript()调用后的PyQt4 QWebview错误

Posted

技术标签:

【中文标题】初始评估JavaScript()调用后的PyQt4 QWebview错误【英文标题】:PyQt4 QWebview error after initial evaluateJavaScript() call 【发布时间】:2015-11-01 21:05:43 【问题描述】:

我有一个使用 PyQt4 运行的 Python 2.7 应用程序,其中有一个 QWebView,它有两种方式与 javascript 进行通信。

该应用程序是通过 QThreadPool、QRunnables 进行多线程处理的,因此我使用信号与 ViewController 类进行通信。

当我运行应用程序时,QWebView 可以很好地使用外部 JS 和 CSS 加载我的 html。我可以通过主程序线程和 ViewController 类与 Javascript 函数进行交互。

一旦用户选择了一个目录并且满足了某些条件,它就会开始一次循环执行一个 QRunnable 任务。在此期间,它通过信号槽回调 ViewController -> Javascript,正如预期的那样。问题是当我调用那些执行 evaluateJavaScript 的 ViewController 方法时,我得到一个 Javascript 错误返回,

未定义的第 1 行:语法错误:解析错误

我反复尝试了很多错误,但似乎无法弄清楚为什么 evaluateJavaScript 不会在这些情况下运行。我尝试发送简单的 Javascript 调用,范围从不接受任何参数的测试函数(认为这可能是一些奇怪的编码问题),到只发送像 Application.main.evaluateJavaScript("alert('foo')") 这样的东西,这些东西通常在线程之外工作。我唯一能想到的另一件事是,可能需要再次在线程中调用self.main.addToJavaScriptWindowObject('view', self.view),但我已经在 Application.main 上运行了 dir() 并且它似乎已经附加了 evaluateJavaScript 方法。

当范围似乎正确并且 ViewController 似乎与 QWebView 通信正常时,有什么想法会发生这种情况吗?如果您以前见过这种情况,Qt C++ 中的答案可能也会起作用!

出于示例目的,我尝试简化代码:

# coding: utf8
import subprocess as sp

import os.path, os, sys, time, datetime
from os.path import basename
import glob

import random
import string

from PyQt4 import QtCore, QtGui, QtWebKit
from PyQt4.QtCore import QObject, pyqtSlot, QThreadPool, QRunnable, pyqtSignal
from PyQt4.QtGui import QApplication, QFileDialog
from PyQt4.QtWebKit import QWebView
from ImportController import *


class Browser(QtGui.QMainWindow):

    def __init__(self):

        QtGui.QMainWindow.__init__(self)
        self.resize(800,500)
        self.centralwidget = QtGui.QWidget(self)

        self.mainLayout = QtGui.QHBoxLayout(self.centralwidget)
        self.mainLayout.setSpacing(0)
        self.mainLayout.setMargin(0)

        self.frame = QtGui.QFrame(self.centralwidget)

        self.gridLayout = QtGui.QVBoxLayout(self.frame)
        self.gridLayout.setMargin(0)
        self.gridLayout.setSpacing(0)

        self.html = QtWebKit.QWebView()

        # for javascript errors
        errors = WebPage()
        self.html.setPage(errors)

        self.main = self.html.page().mainFrame()
        self.gridLayout.addWidget(self.html)
        self.mainLayout.addWidget(self.frame)
        self.setCentralWidget(self.centralwidget)

        path = os.getcwd()

        if self.checkNetworkAvailability() and self.checkApiAvailbility():
            self.default_url = "file://"+path+"/View/mainView.html"
        else:
            self.default_url = "file://"+path+"/View/errorView.html"

        # load the html view
        self.openView()

        # controller class that sends and receives to/from javascript
        self.view = ViewController()
        self.main.addToJavaScriptWindowObject('view', self.view)

        # on gui load finish
        self.html.loadFinished.connect(self.on_loadFinished)

    # to javascript

    def selectDirectory(self):
        # This evaluates the directory we've selected to make sure it fits the criteria, then parses the XML files
        pass


    def evaluateDirectory(self, directory):

        if not directory:
            return False

        if os.path.isdir(directory):
            return True
        else:
            return False

    @QtCore.pyqtSlot()
    def on_loadFinished(self):

        # open directory select dialog
        self.selectDirectory()

    def openView(self):

        self.html.load(QtCore.QUrl(self.default_url))
        self.html.show()

    def checkNetworkAvailability(self):
        #TODO: make sure we can reach the outside world before trying anything else
        return True

    def checkApiAvailbility(self):
        #TODO: make sure the API server is alive and responding
        return True


class WebPage(QtWebKit.QWebPage):
    def javaScriptConsoleMessage(self, msg, line, source):
        print '%s line %d: %s' % (source, line, msg)

class ViewController(QObject):
    def __init__(self, parent=None):
        super(ViewController, self).__init__(parent)

    @pyqtSlot()
    def did_load(self):
        print "View Loaded."

    @pyqtSlot()
    def selectDirectoryDialog(self):
        # FROM JAVASCRIPT: in case they need to re-open the file dialog
        Application.selectDirectory()

    def prepareImportView(self, displayPath):
        # TO JAVASCRIPT: XML directory parsed okay, so let's show the main
        Application.main.evaluateJavaScript("prepareImportView('0');".format(displayPath))

    def generalMessageToView(self, target, message):
        # TO JAVASCRIPT: Send a general message to a specific widget target
        Application.main.evaluateJavaScript("receiveMessageFromController('0', '1')".format(target, message))


    @pyqtSlot()
    def startProductImport(self):
        # FROM JAVASCRIPT: Trigger the product import loop, QThreads
        print "### view.startProductImport"
        position = 1
        count = len(Application.data.products)

        importTasks = ProductImportQueue(Application.data.products)
        importTasks.start()

    @pyqtSlot(str)
    def updateProductView(self, data):
        # TO JAVASCRIPT: Send product information to view
        print "### updateProductView "
        Application.main.evaluateJavaScript('updateProductView("0");'.format(QtCore.QString(data)) )


class WorkerSignals(QObject):
    ''' Declares the signals that will be broadcast to their connected view methods '''
    productResult = pyqtSignal(str)

class ProductImporterTask(QRunnable):
    ''' This is where the import process will be fired for each loop iteration '''
    def __init__(self, product):
        super(ProductImporterTask, self).__init__()

        self.product = product
        self.count = ""
        self.position = ""
        self.signals = WorkerSignals()

    def run(self):
        print "### ProductImporterTask worker 0/1".format(self.position, self.count)

        # Normally we'd create a dict here, but I'm trying to just send a string for testing purposes
        self.signals.productResult.emit(data)

        return

class ProductImportQueue(QObject):
    ''' The synchronous threadpool that is going to one by one run the import threads '''
    def __init__(self, products):
        super(ProductImportQueue, self).__init__()

        self.products = products
        self.pool = QThreadPool()
        self.pool.setMaxThreadCount(1)

    def process_result(self, product):
        return

    def start(self):
        ''' Call the product import worker from here, and format it in a predictable way '''

        count = len(self.products)
        position = 1
        for product in self.products:

            worker = ProductImporterTask("test")

            worker.signals.productResult.connect(Application.view.updateProductView, QtCore.Qt.DirectConnection)
            self.pool.start(worker)
            position = position + 1

        self.pool.waitForDone()




if __name__ == "__main__":

    app = QtGui.QApplication(sys.argv)
    Application = Browser()
    Application.raise_()
    Application.show()
    Application.activateWindow()
    sys.exit(app.exec_())

【问题讨论】:

【参考方案1】:

你知道,我喜欢 PyQt4,但是在搜索和搜索之后,我相信这实际上是一个错误,而不是设计的。

此后我继续前进并尝试在 CEFPython 中使用 WxPython 实现这一点,对于这个特定目的,它似乎有一个更优雅的实现。

【讨论】:

以上是关于初始评估JavaScript()调用后的PyQt4 QWebview错误的主要内容,如果未能解决你的问题,请参考以下文章

PyQt4.QtWebKit:加载停止时 QWebPage QWebView 设置超时

显示后的 Bootstrap 4 Popover Javascript

在 PyQt4 中使用 QThread 运行线程时更新变量值

使用 HTML/CSS/JavaScript 在 Web 应用程序中提供 PyQt4 GUI

从 QWidget (PyQt4) 调用 QMainWindow 中的方法/属性

JavaScript——Array.prototype.reduce()