Skip to content

Commit

Permalink
Added ViewFor to MvvmWindow and MvvmUserControl
Browse files Browse the repository at this point in the history
  • Loading branch information
RolandKoenig committed Dec 31, 2023
1 parent d166d81 commit 5dd21c4
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 15 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ 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.
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
<ext:MvvmWindow xmlns="https://github.com/avaloniaui"
Expand All @@ -126,7 +129,8 @@ the corresponding code behind.
xmlns:ext="https://github.com/RolandK.AvaloniaExtensions"
xmlns:local="clr-namespace:RolandK.AvaloniaExtensions.TestApp"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RolandK.AvaloniaExtensions.TestApp.MainWindow">
x:Class="RolandK.AvaloniaExtensions.TestApp.MainWindow"
ViewFor="{x:Type [YourViewModelTypeHere]}">
</ext:MvvmWindow>
```

Expand Down
1 change: 1 addition & 0 deletions src/RolandK.AvaloniaExtensions.TestApp/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
x:Class="RolandK.AvaloniaExtensions.TestApp.MainWindow"
Title="{Binding Path=Title}"
ExtendClientAreaToDecorationsHint="True"
ViewFor="{x:Type local:MainWindowViewModel}"
DataContext="{ext:CreateUsingDependencyInjection {x:Type local:MainWindowViewModel}}"
d:DataContext="{x:Static local:MainWindowViewModel.DesignViewModel}">
<ext:MainWindowFrame>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public Task Attach_MvvmUserControl_to_ViewModel()

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

// Assert
Expand All @@ -43,6 +44,7 @@ public Task Attach_MvvmUserControl_to_ViewModel_then_detach()

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

testRoot.Content = new Grid();
Expand All @@ -65,6 +67,7 @@ public Task Attach_MvvmUserControl_to_ViewModel_then_detach_with_Grid_in_control

// Act
testMvvmControl.DataContext = testViewModel;
testMvvmControl.ViewFor = typeof(TestViewModel);

var mvvmControlContainer = new Grid();
mvvmControlContainer.Children.Add(testMvvmControl);
Expand Down Expand Up @@ -95,6 +98,7 @@ public Task Attach_MvvmUserControl_to_ViewModel_then_close_parent_Window_using_V

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

parentWindow.Content = null;
Expand All @@ -116,6 +120,7 @@ public Task Attach_MvvmUserControl_to_ViewModel_get_ViewService_MessageBox()

// Act
testMvvmControl.DataContext = testViewModel;
testMvvmControl.ViewFor = typeof(TestViewModel);
var mainWindowFrame = new MainWindowFrame(testMvvmControl);
var testRoot = new TestRootWindow(mainWindowFrame);
var messageBoxService = testViewModel.TryGetViewService<IMessageBoxViewService>();
Expand Down
7 changes: 7 additions & 0 deletions src/RolandK.AvaloniaExtensions.Tests/Mvvm/MvvmWindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ await UnitTestApplication.RunInApplicationContextAsync(() =>

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

// Assert
Expand All @@ -45,6 +46,7 @@ await UnitTestApplication.RunInApplicationContextAsync(() =>

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

testViewModel.TriggerCloseWindowRequest();
Expand All @@ -71,6 +73,7 @@ await UnitTestApplication.RunInApplicationContextAsync(async Task () =>

// Act
mvvmWindow.DataContext = testViewModel;
mvvmWindow.ViewFor = typeof(TestViewModel);
var showDialogTask = mvvmWindow.ShowDialog<object>(topLevelWindow);

var dialogResult = new object();
Expand Down Expand Up @@ -100,6 +103,7 @@ await UnitTestApplication.RunInApplicationContextAsync(() =>

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

var messageBoxService = testViewModel.TryGetViewService<IOpenFileViewService>();
Expand All @@ -124,6 +128,7 @@ await UnitTestApplication.RunInApplicationContextAsync(() =>

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

var messageBoxService = testViewModel.TryGetViewService<ISaveFileViewService>();
Expand Down Expand Up @@ -151,6 +156,7 @@ await UnitTestApplication.RunInApplicationContextAsync(() =>

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

var messageBoxService = testViewModel.TryGetViewService<IMessageBoxViewService>();
Expand Down Expand Up @@ -178,6 +184,7 @@ await UnitTestApplication.RunInApplicationContextAsync(() =>

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

var messageBoxService = testViewModel.TryGetViewService<IMessageBoxViewService>();
Expand Down
39 changes: 30 additions & 9 deletions src/RolandK.AvaloniaExtensions/Mvvm/Markup/MvvmUserControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ public class MvvmUserControl : UserControl, IViewServiceHost
private IAttachableViewModel? _currentlyAttachedViewModel;
private bool _isAttachedToLogicalTree;
private ViewServiceContainer _viewServiceContainer;
private Type? _viewFor;

public Type? ViewFor
{
get => _viewFor;
set
{
if (_viewFor == value) { return; }

_viewFor = value;
this.OnViewForChanged();
}
}

/// <inheritdoc />
public ICollection<IViewService> ViewServices => _viewServiceContainer.ViewServices;
Expand All @@ -34,7 +47,7 @@ public MvvmUserControl(Control initialChild)
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_isAttachedToLogicalTree = true;
this.AttachToDataContext();
this.TryAttachToDataContext();

base.OnAttachedToLogicalTree(e);
}
Expand All @@ -54,23 +67,31 @@ protected override void OnDataContextChanged(EventArgs e)
if (!_isAttachedToLogicalTree)
{
base.OnDataContextChanged(e);
return;
}
else
{
this.DetachFromDataContext();

this.DetachFromDataContext();

base.OnDataContextChanged(e);
base.OnDataContextChanged(e);

this.AttachToDataContext();
}
this.TryAttachToDataContext();
}

private void OnViewForChanged()
{
if (!_isAttachedToLogicalTree) { return; }

this.DetachFromDataContext();
this.TryAttachToDataContext();
}

private void AttachToDataContext()
private void TryAttachToDataContext()
{
if (_currentlyAttachedViewModel == this.DataContext) { return; }

this.DetachFromDataContext();
if (this.DataContext is IAttachableViewModel dataContextAttachable)
if ((this.DataContext is IAttachableViewModel dataContextAttachable) &&
(this.DataContext.GetType() == this.ViewFor))
{
dataContextAttachable.ViewServiceRequest += this.OnDataContextAttachable_ViewServiceRequest;
dataContextAttachable.CloseWindowRequest += this.OnDataContextAttachable_CloseWindowRequest;
Expand Down
30 changes: 26 additions & 4 deletions src/RolandK.AvaloniaExtensions/Mvvm/Markup/MvvmWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,20 @@ public class MvvmWindow : Window, IViewServiceHost
private IAttachableViewModel? _currentlyAttachedViewModel;
private bool _isOpened;
private ViewServiceContainer _viewServiceContainer;
private Type? _viewFor;

public Type? ViewFor
{
get => _viewFor;
set
{
if (_viewFor == value) { return; }

_viewFor = value;
this.OnViewForChanged();
}
}

/// <inheritdoc />
public ICollection<IViewService> ViewServices => _viewServiceContainer.ViewServices;

Expand All @@ -34,7 +47,7 @@ protected override void OnOpened(EventArgs e)
base.OnOpened(e);

_isOpened = true;
this.AttachToDataContext();
this.TryAttachToDataContext();
}

/// <inheritdoc />
Expand All @@ -59,16 +72,25 @@ protected override void OnDataContextChanged(EventArgs e)

base.OnDataContextChanged(e);

this.AttachToDataContext();
this.TryAttachToDataContext();
}
}

private void AttachToDataContext()
private void OnViewForChanged()
{
if (!_isOpened) { return; }

this.DetachFromDataContext();
this.TryAttachToDataContext();
}

private void TryAttachToDataContext()
{
if (_currentlyAttachedViewModel == this.DataContext) { return; }

this.DetachFromDataContext();
if (this.DataContext is IAttachableViewModel dataContextAttachable)
if ((this.DataContext is IAttachableViewModel dataContextAttachable) &&
(this.DataContext.GetType() == this.ViewFor))
{
dataContextAttachable.ViewServiceRequest += this.OnDataContextAttachable_ViewServiceRequest;
dataContextAttachable.CloseWindowRequest += this.OnDataContextAttachable_CloseWindowRequest;
Expand Down

0 comments on commit 5dd21c4

Please sign in to comment.