Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
RolandKoenig committed Feb 7, 2024
2 parents 7e9109d + 358d9e6 commit cc497d1
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 14 deletions.
4 changes: 2 additions & 2 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<!-- Version -->
<PropertyGroup>
<VersionPrefix>2.0.1</VersionPrefix>
<VersionPrefix>2.0.3</VersionPrefix>
</PropertyGroup>

<!-- Nuget -->
Expand Down Expand Up @@ -41,7 +41,7 @@
<!-- Commn configuration-->
<PropertyGroup>
<Nullable>enable</Nullable>
<LangVersion>11.0</LangVersion>
<LangVersion>12</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
<NoWarn>1591,1712</NoWarn>
Expand Down
66 changes: 64 additions & 2 deletions src/RolandK.AvaloniaExtensions.Tests/Mvvm/MvvmUserControlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace RolandK.AvaloniaExtensions.Tests.Mvvm;

[Collection(nameof(ApplicationTestCollection))]
public class MvvmUserControlTests
public partial class MvvmUserControlTests
{
[Fact]
public Task Attach_MvvmUserControl_to_ViewModel()
Expand Down Expand Up @@ -172,10 +172,69 @@ public Task Attach_ViewModel_to_multiple_views_throws_InvalidOperationException(
});
}

[Fact]
public Task Attach_MvvmUserControl_to_ViewModel_then_detach_and_check_events_fired()
{
return UnitTestApplication.RunInApplicationContextAsync(() =>
{
// Arrange
var testMvvmControl = new MvvmUserControl();
var testViewModel = new TestViewModel();

var viewModelAttachedEventCount = 0;
var viewModelDetachedEventCount = 0;
testMvvmControl.ViewModelAttached += (_, _) => viewModelAttachedEventCount++;
testMvvmControl.ViewModelDetached += (_, _) => viewModelDetachedEventCount++;

// Act
testMvvmControl.DataContext = testViewModel;
testMvvmControl.ViewFor = typeof(TestViewModel);
var testRoot = new TestRootWindow(testMvvmControl);
testRoot.Content = new Grid();

// Assert
Assert.Equal(1, viewModelAttachedEventCount);
Assert.Equal(1, viewModelDetachedEventCount);

GC.KeepAlive(testRoot);
});
}

[Fact]
public Task Attach_MvvmUserControl_to_ViewModel_then_check_ViewModelPropertyChanged_event()
{
return UnitTestApplication.RunInApplicationContextAsync(() =>
{
// Arrange
var testMvvmControl = new MvvmUserControl();
var testViewModel = new TestViewModel();

var propertyChangedEventCount = 0;
var lastPropertyChangedEventPropertyName = string.Empty;
testMvvmControl.ViewModelPropertyChanged += (_, args) =>
{
propertyChangedEventCount++;
lastPropertyChangedEventPropertyName = args.PropertyName;
};

// Act
testMvvmControl.DataContext = testViewModel;
testMvvmControl.ViewFor = typeof(TestViewModel);
var testRoot = new TestRootWindow(testMvvmControl);
testViewModel.DummyProperty = "Some other value..";

// Assert
Assert.Equal(1, propertyChangedEventCount);
Assert.Equal(nameof(TestViewModel.DummyProperty), lastPropertyChangedEventPropertyName);

GC.KeepAlive(testRoot);
});
}

//*************************************************************************
//*************************************************************************
//*************************************************************************
private class TestViewModel : ObservableObject, IAttachableViewModel
private partial class TestViewModel : ObservableObject, IAttachableViewModel
{
/// <inheritdoc />
public event EventHandler<CloseWindowRequestEventArgs>? CloseWindowRequest;
Expand All @@ -186,6 +245,9 @@ private class TestViewModel : ObservableObject, IAttachableViewModel
/// <inheritdoc />
public object? AssociatedView { get; set; }

[ObservableProperty]
private string _dummyProperty = string.Empty;

public TViewService? TryGetViewService<TViewService>()
where TViewService : class
{
Expand Down
67 changes: 65 additions & 2 deletions src/RolandK.AvaloniaExtensions.Tests/Mvvm/MvvmWindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace RolandK.AvaloniaExtensions.Tests.Mvvm;

[Collection(nameof(ApplicationTestCollection))]
public class MvvmWindowTests
public partial class MvvmWindowTests
{
[Fact]
public async Task Attach_MvvmWindow_to_ViewModel()
Expand Down Expand Up @@ -234,10 +234,70 @@ public Task Attach_ViewModel_to_multiple_views_throws_InvalidOperationException(
});
}

[Fact]
public Task Attach_MvvmWindow_to_ViewModel_then_detach_and_check_events_fired()
{
return UnitTestApplication.RunInApplicationContextAsync(() =>
{
// Arrange
var testMvvmWindow = new MvvmWindow();
var testViewModel = new TestViewModel();

var viewModelAttachedEventCount = 0;
var viewModelDetachedEventCount = 0;
testMvvmWindow.ViewModelAttached += (_, _) => viewModelAttachedEventCount++;
testMvvmWindow.ViewModelDetached += (_, _) => viewModelDetachedEventCount++;

// Act
testMvvmWindow.DataContext = testViewModel;
testMvvmWindow.ViewFor = typeof(TestViewModel);
testMvvmWindow.Show();
testViewModel.TriggerCloseWindowRequest();

// Assert
Assert.Equal(1, viewModelAttachedEventCount);
Assert.Equal(1, viewModelDetachedEventCount);

// Cleanup
testMvvmWindow.Close();
});
}

[Fact]
public Task Attach_MvvmWindow_to_ViewModel_then_check_ViewModelPropertyChanged_event()
{
return UnitTestApplication.RunInApplicationContextAsync(() =>
{
// Arrange
var testMvvmWindow = new MvvmWindow();
var testViewModel = new TestViewModel();

var propertyChangedEventCount = 0;
var lastPropertyChangedEventPropertyName = string.Empty;
testMvvmWindow.ViewModelPropertyChanged += (_, args) =>
{
propertyChangedEventCount++;
lastPropertyChangedEventPropertyName = args.PropertyName;
};

// Act
testMvvmWindow.DataContext = testViewModel;
testMvvmWindow.ViewFor = typeof(TestViewModel);
testMvvmWindow.Show();
testViewModel.DummyProperty = "Some other value..";

// Assert
Assert.Equal(1, propertyChangedEventCount);
Assert.Equal(nameof(TestViewModel.DummyProperty), lastPropertyChangedEventPropertyName);

testMvvmWindow.Close();
});
}

//*************************************************************************
//*************************************************************************
//*************************************************************************
private class TestViewModel : ObservableObject, IAttachableViewModel
private partial class TestViewModel : ObservableObject, IAttachableViewModel
{
/// <inheritdoc />
public event EventHandler<CloseWindowRequestEventArgs>? CloseWindowRequest;
Expand All @@ -248,6 +308,9 @@ private class TestViewModel : ObservableObject, IAttachableViewModel
/// <inheritdoc />
public object? AssociatedView { get; set; }

[ObservableProperty]
private string _dummyProperty = string.Empty;

public TViewService? TryGetViewService<TViewService>()
where TViewService : class
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@

namespace RolandK.AvaloniaExtensions.Mvvm;

public class CloseWindowRequestEventArgs
public class CloseWindowRequestEventArgs(object? dialogResult) : EventArgs
{
public object? DialogResult { get; }

public CloseWindowRequestEventArgs(object? dialogResult)
{
this.DialogResult = dialogResult;
}
public object? DialogResult { get; } = dialogResult;
}
52 changes: 51 additions & 1 deletion src/RolandK.AvaloniaExtensions/Mvvm/Controls/MvvmUserControl.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using RolandK.AvaloniaExtensions.ViewServices;
Expand All @@ -14,6 +15,12 @@ public class MvvmUserControl : UserControl, IViewServiceHost
private ViewServiceContainer _viewServiceContainer;
private Type? _viewFor;

public event EventHandler<ViewModelAttachedEventArgs>? ViewModelAttached;

public event EventHandler<ViewModelPropertyChangedEventArgs>? ViewModelPropertyChanged;

public event EventHandler<ViewModelDetachedEventArgs>? ViewModelDetached;

public Type? ViewFor
{
get => _viewFor;
Expand Down Expand Up @@ -76,6 +83,21 @@ protected override void OnDataContextChanged(EventArgs e)

this.TryAttachToDataContext();
}

protected virtual void OnViewModelPropertyChanged(ViewModelPropertyChangedEventArgs args)
{
this.ViewModelPropertyChanged?.Invoke(this, args);
}

protected virtual void OnViewModelAttached(ViewModelAttachedEventArgs args)
{
this.ViewModelAttached?.Invoke(this, args);
}

protected virtual void OnViewModelDetached(ViewModelDetachedEventArgs args)
{
this.ViewModelDetached?.Invoke(this, args);
}

private void OnViewForChanged()
{
Expand All @@ -101,8 +123,14 @@ private void TryAttachToDataContext()
$"is already attached to a view of type {dataContextAttachable.AssociatedView.GetType().FullName}");
}

var dataContextPropertyChanged = dataContextAttachable as INotifyPropertyChanged;

dataContextAttachable.ViewServiceRequest += this.OnDataContextAttachable_ViewServiceRequest;
dataContextAttachable.CloseWindowRequest += this.OnDataContextAttachable_CloseWindowRequest;
if (dataContextPropertyChanged != null)
{
dataContextPropertyChanged.PropertyChanged += this.OnDataContextAttachable_PropertyChanged;
}
try
{
dataContextAttachable.AssociatedView = this;
Expand All @@ -111,19 +139,32 @@ private void TryAttachToDataContext()
{
dataContextAttachable.ViewServiceRequest -= this.OnDataContextAttachable_ViewServiceRequest;
dataContextAttachable.CloseWindowRequest -= this.OnDataContextAttachable_CloseWindowRequest;
if (dataContextPropertyChanged != null)
{
dataContextPropertyChanged.PropertyChanged -= this.OnDataContextAttachable_PropertyChanged;
}
throw;
}

_currentlyAttachedViewModel = dataContextAttachable;
this.OnViewModelAttached(new ViewModelAttachedEventArgs(dataContextAttachable));
}
}

private void DetachFromDataContext()
{
if (_currentlyAttachedViewModel != null)
{
_currentlyAttachedViewModel.AssociatedView = null;
_currentlyAttachedViewModel.ViewServiceRequest -= this.OnDataContextAttachable_ViewServiceRequest;
_currentlyAttachedViewModel.CloseWindowRequest -= this.OnDataContextAttachable_CloseWindowRequest;
_currentlyAttachedViewModel.AssociatedView = null;

if (_currentlyAttachedViewModel is INotifyPropertyChanged dataContextPropertyChanged)
{
dataContextPropertyChanged.PropertyChanged -= this.OnDataContextAttachable_PropertyChanged;
}

this.OnViewModelDetached(new ViewModelDetachedEventArgs(_currentlyAttachedViewModel));
}
_currentlyAttachedViewModel = null;
}
Expand All @@ -134,6 +175,15 @@ private void DetachFromDataContext()
return DefaultViewServices.TryGetDefaultViewService(this, viewServiceType);
}

private void OnDataContextAttachable_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_currentlyAttachedViewModel == null) { return; }

this.OnViewModelPropertyChanged(new ViewModelPropertyChangedEventArgs(
_currentlyAttachedViewModel,
e.PropertyName));
}

private void OnDataContextAttachable_ViewServiceRequest(object? sender, ViewServiceRequestEventArgs e)
{
var viewService = this.TryFindViewService(e.ViewServiceType);
Expand Down
Loading

0 comments on commit cc497d1

Please sign in to comment.