Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rolandkoenig/rolandk.avaloniaextensions
A .NET library which extends Avalonia with commonly used features like ViewServices, DependencyInjection and some Mvvm sugar
https://github.com/rolandkoenig/rolandk.avaloniaextensions
avalonia avaloniaui csharp dependency-injection dotnet extensions mvvm theme
Last synced: about 1 month ago
JSON representation
A .NET library which extends Avalonia with commonly used features like ViewServices, DependencyInjection and some Mvvm sugar
- Host: GitHub
- URL: https://github.com/rolandkoenig/rolandk.avaloniaextensions
- Owner: RolandKoenig
- License: mit
- Created: 2023-01-18T06:50:50.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-06-26T16:50:37.000Z (7 months ago)
- Last Synced: 2024-07-05T04:33:34.538Z (6 months ago)
- Topics: avalonia, avaloniaui, csharp, dependency-injection, dotnet, extensions, mvvm, theme
- Language: C#
- Homepage:
- Size: 256 KB
- Stars: 7
- Watchers: 3
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# RolandK.AvaloniaExtensions
## Common Information
A .NET library which extends Avalonia with commonly used features like ViewServices,
DependencyInjection and some Mvvm sugar## Build
[![Continuous integration](https://github.com/RolandKoenig/RolandK.AvaloniaExtensions/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/RolandKoenig/RolandK.AvaloniaExtensions/actions/workflows/continuous-integration.yml)## Nuget
| Package | Link |
|-------------------------------------------------|-------------------------------------------------------------------------------|
| RolandK.AvaloniaExtensions | https://www.nuget.org/packages/RolandK.AvaloniaExtensions |
| RolandK.AvaloniaExtensions.DependencyInjection | https://www.nuget.org/packages/RolandK.AvaloniaExtensions.DependencyInjection |
| RolandK.AvaloniaExtensions.ExceptionHandling | https://www.nuget.org/packages/RolandK.AvaloniaExtensions.ExceptionHandling |
| RolandK.AvaloniaExtensions.FluentThemeDetection | (obsolete due to Avalonia 11) |## Feature overview
- [ViewServices over the popular Mvvm pattern by **not** providing an own Mvvm implementation](#viewservices-over-the-popular-mvvm-pattern)
- [Some default ViewServices (FileDialogs, MessageBox)](#some-default-viewservices)
- [Notification on ViewModels when view is attaching and detaching](#notification-on-viewmodels-when-view-is-attaching-and-detaching)
- [DependencyInjection for Avalonia based on Microsft.Extensions.DependencyInjection](#dependencyinjection-for-avalonia-based-on-microsftextensionsdependencyinjection)
- [Error dialog for unhandled exceptions](#error-dialog-for-unhandled-exceptions)
- [Global error handling for unhandled exceptions](#global-error-handling-for-unhandled-exceptions)# Samples
Here you find samples to the features of RolandK.AvaloniaExtensions. Most of
these features work for themselves and are self-contained. They have no dependencies to
other features of RolandK.AvaloniaExtensions. As Mvvm framework I use
[CommunityToolkit.Mvvm](https://www.nuget.org/packages/CommunityToolkit.Mvvm) in all samples - but you are free to use another one.
RolandK.AvaloniaExtensions has no dependencies on any Mvvm library and does not try to be an own implementation.You can also take a look into the unittest projects. There you find
full examples for each provided feature.## ViewServices over the popular Mvvm pattern
Add nuget package [RolandK.AvaloniaExtensions](https://www.nuget.org/packages/RolandK.AvaloniaExtensions).ViewServices in RolandK.AvaloniaExtensions are interfaces provided by views (Windows, UserControls, etc.).
A view attaches itself to a view model using the IAttachableViewModel interface. Therefore, you have to
implement this interface on your own view models. The following sample implementation is derived
from ObservableObject of CommunityToolkit.Mvvm.```csharp
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using RolandK.AvaloniaExtensions.Mvvm;
using RolandK.AvaloniaExtensions.ViewServices.Base;namespace RolandK.AvaloniaExtensions.TestApp;
public class OwnViewModelBase : ObservableObject, IAttachableViewModel
{
private object? _associatedView;
///
public event EventHandler? CloseWindowRequest;
///
public event EventHandler? ViewServiceRequest;///
public object? AssociatedView
{
get => _associatedView;
set
{
if(_associatedView != value)
{
_associatedView = value;
this.OnAssociatedViewChanged(_associatedView);
}
}
}protected T? TryGetViewService()
where T : class
{
var requestViewServiceArgs = new ViewServiceRequestEventArgs(typeof(T));
this.ViewServiceRequest?.Invoke(this, requestViewServiceArgs);
return requestViewServiceArgs.ViewService as T;
}
protected T GetViewService()
where T : class
{
var viewService = this.TryGetViewService();
if (viewService == null)
{
throw new InvalidOperationException($"ViewService {typeof(T).FullName} not found!");
}return viewService;
}protected void CloseHostWindow(object? dialogResult = null)
{
if (this.CloseWindowRequest == null)
{
throw new InvalidOperationException("Unable to call Close on host window!");
}
this.CloseWindowRequest.Invoke(
this,
new CloseWindowRequestEventArgs(dialogResult));
}
protected void OnAssociatedViewChanged(object? associatedView)
{
}
}
```Now you can access ViewServices from within the view model by calling
GetViewService or TryGetViewService. The later does not throw an exception,
when the ViewService can not be found.In order for that to work, you also have to use one of the base classes MvvmWindow or MvvmUserControl on the
view side. They are responsible for attaching to the view model and detaching again, when
the view is closed. Be sure that you also derive from the correct base class in
the corresponding code behind. Attention: You also have to set the 'ViewFor' property
on MvvmWindow or MvvmUserControl. The reason behind this is that DataContext is also set
on child elements automatically. If one of these would also derive from MvvmWindow oder
MvvmUserControl, these one would attach to your ViewModel too.```xml
```
Register own ViewServices using the ViewServices property of MvvmWindow or
MvvmUserControl.The following code snipped is a command implementation within the view model.
It uses the ViewServices IOpenFileViewServices and IMessageBoxService. Both of
them are provided by default by RolandK.AvaloniaExtensions.```csharp
[RelayCommand]
public async Task OpenFileAsync()
{
var srvOpenFile = this.GetViewService();
var srvMessageBox = this.GetViewService();
var selectedFile = await srvOpenFile.ShowOpenFileDialogAsync(
Array.Empty(),
"Open file");
if (string.IsNullOrEmpty(selectedFile)) { return; }
await srvMessageBox.ShowAsync(
"Open file",
$"File {selectedFile} selected", MessageBoxButtons.Ok);
}
```## Some default ViewServices
RolandK.AvaloniaExtensions provides the following default ViewServices:
- IMessageBoxViewService
- IOpenDirectoryViewService
- IOpenFileViewService
- ISaveFileViewService## Notification on ViewModels when view is attaching and detaching
As some kind of extension to the provided ViewService feature, the IAttachableViewModel
interface can be used to react on attaching / detaching of the view from within the view model.
You can use this for example to start and stop a timer in the view model.The following code snipped shows how to write a OnAssociatedViewChanged method
on a view model base class.```csharp
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using RolandK.AvaloniaExtensions.Mvvm;
using RolandK.AvaloniaExtensions.ViewServices.Base;namespace RolandK.AvaloniaExtensions.TestApp;
public class OwnViewModelBase : ObservableObject, IAttachableViewModel
{
private object? _associatedView;
//. ..
///
public object? AssociatedView
{
get => _associatedView;
set
{
if(_associatedView != value)
{
_associatedView = value;
this.OnAssociatedViewChanged(_associatedView);
}
}
}
// ...
protected void OnAssociatedViewChanged(object? associatedView)
{
}
}
```## DependencyInjection for Avalonia based on Microsft.Extensions.DependencyInjection
Add nuget package [RolandK.AvaloniaExtensions.DependencyInjection](https://www.nuget.org/packages/RolandK.AvaloniaExtensions.DependencyInjection)Enable DependencyInjection by calling UseDependencyInjection on AppBuilder during
startup of your Avalonia application. This method registers the ServiceProvider as
a globally available resource on your Application object. You can find the key
of the resource within the constant DependencyInjectionConstants.SERVICE_PROVIDER_RESOURCE_KEY.```csharp
using RolandK.AvaloniaExtensions.DependencyInjection;public static class Program
{
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
//...
.UseDependencyInjection(services =>
{
// Services
services.AddSingleton();
// ViewModels
services.AddTransient();
});
}
```Now you can inject ViewModels via the MarkupExtension CreateUsingDependencyInjection
in xaml namespace 'https://github.com/RolandK.AvaloniaExtensions'```xml
```
## Error dialog for unhandled exceptions
Add nuget package [RolandK.AvaloniaExtensions.ErrorHandling](https://www.nuget.org/packages/RolandK.AvaloniaExtensions.ExceptionHandling)Then use a try-catch block like the following to show a dialog for unhandled exceptions.
```csharp
try
{
// Some logic
}
catch (Exception ex)
{
await GlobalErrorReporting.ShowGlobalExceptionDialogAsync(ex, this);
}
```The method GlobalErrorReporting.ShowGlobalExceptionDialogAsync opens following modal dialog:
![Unhandled exception dialog](assets/screenshots/unhandled-exception-dialog.png)## Global error handling for unhandled exceptions
One draw back for Avalonia is that is does not offer something similar to
[Application.DispatcherUnhandledException](https://learn.microsoft.com/en-us/dotnet/api/system.windows.application.dispatcherunhandledexception)
in WPF. Therefore, you have little change to react anyhow on errors which you never expected
to happen. The only way you can handle these kind of exceptions is to wrap the entry point
of your application with a global try-catch. In order to show an error dialog in this case
I have the following solution.Add nuget package [RolandK.AvaloniaExtensions.ErrorHandling](https://www.nuget.org/packages/RolandK.AvaloniaExtensions.ExceptionHandling)
Now modify the entry point of your application to handle exceptions like in the sample
application of this repository.
```csharp
[STAThread]
public static void Main(string[] args)
{
try
{
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}
catch (Exception ex)
{
GlobalErrorReporting.TryShowBlockingGlobalExceptionDialogInAnotherProcess(
ex,
".",
".ExceptionViewer");
throw;
}
}
```So, what does GlobalErrorReporting.TryShowBlockingGlobalExceptionDialogInAnotherProcess do?
The problem here is, that we can't just show a dialog. We don't know in which state the
Avalonia application is currently. So, we need something to show the error dialog in a separate
process. GlobalErrorReporting.TryShowBlockingGlobalExceptionDialogInAnotherProcess does exactly this.
It collects error information, serializes it and sends it to a new instance of the
application '.ExceptionViewer'. So, just the application
'.ExceptionViewer' is now missing.In the next step, create a new Avalonia application in your solution that is called
'.ExceptionViewer'. There you also reference RolandK.AvaloniaExtensions.ErrorHandling.
Then you can remove MainWindow.axaml and modify App.xaml.cs to look like the following:```csharp
public partial class App : ExceptionViewerApplication
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
}
```The base class ExceptionViewerApplication does the job then. It reads exception information
from incoming arguments and shows the error dialog.One last thing. You also need to add a reference from your application to '.ExceptionViewer'.
This ensures that the executable of our exception viewer is copied to the output directory
of your application.