Skip to content

Commit

Permalink
Merge pull request #11 from RolandKoenig/10-provide-viewmodel-propert…
Browse files Browse the repository at this point in the history
…y-which-does-not-bubble-like-datacontext

10 Add ViewFor property on MvvmWindow and MvvmUserControl to handle bubbling of DataContext
  • Loading branch information
RolandKoenig authored Dec 31, 2023
2 parents d166d81 + 3f13d3a commit aec59ff
Show file tree
Hide file tree
Showing 23 changed files with 108 additions and 49 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
2 changes: 1 addition & 1 deletion src/RolandK.AvaloniaExtensions.TestApp/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using RolandK.AvaloniaExtensions.Mvvm.Markup;
using RolandK.AvaloniaExtensions.Mvvm.Controls;

namespace RolandK.AvaloniaExtensions.TestApp;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using RolandK.AvaloniaExtensions.Mvvm;
using RolandK.AvaloniaExtensions.Mvvm.Markup;
using RolandK.AvaloniaExtensions.Mvvm.Controls;
using RolandK.AvaloniaExtensions.Tests.Util;
using RolandK.AvaloniaExtensions.Views;
using RolandK.AvaloniaExtensions.Controls;
using RolandK.AvaloniaExtensions.ViewServices;
using RolandK.AvaloniaExtensions.ViewServices.Base;

Expand All @@ -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
11 changes: 9 additions & 2 deletions src/RolandK.AvaloniaExtensions.Tests/Mvvm/MvvmWindowTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using RolandK.AvaloniaExtensions.Mvvm;
using RolandK.AvaloniaExtensions.Mvvm.Markup;
using RolandK.AvaloniaExtensions.Mvvm.Controls;
using RolandK.AvaloniaExtensions.Tests.Util;
using RolandK.AvaloniaExtensions.Views;
using RolandK.AvaloniaExtensions.Controls;
using RolandK.AvaloniaExtensions.ViewServices;
using RolandK.AvaloniaExtensions.ViewServices.Base;

Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Avalonia.Controls;
using RolandK.AvaloniaExtensions.Mvvm.Markup;
using RolandK.AvaloniaExtensions.Mvvm.Controls;
using RolandK.AvaloniaExtensions.Tests.Util;
using RolandK.AvaloniaExtensions.Views;
using RolandK.AvaloniaExtensions.Controls;
using RolandK.AvaloniaExtensions.ViewServices;

namespace RolandK.AvaloniaExtensions.Tests.ViewServices;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using RolandK.AvaloniaExtensions.Mvvm.Markup;
using RolandK.AvaloniaExtensions.Mvvm.Controls;
using RolandK.AvaloniaExtensions.Tests.Util;
using RolandK.AvaloniaExtensions.ViewServices;
using RolandK.AvaloniaExtensions.ViewServices.Base;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Avalonia.Controls;
using Avalonia.LogicalTree;
using RolandK.AvaloniaExtensions.Tests.Util;
using RolandK.AvaloniaExtensions.Views;
using RolandK.AvaloniaExtensions.Controls;

namespace RolandK.AvaloniaExtensions.Tests.Views;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Avalonia.Controls;
using Avalonia.LogicalTree;
using RolandK.AvaloniaExtensions.Tests.Util;
using RolandK.AvaloniaExtensions.Views;
using RolandK.AvaloniaExtensions.Controls;

namespace RolandK.AvaloniaExtensions.Tests.Views;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Avalonia.Controls;
using Avalonia.LogicalTree;
using RolandK.AvaloniaExtensions.Tests.Util;
using RolandK.AvaloniaExtensions.Views;
using RolandK.AvaloniaExtensions.Controls;

namespace RolandK.AvaloniaExtensions.Tests.Views;

Expand Down
4 changes: 2 additions & 2 deletions src/RolandK.AvaloniaExtensions/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

[assembly: XmlnsDefinition(
"https://github.com/RolandK.AvaloniaExtensions",
"RolandK.AvaloniaExtensions.Views")]
"RolandK.AvaloniaExtensions.Controls")]
[assembly: XmlnsDefinition(
"https://github.com/RolandK.AvaloniaExtensions",
"RolandK.AvaloniaExtensions.Mvvm.Markup")]
"RolandK.AvaloniaExtensions.Mvvm.Controls")]

[assembly: InternalsVisibleTo("RolandK.AvaloniaExtensions.Tests")]
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RolandK.AvaloniaExtensions.Views.DialogBoxControl">
x:Class="RolandK.AvaloniaExtensions.Controls.DialogBoxControl">
<Border BorderThickness="1"
BorderBrush="{DynamicResource DialogBoxControlBorderBrush}">
<DockPanel LastChildFill="True">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace RolandK.AvaloniaExtensions.Views;
namespace RolandK.AvaloniaExtensions.Controls;

public partial class DialogBoxControl : UserControl
{
Expand All @@ -14,7 +14,7 @@ public string Header
set => _txtHeader.Text = value;
}

public Controls ContentArea
public Avalonia.Controls.Controls ContentArea
{
get => _gridContent.Children;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Avalonia.Layout;
using Avalonia.Media;

namespace RolandK.AvaloniaExtensions.Views;
namespace RolandK.AvaloniaExtensions.Controls;

public class DialogHostControl : Grid
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RolandK.AvaloniaExtensions.Views"
xmlns:local="clr-namespace:RolandK.AvaloniaExtensions.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RolandK.AvaloniaExtensions.Views.MainWindowFrame">
x:Class="RolandK.AvaloniaExtensions.Controls.MainWindowFrame">
<Grid x:Name="CtrlFullWindowGrid">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Threading;
using RolandK.AvaloniaExtensions.Mvvm.Markup;

namespace RolandK.AvaloniaExtensions.Views;
namespace RolandK.AvaloniaExtensions.Controls;

public partial class MainWindowFrame : MvvmUserControl
public partial class MainWindowFrame : UserControl
{
public static readonly StyledProperty<MainWindowFrameStatus> StatusProperty =
AvaloniaProperty.Register<MainWindowFrame, MainWindowFrameStatus>(
Expand All @@ -26,15 +25,15 @@ public partial class MainWindowFrame : MvvmUserControl
private Panel _ctrlFooterArea;
private Panel _ctrlStatusBar;

public Controls FullBackgroundArea => _ctrlFullBackgroundPanel.Children;
public Avalonia.Controls.Controls FullBackgroundArea => _ctrlFullBackgroundPanel.Children;

public Controls CustomTitleArea => _ctrlCustomTitleArea.Children;
public Avalonia.Controls.Controls CustomTitleArea => _ctrlCustomTitleArea.Children;

public Controls HeaderMenuArea => _ctrlHeaderMenuArea.Children;
public Avalonia.Controls.Controls HeaderMenuArea => _ctrlHeaderMenuArea.Children;

public Controls MainContentArea => _ctrlMainContentArea.Children;
public Avalonia.Controls.Controls MainContentArea => _ctrlMainContentArea.Children;

public Controls FooterArea => _ctrlFooterArea.Children;
public Avalonia.Controls.Controls FooterArea => _ctrlFooterArea.Children;

public DialogHostControl Overlay { get; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace RolandK.AvaloniaExtensions.Views;
namespace RolandK.AvaloniaExtensions.Controls;

public enum MainWindowFrameStatus
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@
using RolandK.AvaloniaExtensions.ViewServices;
using RolandK.AvaloniaExtensions.ViewServices.Base;

namespace RolandK.AvaloniaExtensions.Mvvm.Markup;
namespace RolandK.AvaloniaExtensions.Mvvm.Controls;

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
Loading

0 comments on commit aec59ff

Please sign in to comment.