diff --git a/Directory.Build.props b/Directory.Build.props
index 572dfe7..4d4d0a8 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -15,7 +15,10 @@
https://github.com/AvantiPoint/mauimicromvvm
en
enable
+ enable
true
+ $(NoWarn);NU1507
+ $(MSBuildProjectName.Contains('MauiMicroMvvm'))
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000..becdd4d
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,5 @@
+
+
+ AvantiPoint.$(AssemblyName)
+
+
\ No newline at end of file
diff --git a/MauiMicroMvvm.sln b/MauiMicroMvvm.sln
index c005917..d368dd1 100644
--- a/MauiMicroMvvm.sln
+++ b/MauiMicroMvvm.sln
@@ -18,6 +18,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{58DDD03C-E4B7-431A-8BC4-B8A8199C3DCD}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
+ Directory.Build.targets = Directory.Build.targets
+ Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
Global
diff --git a/sample/MauiMicroSample/App.xaml b/sample/MauiMicroSample/App.xaml
new file mode 100644
index 0000000..dae79e2
--- /dev/null
+++ b/sample/MauiMicroSample/App.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/MauiMicroSample/App.xaml.cs b/sample/MauiMicroSample/App.xaml.cs
new file mode 100644
index 0000000..0d5a419
--- /dev/null
+++ b/sample/MauiMicroSample/App.xaml.cs
@@ -0,0 +1,9 @@
+namespace MauiMicroSample;
+
+public partial class App : Application
+{
+ public App()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/sample/MauiMicroSample/MauiProgram.cs b/sample/MauiMicroSample/MauiProgram.cs
index c3672ea..9ed66f1 100644
--- a/sample/MauiMicroSample/MauiProgram.cs
+++ b/sample/MauiMicroSample/MauiProgram.cs
@@ -13,9 +13,8 @@ public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
- .UseMauiMicroMvvm(
- "Resources/Styles/Colors.xaml",
- "Resources/Styles/Styles.xaml")
+ .UseMauiApp()
+ .UseMauiMicroMvvm()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
diff --git a/src/MauiMicroMvvm.Rx/MauiMicroMvvm.Rx.csproj b/src/MauiMicroMvvm.Rx/MauiMicroMvvm.Rx.csproj
index 8a6c3ed..cc6b1c6 100644
--- a/src/MauiMicroMvvm.Rx/MauiMicroMvvm.Rx.csproj
+++ b/src/MauiMicroMvvm.Rx/MauiMicroMvvm.Rx.csproj
@@ -2,12 +2,9 @@
$(DotNetVersion)
- true
- enable
True
MauiMicroMvvm.Rx is the perfect companion for people who love Reactive design & want to couple it with MauiMicro. With MauiMicro Rx you get an Observables first base ViewModel that let's you design your code around an observable for the App & View lifecycles.
dotnet-maui;mvvm;mauimicro;reactive;rx
- AvantiPoint.$(AssemblyName)
$(AssemblyName)
diff --git a/src/MauiMicroMvvm.Rx/RxMauiMicroViewModel.cs b/src/MauiMicroMvvm.Rx/RxMauiMicroViewModel.cs
index f2e4adf..012ec52 100644
--- a/src/MauiMicroMvvm.Rx/RxMauiMicroViewModel.cs
+++ b/src/MauiMicroMvvm.Rx/RxMauiMicroViewModel.cs
@@ -12,7 +12,7 @@ public class RxMauiMicroViewModel : ReactiveObject, IViewModelActivation, IViewL
private readonly Subject _viewLifecycleState;
private readonly Subject> _queryParameters;
private readonly Lazy _lazyLogger;
- protected ObservableAsPropertyHelper IsBusyHelper;
+ protected ObservableAsPropertyHelper? IsBusyHelper;
private readonly ObservableAsPropertyHelper _isNotBusyHelper;
protected readonly CompositeDisposable Disposables;
@@ -21,7 +21,7 @@ public RxMauiMicroViewModel(ViewModelContext context)
_applifecycleState = new Subject();
_viewLifecycleState = new Subject();
_queryParameters = new Subject>();
- Disposables = new CompositeDisposable();
+ Disposables = [];
Navigation = context.Navigation;
PageDialogs = context.PageDialogs;
_lazyLogger = new Lazy(() => context.Logger.CreateLogger(GetType().Name));
diff --git a/src/MauiMicroMvvm.Templates/MauiMicroMvvm.Templates.csproj b/src/MauiMicroMvvm.Templates/MauiMicroMvvm.Templates.csproj
index e3f9158..9c4cccd 100644
--- a/src/MauiMicroMvvm.Templates/MauiMicroMvvm.Templates.csproj
+++ b/src/MauiMicroMvvm.Templates/MauiMicroMvvm.Templates.csproj
@@ -7,9 +7,9 @@
false
false
True
+ true
Project Template for Maui Micro by AvantiPoint
dotnet-maui;mauimicro;mauimicromvvm;mauimicrotemplates;templates;mvvm;maui;
- AvantiPoint.$(AssemblyName)
MauiMicroMvvm Templates
$(NoWarn);NU5128
diff --git a/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/App.xaml b/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/App.xaml
new file mode 100644
index 0000000..da2fb5c
--- /dev/null
+++ b/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/App.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/App.xaml.cs b/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/App.xaml.cs
new file mode 100644
index 0000000..fbb4295
--- /dev/null
+++ b/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/App.xaml.cs
@@ -0,0 +1,9 @@
+namespace MauiMicroApp._1;
+
+public partial class App : Application
+{
+ public App()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/MauiProgram.cs b/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/MauiProgram.cs
index 451222e..4ea3742 100644
--- a/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/MauiProgram.cs
+++ b/src/MauiMicroMvvm.Templates/content/MauiMicroApp.1/MauiProgram.cs
@@ -10,9 +10,8 @@ public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
- .UseMauiMicroMvvm(
- "Resources/Styles/Colors.xaml",
- "Resources/Styles/Styles.xaml")
+ .UseMauiApp()
+ .UseMauiMicroMvvm()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
diff --git a/src/MauiMicroMvvm/Behaviors/BehaviorFactory.cs b/src/MauiMicroMvvm/Behaviors/BehaviorFactory.cs
new file mode 100644
index 0000000..9b15d88
--- /dev/null
+++ b/src/MauiMicroMvvm/Behaviors/BehaviorFactory.cs
@@ -0,0 +1,27 @@
+namespace MauiMicroMvvm.Behaviors;
+
+public sealed class BehaviorFactory : IBehaviorFactory
+{
+ private readonly IEnumerable _behaviors;
+ private readonly IServiceProvider _services;
+
+ public BehaviorFactory(IServiceProvider services, IEnumerable behaviors)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ _behaviors = behaviors ?? [];
+ _services = services;
+ }
+
+ public void ApplyBehaviors(VisualElement element)
+ {
+ foreach (var registration in _behaviors)
+ {
+ if (!registration.ViewType.IsAssignableFrom(registration.ViewType))
+ continue;
+
+ var behavior = registration.GetBehavior();
+ if (behavior is not null)
+ element.Behaviors.Add(behavior);
+ }
+ }
+}
diff --git a/src/MauiMicroMvvm/Behaviors/DelegateViewBehavior.cs b/src/MauiMicroMvvm/Behaviors/DelegateViewBehavior.cs
new file mode 100644
index 0000000..07c129b
--- /dev/null
+++ b/src/MauiMicroMvvm/Behaviors/DelegateViewBehavior.cs
@@ -0,0 +1,24 @@
+namespace MauiMicroMvvm.Behaviors;
+
+internal sealed class DelegateViewBehavior(Action onAttached, Action onDetached) : Behavior
+ where TView : VisualElement
+{
+ private readonly Action _onAttached = onAttached;
+ private readonly Action _onDetached = onDetached;
+
+ protected override void OnAttachedTo(TView bindable)
+ {
+ base.OnAttachedTo(bindable);
+ var serviceProvider = bindable.Handler?.MauiContext?.Services;
+ ArgumentNullException.ThrowIfNull(serviceProvider);
+ _onAttached(serviceProvider, bindable);
+ }
+
+ protected override void OnDetachingFrom(TView bindable)
+ {
+ base.OnDetachingFrom(bindable);
+ var serviceProvider = bindable.Handler?.MauiContext?.Services;
+ ArgumentNullException.ThrowIfNull(serviceProvider);
+ _onDetached(serviceProvider, bindable);
+ }
+}
diff --git a/src/MauiMicroMvvm/Behaviors/IBehaviorFactory.cs b/src/MauiMicroMvvm/Behaviors/IBehaviorFactory.cs
new file mode 100644
index 0000000..69a0afc
--- /dev/null
+++ b/src/MauiMicroMvvm/Behaviors/IBehaviorFactory.cs
@@ -0,0 +1,6 @@
+namespace MauiMicroMvvm.Behaviors;
+
+public interface IBehaviorFactory
+{
+ void ApplyBehaviors(VisualElement element);
+}
diff --git a/src/MauiMicroMvvm/Behaviors/IRegisteredBehavior.cs b/src/MauiMicroMvvm/Behaviors/IRegisteredBehavior.cs
new file mode 100644
index 0000000..e499636
--- /dev/null
+++ b/src/MauiMicroMvvm/Behaviors/IRegisteredBehavior.cs
@@ -0,0 +1,7 @@
+namespace MauiMicroMvvm.Behaviors;
+
+public interface IRegisteredBehavior
+{
+ Type ViewType { get; }
+ Behavior GetBehavior();
+}
diff --git a/src/MauiMicroMvvm/Behaviors/RegisteredBehavior.cs b/src/MauiMicroMvvm/Behaviors/RegisteredBehavior.cs
new file mode 100644
index 0000000..f50df28
--- /dev/null
+++ b/src/MauiMicroMvvm/Behaviors/RegisteredBehavior.cs
@@ -0,0 +1,10 @@
+namespace MauiMicroMvvm.Behaviors;
+
+internal class RegisteredBehavior(IServiceProvider Services) : IRegisteredBehavior
+ where TView : VisualElement
+ where TBehavior : Behavior
+{
+ public Type ViewType => typeof(TView);
+
+ public Behavior GetBehavior() => Services.GetRequiredService();
+}
diff --git a/src/MauiMicroMvvm/Common/MvvmHelpers.cs b/src/MauiMicroMvvm/Common/MvvmHelpers.cs
new file mode 100644
index 0000000..fecc3e5
--- /dev/null
+++ b/src/MauiMicroMvvm/Common/MvvmHelpers.cs
@@ -0,0 +1,42 @@
+using Microsoft.Maui.Controls;
+
+namespace MauiMicroMvvm.Common;
+
+public static class MvvmHelpers
+{
+ public static void InvokeViewViewModelAction(object? value, Action action)
+ {
+ if (value is T valueAsT)
+ {
+ action(valueAsT);
+ }
+
+ if (value is BindableObject bindable)
+ {
+ InvokeViewViewModelAction(bindable.BindingContext, action);
+ }
+ }
+
+ public static async Task InvokeViewViewModelActionAsync(object? value, Func action)
+ {
+ if (value is T valueAsT)
+ {
+ await action(valueAsT);
+ }
+
+ if (value is BindableObject bindable)
+ {
+ await InvokeViewViewModelActionAsync(bindable.BindingContext, action);
+ }
+ }
+
+ public static void Destroy(object? page)
+ {
+ InvokeViewViewModelAction(page, x => x.Dispose());
+ }
+
+ public static Task DestroyAsync(object? page)
+ {
+ return InvokeViewViewModelActionAsync(page, x => x.DisposeAsync().AsTask());
+ }
+}
diff --git a/src/MauiMicroMvvm/Internals/AppLifecycleBehavior.cs b/src/MauiMicroMvvm/Internals/AppLifecycleBehavior.cs
index 5345a33..0fecae2 100644
--- a/src/MauiMicroMvvm/Internals/AppLifecycleBehavior.cs
+++ b/src/MauiMicroMvvm/Internals/AppLifecycleBehavior.cs
@@ -1,4 +1,5 @@
using System.ComponentModel;
+using MauiMicroMvvm.Common;
using MauiMicroMvvm.Xaml;
namespace MauiMicroMvvm.Internals;
@@ -8,13 +9,15 @@ public class AppLifecycleBehavior : Behavior
{
private bool _didAppear;
private bool _isVisible;
- private Window _window;
- public Page Page { get; set; }
+ private Window? _window;
+ public Page? Page { get; set; }
- public BindableObject View { get; set; }
+ public BindableObject? View { get; set; }
protected override void OnAttachedTo(BindableObject bindable)
{
+ ArgumentNullException.ThrowIfNull(Page);
+ ArgumentNullException.ThrowIfNull(View);
base.OnAttachedTo(bindable);
Page.Appearing += OnAppearing;
Page.Disappearing += OnDisappearing;
@@ -34,13 +37,19 @@ protected override void OnAttachedTo(BindableObject bindable)
}
}
- protected override void OnDetachingFrom(BindableObject bindable)
+ protected override async void OnDetachingFrom(BindableObject bindable)
{
+ ArgumentNullException.ThrowIfNull(Page);
+ ArgumentNullException.ThrowIfNull(View);
+
base.OnDetachingFrom(bindable);
Page.Appearing -= OnAppearing;
Page.Disappearing -= OnDisappearing;
Page.PropertyChanged -= OnPagePropertyChanged;
View.PropertyChanged -= OnViewPropertyChanged;
+
+ MvvmHelpers.Destroy(View);
+ await MvvmHelpers.DestroyAsync(View);
if (_window is not null)
{
_window.Resumed -= OnResumed;
@@ -50,8 +59,11 @@ protected override void OnDetachingFrom(BindableObject bindable)
Page = null;
}
- private void OnViewPropertyChanged(object sender, PropertyChangedEventArgs e)
+ private void OnViewPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
+ ArgumentNullException.ThrowIfNull(Page);
+ ArgumentNullException.ThrowIfNull(View);
+
if (e.PropertyName != MauiMicro.SharedContextProperty.PropertyName)
return;
@@ -60,8 +72,11 @@ private void OnViewPropertyChanged(object sender, PropertyChangedEventArgs e)
MauiMicro.SetSharedContext(Page, value);
}
- private void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e)
+ private void OnPagePropertyChanged(object? sender, PropertyChangedEventArgs e)
{
+ ArgumentNullException.ThrowIfNull(Page);
+ ArgumentNullException.ThrowIfNull(View);
+
if (e.PropertyName != MauiMicro.SharedContextProperty.PropertyName)
return;
@@ -70,32 +85,39 @@ private void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e)
MauiMicro.SetSharedContext(View, value);
}
- private void OnResumed(object sender, EventArgs e)
+ private void OnResumed(object? sender, EventArgs e)
{
- if (_isVisible && View.BindingContext is IAppLifecycle lifecycle)
- lifecycle.OnResume();
+ MvvmHelpers.InvokeViewViewModelAction(View, x => x.OnResume());
}
- private void OnStopped(object sender, EventArgs e)
+ private void OnStopped(object? sender, EventArgs e)
{
- if (_isVisible && View.BindingContext is IAppLifecycle lifecycle)
- lifecycle.OnSleep();
+ MvvmHelpers.InvokeViewViewModelAction(View, x => x.OnSleep());
}
- private void OnAppearing(object sender, EventArgs e)
+ private void OnAppearing(object? sender, EventArgs e)
{
- if (!_didAppear && View.BindingContext is IViewModelActivation initialize)
- initialize.OnFirstLoad();
+ if (!_didAppear)
+ {
+ MvvmHelpers.InvokeViewViewModelAction(View, x => x.OnFirstLoad());
+ }
+
_didAppear = true;
- if (View.BindingContext is IViewLifecycle lifecycle)
- lifecycle.OnAppearing();
+
+ if (!_isVisible)
+ {
+ MvvmHelpers.InvokeViewViewModelAction(View, x => x.OnAppearing());
+ }
+
_isVisible = true;
}
- private void OnDisappearing(object sender, EventArgs e)
+ private void OnDisappearing(object? sender, EventArgs e)
{
- if (View.BindingContext is IViewLifecycle lifecycle)
- lifecycle.OnDisappearing();
+ if (_isVisible)
+ {
+ MvvmHelpers.InvokeViewViewModelAction(View, x => x.OnDisappearing());
+ }
_isVisible = false;
}
diff --git a/src/MauiMicroMvvm/Internals/ViewFactory.cs b/src/MauiMicroMvvm/Internals/ViewFactory.cs
index b9b3a06..3bc6d95 100644
--- a/src/MauiMicroMvvm/Internals/ViewFactory.cs
+++ b/src/MauiMicroMvvm/Internals/ViewFactory.cs
@@ -1,7 +1,9 @@
#nullable enable
+using MauiMicroMvvm.Behaviors;
+
namespace MauiMicroMvvm.Internals;
-public class ViewFactory(IServiceProvider services, IEnumerable mappings) : IViewFactory
+public class ViewFactory(IServiceProvider services, IEnumerable mappings, IBehaviorFactory behaviorFactory) : IViewFactory
{
public static readonly BindableProperty NavigationKeyProperty =
BindableProperty.CreateAttached("NavigationKey", typeof(string), typeof(ViewFactory), null);
@@ -38,6 +40,8 @@ public virtual TView Configure(TView view)
if(view.BindingContext is null && (!view.IsSet(Xaml.MauiMicro.AutowireProperty) || Xaml.MauiMicro.GetAutowire(view)))
SetBindingContext(view);
+ behaviorFactory.ApplyBehaviors(view);
+
if (view is Shell || view is Window || view.Behaviors.OfType().Any())
return view;
diff --git a/src/MauiMicroMvvm/Internals/WindowCreator.cs b/src/MauiMicroMvvm/Internals/WindowCreator.cs
new file mode 100644
index 0000000..86a904c
--- /dev/null
+++ b/src/MauiMicroMvvm/Internals/WindowCreator.cs
@@ -0,0 +1,15 @@
+namespace MauiMicroMvvm.Internals;
+
+internal class WindowCreator(TShell Shell) : IWindowCreator
+ where TShell : Shell
+{
+ private Window? _window;
+
+ public Window CreateWindow(Application app, IActivationState? activationState)
+ {
+ return _window ??= new Window
+ {
+ Page = Shell
+ };
+ }
+}
diff --git a/src/MauiMicroMvvm/MauiMicroBuilderExtensions.cs b/src/MauiMicroMvvm/MauiMicroBuilderExtensions.cs
index 0d586e2..70d6c2d 100644
--- a/src/MauiMicroMvvm/MauiMicroBuilderExtensions.cs
+++ b/src/MauiMicroMvvm/MauiMicroBuilderExtensions.cs
@@ -1,4 +1,5 @@
using MauiMicroMvvm;
+using MauiMicroMvvm.Behaviors;
using MauiMicroMvvm.Internals;
using INavigation = MauiMicroMvvm.INavigation;
@@ -6,24 +7,40 @@ namespace Microsoft.Maui.Hosting;
public static class MauiMicroBuilderExtensions
{
+ public static MauiAppBuilder UseMauiMicroMvvm(this MauiAppBuilder builder)
+ where TShell : Shell
+ {
+ builder.Services
+ .AddSingleton()
+ .AddSingleton>()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddScoped();
+ return builder;
+ }
+
+ [Obsolete("Use `UseMauiMicroMvvm()` instead.")]
public static MauiAppBuilder UseMauiMicroMvvm(this MauiAppBuilder builder, params string[] mergedDictionaries)
where TApp : Application
where TShell : Shell =>
- builder.UseMauiMicroMvvm(new ResourceDictionary(), mergedDictionaries);
+ builder.UseMauiMicroMvvm([], mergedDictionaries);
+ [Obsolete("Use `UseMauiMicroMvvm()` instead.")]
public static MauiAppBuilder UseMauiMicroMvvm(this MauiAppBuilder builder, ResourceDictionary resources, params string[] mergedDictionaries)
where TApp : Application
where TShell : Shell
{
builder.UseMauiApp();
- builder.Services.AddSingleton()
+ builder.Services
.AddSingleton()
.AddSingleton(sp =>
{
- var app = sp.GetRequiredService();
+ var app = sp.GetRequiredService();
- if (mergedDictionaries.Any())
+ if (mergedDictionaries.Length != 0)
{
var assembly = typeof(TShell).Assembly;
var qualifiedResources = mergedDictionaries.Select(x =>
@@ -42,36 +59,32 @@ public static MauiAppBuilder UseMauiMicroMvvm(this MauiAppBuilder
";
- app.Resources.LoadFromXaml(xaml);
+ app.Resources.LoadFromXaml(xaml);
}
- if (resources != null && resources.Keys.Any())
+ if (resources != null && resources.Keys.Count != 0)
{
app.Resources.Add(resources);
}
- var shell = sp.GetRequiredService();
- app.MainPage = shell;
return app;
});
- builder.Services
- .AddSingleton()
- .AddSingleton()
- .AddSingleton()
- .AddScoped();
- return builder;
+ return builder
+ .UseMauiMicroMvvm();
}
+ [Obsolete("Use `UseMauiMicroMvvm()` instead.")]
public static MauiAppBuilder UseMauiMicroMvvm(this MauiAppBuilder builder, params string[] mergedDictionaries)
where TShell : Shell =>
- builder.UseMauiMicroMvvm(new ResourceDictionary(), mergedDictionaries);
+ builder.UseMauiMicroMvvm([], mergedDictionaries);
+ [Obsolete("Use `UseMauiMicroMvvm()` instead.")]
public static MauiAppBuilder UseMauiMicroMvvm(this MauiAppBuilder builder, ResourceDictionary resources, params string[] mergedDictionaries)
where TShell : Shell =>
builder.UseMauiMicroMvvm(resources, mergedDictionaries);
- public static IServiceCollection MapView(this IServiceCollection services, string key = null)
+ public static IServiceCollection MapView(this IServiceCollection services, string? key = null)
where TView : VisualElement
where TViewModel : class
{
@@ -91,4 +104,26 @@ public static IServiceCollection MapView(this IServiceCollect
.AddSingleton(new ViewMapping(key, typeof(TView), typeof(TViewModel)))
.AddTransient();
}
+
+ public static IServiceCollection ApplyBehavior(this IServiceCollection services)
+ where TView : VisualElement
+ where TBehavior : Behavior
+ {
+ return services.AddTransient()
+ .AddSingleton>();
+ }
+
+ public static IServiceCollection ApplyBehavior(this IServiceCollection services, Action onAttached, Action? onDetached = null)
+ where TView : VisualElement
+ {
+ onDetached ??= delegate { };
+ return services.AddSingleton(new DelegateViewBehavior(onAttached, onDetached));
+ }
+
+ public static IServiceCollection ApplyBehavior(this IServiceCollection services, Action onAttached, Action? onDetached = null)
+ where TView : VisualElement
+ {
+ onDetached ??= delegate { };
+ return services.AddSingleton(new DelegateViewBehavior((_, view) => onAttached(view), (_, view) => onDetached(view)));
+ }
}
\ No newline at end of file
diff --git a/src/MauiMicroMvvm/MauiMicroMvvm.csproj b/src/MauiMicroMvvm/MauiMicroMvvm.csproj
index 3cda0f4..b318b04 100644
--- a/src/MauiMicroMvvm/MauiMicroMvvm.csproj
+++ b/src/MauiMicroMvvm/MauiMicroMvvm.csproj
@@ -2,12 +2,9 @@
$(DotNetVersion)
- true
- enable
True
MauiMicroMvvm is a micro Mvvm Framework built specifically for use with .NET MAUI Shell applications. It's built in a way that gives you the flexibility to do what you need with a proper decoupling between the View & ViewModel that many frameworks seems to mess up.
dotnet-maui;mvvm;mauimicro;
- AvantiPoint.$(AssemblyName)
MauiMicroMvvm
diff --git a/src/MauiMicroMvvm/MauiMicroViewModel.cs b/src/MauiMicroMvvm/MauiMicroViewModel.cs
index 189ae8a..3656746 100644
--- a/src/MauiMicroMvvm/MauiMicroViewModel.cs
+++ b/src/MauiMicroMvvm/MauiMicroViewModel.cs
@@ -6,10 +6,11 @@
namespace MauiMicroMvvm;
-public abstract class MauiMicroViewModel : INotifyPropertyChanging, INotifyPropertyChanged, IViewModelActivation, IViewLifecycle, IAppLifecycle, IQueryAttributable
+public abstract class MauiMicroViewModel : INotifyPropertyChanging, INotifyPropertyChanged, IViewModelActivation, IViewLifecycle, IAppLifecycle, IQueryAttributable, IDisposable
{
private readonly Dictionary _properties = [];
private readonly Lazy _lazyLogger;
+ private readonly object _locker = new ();
protected MauiMicroViewModel(ViewModelContext context)
{
@@ -19,14 +20,16 @@ protected MauiMicroViewModel(ViewModelContext context)
QueryParameters = new Dictionary();
}
+ protected bool IsDisposed { get; private set; }
+
protected ILogger Logger => _lazyLogger.Value;
protected INavigation Navigation { get; }
protected IPageDialogs PageDialogs { get; }
- public event PropertyChangedEventHandler PropertyChanged;
- public event PropertyChangingEventHandler PropertyChanging;
+ public event PropertyChangedEventHandler? PropertyChanged;
+ public event PropertyChangingEventHandler? PropertyChanging;
public bool IsBusy
{
@@ -50,26 +53,55 @@ public virtual void OnSleep() { }
protected virtual void OnParametersSet() { }
- protected T Get(T defaultValue = default, [CallerMemberName]string propertyName = null)
+ protected T Get(T? defaultValue = default, [CallerMemberName]string? propertyName = null)
{
- if (_properties.ContainsKey(propertyName))
- return (T)_properties[propertyName];
+ ArgumentException.ThrowIfNullOrEmpty(propertyName);
+ if (_properties.TryGetValue(propertyName, out var value) && value is T valueAsT)
+ return valueAsT;
+
+ if (defaultValue is null && typeof(T).IsValueType)
+ defaultValue = Activator.CreateInstance();
- return defaultValue;
+ if (Nullable.GetUnderlyingType(typeof(T)) != null)
+ ArgumentNullException.ThrowIfNull(defaultValue);
+
+ return defaultValue!;
}
- protected bool Set(T value, [CallerMemberName]string propertyName = null)
+ protected bool Set(T value, [CallerMemberName]string? propertyName = null)
{
- if(EqualityComparer.Default.Equals(Get(propertyName: propertyName), value))
+ ArgumentException.ThrowIfNullOrEmpty(propertyName);
+
+ var isSet = _properties.ContainsKey(propertyName);
+ if (isSet && EqualityComparer.Default.Equals(Get(propertyName: propertyName), value))
return false;
+ RaisePropertyChanging(propertyName);
+
+ if (value is null && isSet)
+ {
+ _properties.Remove(propertyName);
+ }
+ else if (value is not null)
+ {
+ _properties[propertyName] = value;
+ }
+
+ RaisePropertyChanged(propertyName);
+ return true;
+ }
+
+ protected virtual void RaisePropertyChanging(string propertyName)
+ {
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
- _properties[propertyName] = value;
+ }
+
+ protected virtual void RaisePropertyChanged(string propertyName)
+ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- return true;
}
- protected bool Set(T value, Action callback, [CallerMemberName]string propertyName = null)
+ protected bool Set(T value, Action callback, [CallerMemberName]string? propertyName = null)
{
if (Set(value, propertyName))
{
@@ -100,4 +132,24 @@ public void ApplyQueryAttributes(IDictionary query)
OnParametersSet();
}
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting
+ /// unmanaged resources.
+ ///
+ protected virtual void Dispose() { }
+
+ void IDisposable.Dispose()
+ {
+ lock(_locker)
+ {
+ if (!IsDisposed)
+ {
+ Dispose();
+ }
+ IsDisposed = true;
+ }
+
+ GC.SuppressFinalize(this);
+ }
}