如何使用 Python+PyGObject 的 GObject.bind_property 函数进行 2 路数据绑定?

Posted

技术标签:

【中文标题】如何使用 Python+PyGObject 的 GObject.bind_property 函数进行 2 路数据绑定?【英文标题】:How to do 2-way data binding using Python+PyGObject's GObject.bind_property function? 【发布时间】:2021-08-18 03:06:03 【问题描述】:

这个问题的背景(以及我的总体目标)是以一种很好的方式构建 Python GTK 应用程序。我正在尝试使用GTK's bidirectional data bindings 将小部件属性绑定到模型属性。

我的期望是双向绑定应该保持两个属性同步。相反,我发现更改仅在一个方向传播,即使我使用的是GObject.BindingFlags.BIDIRECTIONAL 标志。我创建了以下最小示例和失败的测试用例test_widget_syncs_to_model 来说明问题。请注意,在更实际的示例中,模型对象可以是Gtk.Application 的实例,而小部件对象可以是Gtk.Entry 的实例。

import gi

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
import unittest


class Obj(GObject.Object):
    """A very simple GObject with a `txt` property."""

    name = "default"
    txt = GObject.Property(type=str, default="default")

    def __init__(self, name, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.name = name
        self.connect("notify", self.log)

    def log(self, source, parameter_name):
        print(
            f"The 'self.name' object received a notify event, "
            f"its txt now is 'self.txt'."
        )


class TestBindings(unittest.TestCase):
    def setUp(self):
        """Sets up a bidirectional binding between a model and a widget"""
        print(f"\n\nself.id()")
        self.model = Obj("model")
        self.widget = Obj("widget")
        self.model.bind_property(
            "txt", self.widget, "txt", flags=GObject.BindingFlags.BIDIRECTIONAL
        )

    @unittest.skip("suceeds")
    def test_properties_are_found(self):
        """Verifies that the `txt` properties are correctly set up."""
        for obj in [self.model, self.widget]:
            self.assertIsNotNone(obj.find_property("txt"))

    @unittest.skip("suceeds")
    def test_model_syncs_to_widget(self, data="hello"):
        """Verifies that model changes propagate to the widget"""
        self.model.txt = data
        self.assertEqual(self.widget.txt, data)

    def test_widget_syncs_to_model(self, data="world"):
        """Verifies that widget changes propagate back into the model"""
        self.widget.txt = data
        self.assertEqual(self.widget.txt, data)  # SUCCEEDS
        self.assertEqual(self.model.txt, data)  # FAILS


if __name__ == "__main__":
    unittest.main()

以上程序输出:

ssF
======================================================================
FAIL: test_widget_syncs_to_model (__main__.TestBindings)
Verifies that widget changes propagate back into the model
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/jh/.config/JetBrains/PyCharmCE2021.1/scratches/scratch_14.py", line 52, in test_widget_syncs_to_model
    self.assertEqual(self.model.txt, data)  # FAILS
AssertionError: 'default' != 'world'
- default
+ world


----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1, skipped=2)


__main__.TestBindings.test_widget_syncs_to_model
The 'widget' object received a notify event, its txt now is 'world'.

Process finished with exit code 1

我的具体问题是,我怎样才能让双向数据绑定工作?...如果有人可以修复我的示例或提供另一个工作示例,我会很高兴。

从更广泛的意义上说,双向绑定是在结构良好的 Python GTK 应用程序中同步 UI 状态和模型状态的方式吗?这样做的预期和良好支持的方式是什么?谢谢!

【问题讨论】:

GObject::notify 信号是否在您的 txt 属性更改时发出?属性绑定依赖notify 信号来工作。 谢谢!我在代码示例中添加了日志记录。这似乎表明收到了带有错误文本值“txt”的通知信号。 我认为您实际上希望 print("received notify event", self.txt)log() 函数中。 param 参数实际上是一个GParamSpec 实例,它描述了属性类型,而不是它的当前值。所以我认为正在打印的txt 实际上只是属性名称。 注意:我在GNOME discourse forum under "Bidirectional Bindings using PyGObject - cannot get it to work, help or working example needed" 中发布了这个问题的链接,因为我很确定 GNOME 社区会有答案。 嗨!谢谢菲利普的帮助和考虑! Zander Brown 在 GNOME 讨论线程中友好地指出了我的代码中的错误:设置绑定时,标志必须作为位置参数传递。您或 Zander 是否也想在 *** 上提供答案? 【参考方案1】:

我在gnome discourse thread about bidirectional property bindings in python 得到了答复。

为了更清楚,下面的代码确实工作,因为标志没有正确传递:

# broken, flags are passed incorrectly as keywords argument:
self.model.bind_property("txt", self.widget, "txt", flags=GObject.BindingFlags.BIDIRECTIONAL)

相反,标志必须按如下方式传递:

# functioning, flags are passed correctly as a positional argument:
self.model.bind_property("txt", self.widget, "txt", GObject.BindingFlags.BIDIRECTIONAL)

更多示例代码:例如,test_bidirectional_binding test case 中的 pygobject 源代码中演示了正确使用双向绑定。

【讨论】:

以上是关于如何使用 Python+PyGObject 的 GObject.bind_property 函数进行 2 路数据绑定?的主要内容,如果未能解决你的问题,请参考以下文章

在Pygobject GTK3中使用Gtk.GLArea

Windows 上的 PyGObject

python SITCON 2018中使用的PyGObject示例。

用 pygobject 编写 D-Bus 服务?

使用 Pygobject 和 Python 3 从内存数据中显示图像

如何使用 PyGObject 使窗口居中