Skip to content

Commit

Permalink
Merge pull request #1062 from gircore/add-notify-methods
Browse files Browse the repository at this point in the history
Property: Add Notify / Unnotify methods
  • Loading branch information
badcel authored May 17, 2024
2 parents 9c0214b + 9b21c13 commit 139ee1d
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 5 deletions.
15 changes: 12 additions & 3 deletions docs/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.)

Expand All @@ -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.
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
);
```
43 changes: 41 additions & 2 deletions src/Libs/GObject-2.0/Public/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ public Property(string unmanagedName, string managedName)
/// <summary>
/// Get the value of this property in the given object.
/// </summary>
/// <exception cref="ArgumentException">If obj is not a GObject.Object.</exception>
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);
Expand All @@ -49,10 +50,11 @@ public T Get(K obj)
/// Set the value of this property in the given object
/// using the given value.
/// </summary>
/// <exception cref="ArgumentException">If obj is not a GObject.Object.</exception>
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);
Expand All @@ -70,4 +72,41 @@ private Type GetPropertyType(IntPtr handle)

return new Type(paramSpec.ValueType);
}

/// <summary>
/// Registers a signal handler to get notified if the property is changed.
/// </summary>
/// <param name="sender">The instance providing the property which should be listened to.</param>
/// <param name="signalHandler">The handler which should be invoked if the property changes.</param>
/// <param name="after">Define if the signal handler action must be called before or after the default handler.</param>
/// <exception cref="ArgumentException">If the sender is not a GObject.Object.</exception>
public void Notify(K sender, SignalHandler<Object, Object.NotifySignalArgs> 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
);
}

/// <summary>
/// Deregisters a signal handler.
/// </summary>
/// <param name="sender">The instance providing the property which should be deregistered.</param>
/// <param name="signalHandler">The signal handler which should be deregistered.</param>
/// <exception cref="ArgumentException">If the sender is not a GObject.Object.</exception>
public void Unnotify(K sender, SignalHandler<Object, Object.NotifySignalArgs> 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
);
}
}
20 changes: 20 additions & 0 deletions src/Tests/Libs/GirTest-0.1.Tests/PropertyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down

0 comments on commit 139ee1d

Please sign in to comment.