Skip to content

Commit

Permalink
fix: Fix spurious DataContext propagation in ContentControl
Browse files Browse the repository at this point in the history
  • Loading branch information
davidjohnoliver committed Apr 15, 2021
1 parent 101e5bf commit 996d574
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages
{
public partial class BoundContentApplicator : Grid
{
public object PseudoContent
{
get { return (object)GetValue(PseudoContentProperty); }
set { SetValue(PseudoContentProperty, value); }
}

public static readonly DependencyProperty PseudoContentProperty =
DependencyProperty.Register("PseudoContent", typeof(object), typeof(BoundContentApplicator), new PropertyMetadata(null));

public void SpawnButton()
{
var button = new Button(); // Since we create the Button from code, its default style and its template will only be applied when it's loaded into the visual tree
button.Content = PseudoContent;
Children.Add(button);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Page
x:Class="Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages.When_Template_Applied_On_Loading_DataContext_Propagation_Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="0,50,0,0"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button x:Name="AddButtonButton"
Content="Add test button"
Click="AddBoundContentButton" />
<local:BoundContentApplicator x:Name="SpawnedButtonHost" x:FieldModifier="public">
<local:BoundContentApplicator.PseudoContent>
<ComboBox ItemsSource="{Binding MyItemsSource}"
SelectedItem="{Binding MyText, Mode=TwoWay}" />
</local:BoundContentApplicator.PseudoContent>
</local:BoundContentApplicator>
</StackPanel>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class When_Template_Applied_On_Loading_DataContext_Propagation_Page : Page
{
public When_Template_Applied_On_Loading_DataContext_Propagation_Page()
{
this.InitializeComponent();
DataContext = new ViewModel() { MyItemsSource = new[] { "Froot", "Veg" }, MyText = "Froot" };
}

private void AddBoundContentButton(object sender, object args)
{
SpawnedButtonHost.SpawnButton();
}

private class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public IEnumerable<string> MyItemsSource { get; set; }

public string MyText
{
get { return _myText; }
set
{
if (_myText != value)
{
_myText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MyText)));
}
}
}
string _myText;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Linq;
using static Private.Infrastructure.TestServices;
using Uno.UI.RuntimeTests.Helpers;
using Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages;
#if NETFX_CORE
using Uno.UI.Extensions;
#elif __IOS__
Expand Down Expand Up @@ -132,6 +133,28 @@ public async Task When_ContentTemplateSelector_And_Default_Style_And_Fluent()
}
}

[TestMethod]
public async Task When_Template_Applied_On_Loading_DataContext_Propagation()
{
var page = new When_Template_Applied_On_Loading_DataContext_Propagation_Page();
WindowHelper.WindowContent = page;
await WindowHelper.WaitForLoaded(page);
var comboBox = page.SpawnedButtonHost.PseudoContent as ComboBox;

var dataContextChangedCounter = 0;
var itemsSourceChangedCounter = 0;
comboBox.DataContextChanged += (_, __) => dataContextChangedCounter++;
comboBox.RegisterPropertyChangedCallback(ItemsControl.ItemsSourceProperty, (_, __) => itemsSourceChangedCounter++);

page.SpawnedButtonHost.SpawnButton();
Assert.IsNotNull(comboBox);

await WindowHelper.WaitForLoaded(comboBox);
Assert.AreEqual("Froot", comboBox.SelectedItem);
Assert.AreEqual(1, dataContextChangedCounter);
Assert.AreEqual(1, itemsSourceChangedCounter);
}

private class SignInViewModel
{
public string UserName { get; set; } = "Steve";
Expand Down
6 changes: 6 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ public virtual object Content
typeof(ContentControl),
new FrameworkPropertyMetadata(
defaultValue: null,
// Don't propagate DataContext to Content qua Content, only propagate it via the visual tree. Prevents spurious
// propagation in case that default style and template is only applied once the control enters the visual tree
// (ie if created in code by new SomeControl())
// NOTE: There's a case we currently don't support: if the Content is a DependencyObject but *not* a FrameworkElement, then
// the DataContext won't get propagated and any bindings won't get updated.
FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext,
propertyChangedCallback: (s, e) => ((ContentControl)s)?.OnContentChanged(e.OldValue, e.NewValue)
)
);
Expand Down

0 comments on commit 996d574

Please sign in to comment.