An open API service indexing awesome lists of open source software.

https://github.com/prophetlamb/immutableeditableobjectadapter

Source generator adapting immutable state `record`s into an `IEditableObject` replacing the `record` on edit, intended for `Binding` in a UNO WindowsCommunityToolkit `DataGrid`.
https://github.com/prophetlamb/immutableeditableobjectadapter

Last synced: 5 months ago
JSON representation

Source generator adapting immutable state `record`s into an `IEditableObject` replacing the `record` on edit, intended for `Binding` in a UNO WindowsCommunityToolkit `DataGrid`.

Awesome Lists containing this project

README

          

[![NuGet Version](https://img.shields.io/nuget/v/ImmutableEditableObjectAdapter)](https://www.nuget.org/packages/ImmutableEditableObjectAdapter) [![NuGet Downloads](https://img.shields.io/nuget/dt/ImmutableEditableObjectAdapter)](https://www.nuget.org/packages/ImmutableEditableObjectAdapter)

```bash
dotnet add package ImmutableEditableObjectAdapter
```

# ImmutableEditableObjectAdapter

Adapts immutable state `record`s into an `IEditableObject` replacing the `record` on edit, intended for `Binding` in a `DataGrid`.

```csharp
using System.ComponentModel;

Person p = new("Max", "Green", DateTimeOffset.Now.AddYears(-43), null);
EditablePerson editable = new(p);
editable.Edited += (s, e) => p = s.IsPropertyChanged(nameof(Person.Name)) ? e.NewValue : p;
editable.BeginEdit();
editable.Name = "Müller";
editable.EndEdit();
Console.WriteLine("Hello, World!");

internal sealed record Person(string Name, string FavouriteColor, DateTimeOffset BirthDay, DateTimeOffset? DeceasedAt);

internal sealed partial class EditablePerson : ImmutableEditableObjectAdapter;
```

Feel free to review the [generated code](https://github.com/ProphetLamb/ImmutableEditableObjectAdapter/blob/main/sample/ImmutableEditableObjectAdapter.Samples/GeneratedFiles/ImmutableEditableObjectAdapter/ImmutableEditableObjectAdapter.ImmutableEditableObjectAdapterGenerator/EditablePerson.g.cs#L7) for this example.

Generated `ImmutableEditableObjectAdapter` types mirrors the `public` Properties of the `record` passed as a generic type parameter. However, all properties have setter.
Each property, set to a different value than the property in the `Unedited` reference, is used to reconstruct `Unedited` into a new `record`:

```csharp
Person edited = Unedited with {
Name = NamePropertyChanged ? Name : Unedited.Name,
}
```

The constructed record is passed as `NewValue` to the `Edited` event, then set as the new `Unedited`.

## UNO Platform Integration

https://github.com/user-attachments/assets/26737d4e-9eeb-48d0-814c-687048ce235d

Binding commands to edits in UNO requires
- a `IValueConverter`
- a `ICommand` attached property

in addition to the above example. `ImmutableEditableObjectAdapter` generates these implementations.

**Declare the models**

```csharp
namespace ImmutableEditableObjectAdapter.Samples.Uno.Models;

public sealed record Person(
string Name,
[property: Display(Name = "Color")] string FavouriteColor,
DateTimeOffset BirthDay,
DateTimeOffset? DeceasedAt
);

public sealed partial class EditablePerson : System.ComponentModel.ImmutableEditableObjectAdapter;
```

**Declare the converter**

Annotate the `EditablePersonValueConverter` implementing `IValueConverter` with the `ImmutableEditableValueConverter` attribute for the type `EditablePerson`.

```csharp
namespace ImmutableEditableObjectAdapter.Samples.Uno.Converters;

[ImmutableEditableValueConverter(typeof(EditablePerson))]
public sealed partial class EditablePersonValueConverter : IValueConverter;
```

**Create the model**

- `Persons` provides data for the `DataGrid`.
- `LastEdited` informs the user about the latest changes.
- `PersonChanged` is invoked when a person changed.

```csharp
namespace ImmutableEditableObjectAdapter.Samples.Uno.Presentation;

public partial record MainModel
{
public IState LastEdited => State.Empty(this);

public IListState Persons => ListState.Value(this, IImmutableList () => [
new("Max", "Green", DateTimeOffset.Now.AddYears(-43), null),
new("Günter", "Orange", DateTimeOffset.Now.AddYears(-32), null),
]);

public async Task PersonChanged(EditedEventArgs edited)
{
if (edited.CancelledOrUnchanged)
{
return;
}

await LastEdited.UpdateAsync(_ => edited.NewValue);
}
}
```

**Create the UI**

```xml

```

```xml






```

Feel free to review the [generated code](https://github.com/ProphetLamb/ImmutableEditableObjectAdapter/blob/main/sample/ImmutableEditableObjectAdapter.Samples.Uno/ImmutableEditableObjectAdapter.Samples.Uno/GeneratedFiles/ImmutableEditableObjectAdapter/ImmutableEditableObjectAdapter.ImmutableEditableObjectAdapterGenerator/EditablePersonValueConverter.g.cs#L5) for this example.

## Customization and API

`ImmutableEditableObjectAdapter` API allows customizing the creation of events.

- OnPropertyChanging
- OnPropertyChanged
- OnEdited

```csharp
///
/// Provides the old, and new value of the .
///
/// The type of the contract record.
public sealed class EditedEventArgs : EventArgs
{
public TContract OldValue { get; }
public TContract NewValue { get; }
public bool CancelledOrUnchanged { get; }
}

///
/// Represents the method that will handle the event of an instance.
///
/// The type of the contract record.
public delegate void EditedEventHandler(
ImmutableEditableObjectAdapter sender,
EditedEventArgs args
) where TContract : notnull;

///
/// Non-generic interface implemented by .
///
public interface IImmutableEditableObjectAdapter : IEditableObject, INotifyPropertyChanged, INotifyPropertyChanging
{
///
/// Occurs once, before replaces the immutable state record, or discards changes.
///

/// sender is
///

/// event args is
///
void RegisterOnce(EventHandler callback);
}

///
/// Derive a sealed partial class to generate a from a immutable state record type.
///

/// Update the immutable state when the event indicates the state is replaced.
///
/// The type of the contract record.
public abstract class ImmutableEditableObjectAdapter
: IImmutableEditableObjectAdapter
where TContract : notnull
{
///
public event PropertyChangedEventHandler? PropertyChanged;

///
public event PropertyChangingEventHandler? PropertyChanging;

///
/// Occurs before replaces the immutable state record.
///
public event EditedEventHandler? Edited;

///
public abstract void BeginEdit();

///
public abstract void CancelEdit();

///
public abstract void EndEdit();

///
/// Enumerate names of all changed properties during edit, and .
///
public abstract IEnumerable ChangedProperties();

///
/// Indicates whether the property with the name name has changed during edit, and .
///
public abstract bool IsPropertyChanged(string propertyName);

protected virtual void OnPropertyChanging([CallerMemberName] string? propertyName = null);
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null);
protected virtual void OnEdited(TContract oldValue, TContract newValue);
protected bool SetField(ref T field, T value, [CallerMemberName] string? propertyName = null);
}

///
/// Generates an for your type, by annotating it with the converter type you wish to generate the members of.
///
/// The sealed partial class type of the to generate.
public sealed class ImmutableEditableValueConverterAttribute(Type valueConverterToGenerateType) : Attribute
{
public Type ValueConverterToGenerateType { get; } = valueConverterToGenerateType;
}
```