1

I am working with pyqt5. I want the user to click on the browser which should be embeded on my pyqt5 application to get the Class of the element which he/she is clicking on it.

I just found on stack overflow that it's doable by combining javascript code with python. Any idea how to extract the class of the element?

  • 1
    Learning javascript just for this small project is insane XD... –  Mar 04 '20 at 11:17
  • 2
    It's not that hard, and you don't have to learn everything about it for your purpose. If you're experienced with python, it won't take you that much. – musicamante Mar 04 '20 at 11:25
  • 2
    Yeah you are right. But still, learning any language for every small tasks is not a good idea... what if i got a problem in c++ and java after some time? Do i have to learn both of them one after another? Currently i am learning kotlin alongside python. I can't take any more languages. Also i have only 20 days left in my holidays to complete... So need to complete it now. –  Mar 04 '20 at 11:33
  • 1
    If you got a problem in c++ and java, you'll need to learn some c++ and java basics and then study what is need for your purposes. You already are learning languages that share some common concepts (they are object based) and part of the syntax of javascript. Plus, what you need does not require that you become some "js master". Actually, it's very easy, and it would have required you just some more focused searches. I've given you an answer that is just a sum of a less-than-10-minutes research, and I know almost nothing about javascript. – musicamante Mar 04 '20 at 12:58
  • 1
    Salute to you @musicamante. Thanks for your answer and the comment. I will try to improve myself and do research and learn more about the topics before asking here on stack overflow.... Really thanks a lot dude. –  Mar 04 '20 at 13:29

1 Answers1

3

The solution is to add an "event listener" that makes possible communication between the DOM objects and (Py)Qt.

The following solution is based on this answer.

from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
from jinja2 import Template

class Element(QtCore.QObject):
    def __init__(self, name, parent=None):
        super(Element, self).__init__(parent)
        self._name = name

    @property
    def name(self):
        return self._name

    def script(self):
        return ""

class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self, parent=None):
        super(WebEnginePage, self).__init__(parent)
        self.loadFinished.connect(self.onLoadFinished)
        self._objects = []
        self._scripts = []

    def add_object(self, obj):
        self._objects.append(obj)

    @QtCore.pyqtSlot(bool)
    def onLoadFinished(self, ok):
        print("Finished loading: ", ok)
        if ok:
            self.load_qwebchannel()
            self.add_objects()

    def load_qwebchannel(self):
        file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
        if file.open(QtCore.QIODevice.ReadOnly):
            content = file.readAll()
            file.close()
            self.runJavaScript(content.data().decode())
        if self.webChannel() is None:
            channel = QtWebChannel.QWebChannel(self)
            self.setWebChannel(channel)

    def add_objects(self):
        if self.webChannel() is not None:
            objects = {obj.name: obj for obj in self._objects}
            self.webChannel().registerObjects(objects)
            _script = """
            {% for obj in objects %}
            var {{obj}};
            {% endfor %}
            new QWebChannel(qt.webChannelTransport, function (channel) {
            {% for obj in objects %}
                {{obj}} = channel.objects.{{obj}};
            {% endfor %}
            }); 
            """
            self.runJavaScript(Template(_script).render(objects=objects.keys()))
            for obj in self._objects:
                if isinstance(obj, Element):
                    self.runJavaScript(obj.script())


class Helper(Element):
    classClicked = QtCore.pyqtSignal(str)

    def script(self):
        js = """
        document.addEventListener('click', function(e) {
            e = e || window.event;
            var target = e.target || e.srcElement;
            {{name}}.objectClicked(target.className);
        }, false);"""
        return Template(js).render(name=self.name)

    @QtCore.pyqtSlot(str)
    def objectClicked(self, className):
        if className:
            self.classClicked.emit(className)

if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)

    classname_helper = Helper("classname_helper")
    classname_helper.classClicked.connect(lambda name: print("clicked", name))
    view = QtWebEngineWidgets.QWebEngineView()
    page = WebEnginePage()
    page.add_object(classname_helper)
    view.setPage(page)
    view.load(QtCore.QUrl("https://stackoverflow.com/questions/60524774"))
    view.show()
    sys.exit(app.exec_())
Abhay Salvi
  • 890
  • 3
  • 15
  • 39
musicamante
  • 41,230
  • 6
  • 33
  • 58