https://github.com/michaldivis/dark-helpers
A collection of MVVM helpers for .NET MAUI, Xamarin.Forms and WPF. Includes a navigation service, observable collection, observable object and async command.
https://github.com/michaldivis/dark-helpers
maui mvvm navigation-service observable-collection wpf xamarin-forms
Last synced: about 1 month ago
JSON representation
A collection of MVVM helpers for .NET MAUI, Xamarin.Forms and WPF. Includes a navigation service, observable collection, observable object and async command.
- Host: GitHub
- URL: https://github.com/michaldivis/dark-helpers
- Owner: michaldivis
- License: mit
- Created: 2021-01-19T06:37:56.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2022-09-20T07:51:03.000Z (over 2 years ago)
- Last Synced: 2025-04-24T10:47:52.376Z (about 2 months ago)
- Topics: maui, mvvm, navigation-service, observable-collection, wpf, xamarin-forms
- Language: C#
- Homepage:
- Size: 944 KB
- Stars: 4
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# DarkHelpers
A collection of MVVM helpers for WPF, Xamarin.Forms and .NET MAUI.I wanted to create this library for my self, however, I'll be stoked if anyone else finds it useful. Feel free to request more features or changes.
*Code & inspiration:
I've used the `ObservableObject`, `ObservableRangeCollection` and `ICommand` implementations from [James Montemagno's MVVM-Helpers library](https://github.com/jamesmontemagno/mvvm-helpers), with some minor tweaks here and there. Thank you James!*Status:

[](https://github.com/michaldivis/DarkHelpers/blob/master/README.md)## Packages
[](https://www.nuget.org/packages/Divis.DarkHelpers) [](https://www.nuget.org/packages/Divis.DarkHelpers)\
General package, includes all the interfaces and some shared implementations.
```powershell
Install-Package Divis.DarkHelpers
```[](https://www.nuget.org/packages/Divis.DarkHelpers.WPF) [](https://www.nuget.org/packages/Divis.DarkHelpers.WPF)\
Platform specific implementations (base view, navigation service, etc) for WPF.
```powershell
Install-Package Divis.DarkHelpers.WPF
```[](https://www.nuget.org/packages/Divis.DarkHelpers.XF) [](https://www.nuget.org/packages/Divis.DarkHelpers.XF)\
Platform specific implementations (base view, navigation service, etc) for Xamarin.Forms.
```powershell
Install-Package Divis.DarkHelpers.XF
```[](https://www.nuget.org/packages/Divis.DarkHelpers.Maui) [](https://www.nuget.org/packages/Divis.DarkHelpers.Maui)\
Platform specific implementations (base view, navigation service, etc) for .NET MAUI.
```powershell
Install-Package Divis.DarkHelpers.Maui
```## Features
### DarkObservableObject
Simple implementation of INotifyPropertyChanged that any class can inherit from.```csharp
public class MyObject : DarkObservableObject
{
private string _firstName;
public string FirstName
{
get => _firstName;
set => SetProperty(ref _firstName, value, onChanged: DoSomething);
}
private void DoSomething()
{
//react to changes
}
}
```### DarkViewModel
A base view model class that implements `INotifyPropertyChanged` and has all the properties and events you would usually need.Properties:
- `IsBusy`
- `IsNotBusy`
- `CanLoadMore`
- `IsLoadingMore`Events (virtual methods):
- `OnInitializeAsync` - One time initialzation method, to be called when the view model is first used
- `OnRefreshAsync` - To be called whenever the view model is brought to user's attention, i.e. anytime the corresponding view is shown
- `OnBeforeExitAsync` - To be called before a view is exited, determines whether the exit should be continued or cancelled
- `OnExitAsync` - To be called when a view is exiting, useful for any cleanup workEvent support:
| | WPF | Xamarin.Forms | .NET MAUI |
| ------------- | ------------- | ------------- | ------------- |
| `OnInitializeAsync` | Yes | Yes | Yes |
| `OnRefreshAsync` | No | Yes | Yes |
| `OnBeforeExitAsync` | Yes | No | No |
| `OnExitAsync` | Yes | Yes | Yes |### DarkObservableCollection
A ObservableCollection that adds important methods such as: AddRange, RemoveRange, Replace, and ReplaceRange.```csharp
public DarkObservableCollection Items { get; set; } = new DarkObservableCollection();private void ReloadItems(){
var items = _dataService.GetItems();
Items.ReplaceRange(items);
}
```#### Enable synchronization
Collection synchronization can be enabled using the static `DarkObservableCollectionSettings` class. Since collection synchronization is implemented differently for WPF and Xamarin.Forms, the initialization differs a bit.WPF (App.xaml.cs)
```csharp
using DarkHelpers.Collections;protected override void OnStartup(StartupEventArgs e)
{
DarkObservableCollectionSettings.RegisterSynchronizer();//other code
}
```Xamarin.Forms (App.xaml.cs)
```csharp
using DarkHelpers.Collections;public App()
{
DarkObservableCollectionSettings.RegisterSynchronizer();//other code
}
```.NET MAUI (App.xaml.cs)
```csharp
using DarkHelpers.Collections;public App()
{
DarkObservableCollectionSettings.RegisterSynchronizer();//other code
}
```### IDarkNavigationService
Navigate freely, even from a class library where you might be storing your view models.It works like this:
- register the ViewModel & View pairs to the instance of the platform specific nagivation service (`DarkXfNavigationService` for Xamarin.Forms and `DarkWpfNavigationService` for WPF). The view model has to implement the `DarkViewModel` class and the view has to implement the specific view base class (`DarkWpfViewBase` for WPF and `DarkXfViewBase` class for Xamarin.Forms). More on that in the "Custom base view" section
- store the platform specific implementation as an instance of `IDarkNavigationService` in a DI container (or wherever)
- only use the `IDarkNavigationService` instance to perform navigation#### ViewModel & View registration
WPF:
```csharp
using DarkHelpers.Abstractions;
using DarkHelpers.WPF;var nav = new DarkWpfNavigationService();
nav.Register();
nav.Register();
nav.Register();someContainer.RegisterSingleton(nav);
```Xamarin.Forms:
```csharp
using DarkHelpers.Abstractions;
using DarkHelpers.XF;var nav = new DarkXfNavigationService();
nav.Register();
nav.Register();
nav.Register();someContainer.RegisterSingleton(nav);
```.NET MAUI:
```csharp
using DarkHelpers.Abstractions;
using DarkHelpers.Maui;var nav = new DarkMauiNavigationService();
nav.Register();
nav.Register();
nav.Register();someContainer.RegisterSingleton(nav);
```#### Usage
```csharp
using DarkHelpers;var nav = someContainer.Get();
await nav.PushAsync(new HomeViewModel());
await nav.PopAsync();
```### DarkAsyncCommand, DarkCommand, and DarkEventManager
Synchronous and asynchronous ICommand implementations, plus a DarkEventManager to help your events be garbage collection safe
### Custom base view
Custom base view that allows the navigation by view model.
*NOTE: you can now use my Visual Studio extension that contains view templates for WPF, Xamarin.Forms and .NET MAUI for easier view creation. The extension can be found [HERE](https://marketplace.visualstudio.com/items?itemName=michaldivis.DarkHelpersTemplates)*
#### Creating a WPF view
Create a new `Window` and change the code to look like this.
*I'm using the SomeApp as my example project namespace*
SomeView.xaml
```xaml
```
SomeView.xaml.cs
```csharp
using DarkHelpers.WPF;
using SomeApp.ViewModels;namespace SomeApp.Views
{
public partial class SomeView : DarkWpfViewBase
{
public HomeView(SomeViewModel vm) : base(vm)
{
InitializeComponent();
}
}
}
```#### Creating a Xamarin.Forms view
Create a new `ContentPage` and change the code to look like this.
*I'm using the SomeApp as my example project namespace*
SomeView.xaml
```xaml
```
SomeView.xaml.cs
```csharp
using DarkHelpers.XF;
using SomeApp.ViewModels;
using Xamarin.Forms.Xaml;namespace SomeApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SomeView : DarkXfViewBase
{
public HomeView(SomeViewModel vm) : base(vm)
{
InitializeComponent();
}
}
}
```#### Creating a .NET MAUI view
SomeView.xaml
```xaml
```
SomeView.xaml.cs
```csharp
using DarkHelpers.Maui;
using SomeApp.ViewModels;namespace SomeApp.Views;
public partial class SomeView : DarkMauiViewBase
{
public SomeView(SomeViewModel vm) : base(vm)
{
InitializeComponent();
}
}
```### Extensions
#### SafeFireAndForget (for `Task`)
Safely fire and forget a `Task` while being able to handle exceptions. Sometimes you don't care about waiting for a `Task` to complete or you can't await it (in an event). That's what this extension is for.
```csharp
using DarkHelpers;protected override void OnAppearing()
{
//use an Action to handle exceptions
Action errorHandler = (ex) => Console.WriteLine(ex);
DoSomethingAsync().SafeFireAndForget(errorHandler);//or use the ITaskErrorHandler to handle exceptions
ITaskErrorHandler taskErrorHandler = null; //this might get injected by DI
DoSomethingAsync().SafeFireAndForget(taskErrorHandler);
}private async Task DoSomethingAsync()
{
//simulate work
await Task.Delay(500);
}
```#### TryExecute (for `ICommand`)
This is useful for executing `ICommand`s manually. It checks the `CanExecute` and calls the `Execute` method if `CanExecute` returns `true`.```csharp
using DarkHelpers;//this might be called from a code behind of a page if there's no option to bind the command in XAML
Book book = null;
viewModel.LoadItemCommand.TryExecute(book);
```### Converter base classes
Simplify your converters with the `DarkConverterBase` (`IValueConverter`) and `DarkMultiConverterBase` (`IMultiValueConverter`) base classes.Simply inherit from either of these base classes and only override the methods you need (`Convert` or `ConvertBack` or both). The rest will throw a verbose `NotSupportedException`.
####
Before:
```csharp
public class ToUpperTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is string text)
{
return text.ToUpper();
}return value;
}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException($"The {nameof(ConvertBack)} method is not supported in {nameof(ToUpperTextConverter)}.");
}
}
```After:
```csharp
public class ToUpperCaseTextConverter : DarkConverterBase
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string text)
{
return text.ToUpper();
}return value;
}
}
```