Working on ReadMe for custom Interaction sample

This commit is contained in:
timunie 2023-08-31 21:18:28 +02:00
Родитель a0913c944c
Коммит af970ca1a2
1 изменённых файлов: 161 добавлений и 1 удалений

Просмотреть файл

@ -132,6 +132,167 @@ Now we can setup the `XAML` as following. Mind the binding of the `Button` to ou
Running the App you should be able to select any files on your system.
== Solution 2 : Write your own Interaction-class
If you don't want to use Reactive-UI you can also write your own `Interaction`-class to provide a similar functionality. In this section you will see one possible solution.
NOTE: We are going to use the https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/[[CommunityToolkit.Mvvm\]] in this sample, especially their source generators. If you are not familiar with it yet, read the online docs they provide first.
=== Step1: Create the Interaction-class
In our project (or in a class library we use) we add a folder called `Core`. Inside this folder we add a new generic class called `Interaction`, which has basically the below mentioned API.
The class will have two generic parameters:
TInput:: The type of the input we expect
TOutput:: The type of the output we expect
It will implement two interfaces:
ICommand:: This interface helps us to use the interaction like any other command
IDisposable:: This interface helps us to unregister from event listeners
In addition we will add two methods:
IDisposable RegisterHandler(Func<TInput, Task<TOutput>> handler):: This method will be used by the View to register the action to be performed.
Task<TOutput> HandleAsync(TInput input):: This method will be called from the `ViewModel` with a given input and the `View` will return the requested output.
And this is how the final class looks like:
[source,c#]
----
/// <summary>
/// Simple implementation of Interaction pattern from ReactiveUI framework.
/// https://www.reactiveui.net/docs/handbook/interactions/
/// </summary>
public sealed class Interaction<TInput, TOutput> : IDisposable, ICommand
{
// this is a reference to the registered interaction handler.
private Func<TInput, Task<TOutput>>? _handler;
/// <summary>
/// Performs the requested interaction <see langword="async"/>. Returns the result provided by the View
/// </summary>
/// <param name="input">The input parameter</param>
/// <returns>The result of the interaction</returns>
/// <exception cref="InvalidOperationException"></exception>
public Task<TOutput> HandleAsync(TInput input)
{
if (_handler is null)
{
throw new InvalidOperationException("Handler wasn't registered");
}
return _handler(input);
}
/// <summary>
/// Registers a handler to our Interaction
/// </summary>
/// <param name="handler">the handler to register</param>
/// <returns>a disposable object to clean up memory if not in use anymore/></returns>
/// <exception cref="InvalidOperationException"></exception>
public IDisposable RegisterHandler(Func<TInput, Task<TOutput>> handler)
{
if (_handler is not null)
{
throw new InvalidOperationException("Handler was already registered");
}
_handler = handler;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
return this;
}
public void Dispose()
{
_handler = null;
}
public bool CanExecute(object? parameter) => _handler is not null;
public void Execute(object? parameter) => HandleAsync((TInput?)parameter!);
public event EventHandler? CanExecuteChanged;
}
----
=== Step 2: Prepare the ViewModel
In our `CustomInteractionViewModel` we need to add a new instance of the `Interaction`. In our sample we want to provide a dialog title (`string`) as the input and we expect a list of selected files (`string[]?`)
[soruce,c#]
----
/// <summary>
/// Gets an instance of our own Interaction class
/// </summary>
public Interaction<string, string[]?> SelectFilesInteraction { get; } = new Interaction<string, string[]?>();
----
In a next step we add a Command which will call the interaction:
[soruce,c#]
----
[RelayCommand]
private async Task SelectFilesAsync()
{
SelectedFiles = await SelectFilesInteraction.HandleAsync("Hello from Avalonia");
}
----
=== Step 3: Prepare the View
Somehow we need to register the `View` to the `Interaction` of the `ViewModel`. In Avalonia we have an event called `OnDataContextChanged` which we can listen to, or, if we are in code behind, simply override it.
[soruce, c#]
----
// Stores a reference to the disposable in order to clean it up if needed
IDisposable? _selectFilesInteractionDisposable;
protected override void OnDataContextChanged(EventArgs e)
{
// Dispose any old handler
_selectFilesInteractionDisposable?.Dispose();
if (DataContext is CustomInteractionViewModel vm)
{
// register the interaction handler
_selectFilesInteractionDisposable =
vm.SelectFilesInteraction.RegisterHandler(InteractionHandler);
}
base.OnDataContextChanged(e);
}
----
WARNING: Remember that the DataContext can change several times. In order to not get any memory leak, we have to dispose any earlier registration to an older view model
The interaction handler itself is quite simple
[soruce,c#]
----
private async Task<string[]?> InteractionHandler(string input)
{
// Get a reference to our TopLevel (in our case the parent Window)
var topLevel = TopLevel.GetTopLevel(this);
// Try to get the files
var storageFiles = await topLevel!.StorageProvider.OpenFilePickerAsync(
new FilePickerOpenOptions()
{
AllowMultiple = true,
Title = input
});
// Transform the files as needed and return them. If no file was selected, null will be returned
return storageFiles?.Select(x => x.Name)?.ToArray();
}
----
=== Step 4: See it in action
Run the App and try to select as many files as you like.
== Related
@ -139,7 +300,6 @@ There are more ways to show dialogs from the ViewModel, for example:
* link:../AdvancedMvvmDialogSample[Dialog Service]
* https://github.com/AvaloniaCommunity/awesome-avalonia#mvvm--mvp--mvu[third party libs]
// Any related information or further readings goes here.