如何动态创建 PyQt 属性 [英] How to create PyQt Properties dynamically

查看:76
本文介绍了如何动态创建 PyQt 属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在研究一种使用 PyQt5 的 QWebEngineView 使用 Python 和 HTML/CSS/JS 创建 GUI 桌面应用程序的方法.

I am currently looking into a way to create GUI desktop applications with Python and HTML/CSS/JS using PyQt5's QWebEngineView.

在我的小演示应用程序中,我使用 QWebChannel 将 Python QObject 发布到 JavaScript 端,以便数据可以共享和来回传递.到目前为止,共享和连接插槽和信号工作正常.

In my little demo application, I use a QWebChannel to publish a Python QObject to the JavaScript side, so that data can be shared and passed back and forth. Sharing and connecting slots and signals so far works fine.

我在同步简单(属性)值时遇到了困难.从我读过的内容来看,要走的路是通过装饰的 getter 和 setter 函数在共享 QObject 中实现一个 pyqtProperty,并在 setter 中发出一个额外的信号,用于在值发生变化时通知 JavaScript.下面的代码显示了这一点,到目前为止它工作正常:

I'm having difficulties though with the synchronisation of simple (property) values. From what I've read, the way to go is to implement a pyqtProperty in the shared QObject via decorated getter and setter functions, with an additional signal emitted in the setter, used to notify JavaScript when the value has changed. The code below shows that and so far this works fine:

import sys
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal 
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage


class HelloWorldHtmlApp(QWebEngineView):
    html = '''
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8"/>        
        <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
        <script>
        var backend;
        new QWebChannel(qt.webChannelTransport, function (channel) {
            backend = channel.objects.backend;
        });
        </script>
    </head>
    <body> <h2>HTML loaded.</h2> </body>
    </html>
    '''

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

        # setup a page with my html
        my_page = QWebEnginePage(self)
        my_page.setHtml(self.html)
        self.setPage(my_page)

        # setup channel
        self.channel = QWebChannel()
        self.backend = self.Backend(self)
        self.channel.registerObject('backend', self.backend)
        self.page().setWebChannel(self.channel)

    class Backend(QObject):
        """ Container for stuff visible to the JavaScript side. """
        foo_changed = pyqtSignal(str)

        def __init__(self, htmlapp):
            super().__init__()
            self.htmlapp = htmlapp
            self._foo = "Hello World"

        @pyqtSlot()
        def debug(self):
            self.foo = "I modified foo!"

        @pyqtProperty(str, notify=foo_changed)
        def foo(self):            
            return self._foo

        @foo.setter
        def foo(self, new_foo):            
            self._foo = new_foo
            self.foo_changed.emit(new_foo)


if __name__ == "__main__":
    app = QApplication.instance() or QApplication(sys.argv)
    view = HelloWorldHtmlApp()
    view.show()
    app.exec_()

在连接调试器的情况下开始,我可以调用 JavaScript 控制台中的 backend.debug() 槽,这导致 backend.foo 的值为我修改了foo!"之后,这意味着 Python 代码成功地更改了 JavaScript 变量.

Starting this with the Debugger connected, I can call the backend.debug() slot in the JavaScript console, which leads to the value of backend.foo being "I modified foo!" afterwards, which means the Python code succesfully changed the JavaScript variable.

虽然这有点乏味.对于我想分享的每一个价值,我都必须

This is kind of tedious though. For every value I'd want to share, I'd have to

  • 创建一个内部变量(这里是self._foo)
  • 创建一个getter函数
  • 创建一个setter函数
  • QObject 的主体中创建一个信号
  • 在 setter 函数中明确发出这个信号
  • create an internal variable (here self._foo)
  • create a getter function
  • create a setter function
  • create a signal in the QObject's body
  • emit this signal explicitly in the setter function

有没有更简单的方法来实现这一目标?理想情况下某种单行声明?也许使用类或函数来打包所有内容?稍后我将如何将其绑定到 QObject ?我正在考虑类似的事情

Is there any simpler way to achieve this? Ideally some sort of one-liner declaration? Maybe using a class or function to pack that all up? How would I have to bind this to the QObject later on? I'm thinking of something like

# in __init__
self.foo = SyncedProperty(str)

这可能吗?感谢您的想法!

Is this possible? Thanks for your ideas!

推荐答案

一种方法是使用元类:

class Property(pyqtProperty):
    def __init__(self, value, name='', type_=None, notify=None):
        if type_ and notify:
            super().__init__(type_, self.getter, self.setter, notify=notify)
        self.value = value
        self.name = name

    def getter(self, inst=None):
        return self.value

    def setter(self, inst=None, value=None):
        self.value = value
        getattr(inst, '_%s_prop_signal_' % self.name).emit(value)

class PropertyMeta(type(QObject)):
    def __new__(mcs, name, bases, attrs):
        for key in list(attrs.keys()):
            attr = attrs[key]
            if not isinstance(attr, Property):
                continue
            value = attr.value
            notifier = pyqtSignal(type(value))
            attrs[key] = Property(
                value, key, type(value), notify=notifier)
            attrs['_%s_prop_signal_' % key] = notifier
        return super().__new__(mcs, name, bases, attrs)

class HelloWorldHtmlApp(QWebEngineView):
    ...
    class Backend(QObject, metaclass=PropertyMeta):
        foo = Property('Hello World')

        @pyqtSlot()
        def debug(self):
            self.foo = 'I modified foo!'

这篇关于如何动态创建 PyQt 属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆