From 9b21c135f44d0ae4b1946e635f2dc33c6179f9ad Mon Sep 17 00:00:00 2001 From: badcel <1218031+badcel@users.noreply.github.com> Date: Fri, 17 May 2024 22:11:49 +0200 Subject: [PATCH] Property: Add Notify / Unnotify methods --- docs/docs/faq.md | 15 +++++-- src/Libs/GObject-2.0/Public/Property.cs | 43 ++++++++++++++++++- .../Libs/GirTest-0.1.Tests/PropertyTest.cs | 20 +++++++++ 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/docs/docs/faq.md b/docs/docs/faq.md index f0dca1ff4..40eadd159 100644 --- a/docs/docs/faq.md +++ b/docs/docs/faq.md @@ -20,9 +20,9 @@ C# developers are familar with the `INotifyPropertyChanged` interface, which can Every class which inherits from `GObject.Object` has an event called `Object.OnNotify`. Subscriber to this event get notified about every changed property similar to `INotifyPropertyChanged`. The `NotifySignalArgs` event argument contains a `ParamSpec` instance which can be queried for the *native* property name via `GetName()`. -As the *native* `Object` instance is represented in C# the properties in C# are named differently (mostly camel cased) in comparision to their *native* counterparts. To be able to match the *native* property name with their managed one, every *native* property has a static `Property` descriptor which provides the managed and unmanged name. Additionally it is possible to get or set the properties via their descriptor. +As the *native* `Object` instance is represented in C# the properties in C# are named differently (mostly camel cased) in comparision to their *native* counterparts. To be able to match the *native* property name with their managed one, every *native* property has a static `Property` descriptor which provides the managed and unmanged name. Additionally, it is possible to get or set the properties via their descriptor. -In Addition to the `OnNotify` event there is a static field for each event which describes the event. Similar to properties it provides the managed and unmanaged name of every event and allows to connect to the event. +In Addition to the `OnNotify` event there is a static field for each event which describes the event. Similar to properties it provides the managed and unmanaged name of an event and allows to connect to it. The GObject type system is more advanced than `INotifyPropertyChanged` as it allows to subscribe to specific properties of an instance: This means there are only events received for properties the application is actually interested in. This feature can be used if an event listener is registered via the `NotifySignal.Connect()` method instead of the `OnNotify` event and supplies a *detail* parameter containing the *native* name of the property to watch. (Other events have a *detail* parameter, too with different meanings.) @@ -35,4 +35,13 @@ NotifySignal.Connect( ); ``` -Please remember that the detail information must contain the *native name* of a property which is available through the static property descriptor, too. \ No newline at end of file +Please remember that the detail information must contain the *native name* of a property which is available through the static property descriptor. + +There is an alternative API available which avoids defining the name of the property. Every property definition has a `Notify()` method: + +```csharp +Gtk.Button.LabelPropertyDefinition.Notify( + sender: myButton, + signalHandler: OnButtonLabelChanged +); +``` \ No newline at end of file diff --git a/src/Libs/GObject-2.0/Public/Property.cs b/src/Libs/GObject-2.0/Public/Property.cs index 8e89d266b..18e9a98d1 100644 --- a/src/Libs/GObject-2.0/Public/Property.cs +++ b/src/Libs/GObject-2.0/Public/Property.cs @@ -34,10 +34,11 @@ public Property(string unmanagedName, string managedName) /// /// Get the value of this property in the given object. /// + /// If obj is not a GObject.Object. public T Get(K obj) { if (obj is not Object o) - throw new Exception($"Can't get property {ManagedName} for object of type {typeof(K).Name} as it is not derived from {nameof(Object)}."); + throw new ArgumentException($"Can't get property {ManagedName} for object of type {typeof(K).Name} as it is not derived from {nameof(Object)}."); using var value = new Value(); o.GetProperty(UnmanagedName, value); @@ -49,10 +50,11 @@ public T Get(K obj) /// Set the value of this property in the given object /// using the given value. /// + /// If obj is not a GObject.Object. public void Set(K obj, T value) { if (obj is not Object o) - throw new Exception($"Can't set property {ManagedName} for object of type {typeof(K).Name} as it is not derived from {nameof(Object)}."); + throw new ArgumentException($"Can't set property {ManagedName} for object of type {typeof(K).Name} as it is not derived from {nameof(Object)}."); var type = GetPropertyType(o.Handle); using var gvalue = new Value(type); @@ -70,4 +72,41 @@ private Type GetPropertyType(IntPtr handle) return new Type(paramSpec.ValueType); } + + /// + /// Registers a signal handler to get notified if the property is changed. + /// + /// The instance providing the property which should be listened to. + /// The handler which should be invoked if the property changes. + /// Define if the signal handler action must be called before or after the default handler. + /// If the sender is not a GObject.Object. + public void Notify(K sender, SignalHandler signalHandler, bool after = false) + { + if (sender is not Object obj) + throw new ArgumentException("The sender must be a GObject.Object", nameof(sender)); + + Object.NotifySignal.Connect( + sender: obj, + signalHandler: signalHandler, + after: after, + detail: UnmanagedName + ); + } + + /// + /// Deregisters a signal handler. + /// + /// The instance providing the property which should be deregistered. + /// The signal handler which should be deregistered. + /// If the sender is not a GObject.Object. + public void Unnotify(K sender, SignalHandler signalHandler) + { + if (sender is not Object obj) + throw new ArgumentException("The sender must be a GObject.Object", nameof(sender)); + + Object.NotifySignal.Disconnect( + sender: obj, + signalHandler: signalHandler + ); + } } diff --git a/src/Tests/Libs/GirTest-0.1.Tests/PropertyTest.cs b/src/Tests/Libs/GirTest-0.1.Tests/PropertyTest.cs index 6624b01ea..a9b4d1864 100644 --- a/src/Tests/Libs/GirTest-0.1.Tests/PropertyTest.cs +++ b/src/Tests/Libs/GirTest-0.1.Tests/PropertyTest.cs @@ -31,6 +31,26 @@ public void TestNullStringProperty() PropertyTester.StringValuePropertyDefinition.ManagedName.Should().Be(nameof(PropertyTester.StringValue)); } + [TestMethod] + public void PropertiesCanBeUsedToReceiveNotifyCallbacks() + { + var value = "MyNewValue"; + var result = string.Empty; + + var obj = PropertyTester.New(); + PropertyTester.StringValuePropertyDefinition.Notify(obj, StringValueSignalHandler); + + obj.StringValue = value; + result.Should().Be(value); + + PropertyTester.StringValuePropertyDefinition.Unnotify(obj, StringValueSignalHandler); + + void StringValueSignalHandler(GObject.Object sender, GObject.Object.NotifySignalArgs args) + { + result = ((PropertyTester) sender).StringValue; + } + } + [TestMethod] public void PropertyNamedLikeClass() {