我目前正在研究一种使用PyQt5的QWebEngineView用Python和HTML / CSS / JS创建GUI桌面应用程序的方法。
在我的小演示应用程序中,我使用QWebChannel将Python QObject发布到JavaScript端,以便可以共享数据和来回传递数据。到目前为止,共享和连接插槽和信号都可以正常工作。
尽管在同步简单(属性)值时遇到困难。从我所读的内容来看,方法是通过修饰的getter和setter函数在共享QObject中实现pyqtProperty,并在setter中发出一个附加信号,用于在值更改时通知JavaScript。下面的代码说明了这一点,到目前为止效果很好:
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变量。
虽然这有点乏味。对于我想分享的每个价值,我都必须
self._foo
)QObject
的主体中创建信号有没有更简单的方法来实现这一目标?理想情况下是某种单线声明?也许使用类或函数将其打包?以后如何将其绑定到QObject
?我在想类似的东西
# in __init__
self.foo = SyncedProperty(str)
这可能吗?感谢您的想法!
一种方法是通过使用元类:
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!'
感谢您的元类概念,我对其进行了一些修改,以使其能够与包含多个实例的类一起正常使用。我遇到的问题是该值存储在Property
本身中,而不是存储在类实例属性中。为了清楚起见,我还将Property
类分成两个类。
class PropertyMeta(type(QtCore.QObject)):
def __new__(cls, name, bases, attrs):
for key in list(attrs.keys()):
attr = attrs[key]
if not isinstance(attr, Property):
continue
initial_value = attr.initial_value
type_ = type(initial_value)
notifier = QtCore.pyqtSignal(type_)
attrs[key] = PropertyImpl(
initial_value, name=key, type_=type_, notify=notifier)
attrs[signal_attribute_name(key)] = notifier
return super().__new__(cls, name, bases, attrs)
class Property:
""" Property definition.
This property will be patched by the PropertyMeta metaclass into a PropertyImpl type.
"""
def __init__(self, initial_value, name=''):
self.initial_value = initial_value
self.name = name
class PropertyImpl(QtCore.pyqtProperty):
""" Actual property implementation using a signal to notify any change. """
def __init__(self, initial_value, name='', type_=None, notify=None):
super().__init__(type_, self.getter, self.setter, notify=notify)
self.initial_value = initial_value
self.name = name
def getter(self, inst):
return getattr(inst, value_attribute_name(self.name), self.initial_value)
def setter(self, inst, value):
setattr(inst, value_attribute_name(self.name), value)
notifier_signal = getattr(inst, signal_attribute_name(self.name))
notifier_signal.emit(value)
def signal_attribute_name(property_name):
""" Return a magic key for the attribute storing the signal name. """
return f'_{property_name}_prop_signal_'
def value_attribute_name(property_name):
""" Return a magic key for the attribute storing the property value. """
return f'_{property_name}_prop_value_'
演示用法:
class Demo(QtCore.QObject, metaclass=PropertyMeta):
my_prop = Property(3.14)
demo1 = Demo()
demo2 = Demo()
demo1.my_prop = 2.7