Skip to content

Commit 996d574

Browse files
fix: Fix spurious DataContext propagation in ContentControl
1 parent 101e5bf commit 996d574

File tree

5 files changed

+135
-0
lines changed

5 files changed

+135
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using Windows.UI.Xaml;
5+
using Windows.UI.Xaml.Controls;
6+
7+
namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages
8+
{
9+
public partial class BoundContentApplicator : Grid
10+
{
11+
public object PseudoContent
12+
{
13+
get { return (object)GetValue(PseudoContentProperty); }
14+
set { SetValue(PseudoContentProperty, value); }
15+
}
16+
17+
public static readonly DependencyProperty PseudoContentProperty =
18+
DependencyProperty.Register("PseudoContent", typeof(object), typeof(BoundContentApplicator), new PropertyMetadata(null));
19+
20+
public void SpawnButton()
21+
{
22+
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
23+
button.Content = PseudoContent;
24+
Children.Add(button);
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Page
2+
x:Class="Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages.When_Template_Applied_On_Loading_DataContext_Propagation_Page"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:local="using:Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages"
6+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
7+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
8+
mc:Ignorable="d"
9+
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
10+
<StackPanel Margin="0,50,0,0"
11+
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
12+
<Button x:Name="AddButtonButton"
13+
Content="Add test button"
14+
Click="AddBoundContentButton" />
15+
<local:BoundContentApplicator x:Name="SpawnedButtonHost" x:FieldModifier="public">
16+
<local:BoundContentApplicator.PseudoContent>
17+
<ComboBox ItemsSource="{Binding MyItemsSource}"
18+
SelectedItem="{Binding MyText, Mode=TwoWay}" />
19+
</local:BoundContentApplicator.PseudoContent>
20+
</local:BoundContentApplicator>
21+
</StackPanel>
22+
</Page>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Runtime.InteropServices.WindowsRuntime;
7+
using Windows.Foundation;
8+
using Windows.Foundation.Collections;
9+
using Windows.UI.Xaml;
10+
using Windows.UI.Xaml.Controls;
11+
using Windows.UI.Xaml.Controls.Primitives;
12+
using Windows.UI.Xaml.Input;
13+
using Windows.UI.Xaml.Media;
14+
using Windows.UI.Xaml.Navigation;
15+
16+
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
17+
18+
namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages
19+
{
20+
/// <summary>
21+
/// An empty page that can be used on its own or navigated to within a Frame.
22+
/// </summary>
23+
public sealed partial class When_Template_Applied_On_Loading_DataContext_Propagation_Page : Page
24+
{
25+
public When_Template_Applied_On_Loading_DataContext_Propagation_Page()
26+
{
27+
this.InitializeComponent();
28+
DataContext = new ViewModel() { MyItemsSource = new[] { "Froot", "Veg" }, MyText = "Froot" };
29+
}
30+
31+
private void AddBoundContentButton(object sender, object args)
32+
{
33+
SpawnedButtonHost.SpawnButton();
34+
}
35+
36+
private class ViewModel : INotifyPropertyChanged
37+
{
38+
public event PropertyChangedEventHandler PropertyChanged;
39+
40+
public IEnumerable<string> MyItemsSource { get; set; }
41+
42+
public string MyText
43+
{
44+
get { return _myText; }
45+
set
46+
{
47+
if (_myText != value)
48+
{
49+
_myText = value;
50+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MyText)));
51+
}
52+
}
53+
}
54+
string _myText;
55+
}
56+
}
57+
}

src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ContentControl.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Linq;
1313
using static Private.Infrastructure.TestServices;
1414
using Uno.UI.RuntimeTests.Helpers;
15+
using Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentControlPages;
1516
#if NETFX_CORE
1617
using Uno.UI.Extensions;
1718
#elif __IOS__
@@ -132,6 +133,28 @@ public async Task When_ContentTemplateSelector_And_Default_Style_And_Fluent()
132133
}
133134
}
134135

136+
[TestMethod]
137+
public async Task When_Template_Applied_On_Loading_DataContext_Propagation()
138+
{
139+
var page = new When_Template_Applied_On_Loading_DataContext_Propagation_Page();
140+
WindowHelper.WindowContent = page;
141+
await WindowHelper.WaitForLoaded(page);
142+
var comboBox = page.SpawnedButtonHost.PseudoContent as ComboBox;
143+
144+
var dataContextChangedCounter = 0;
145+
var itemsSourceChangedCounter = 0;
146+
comboBox.DataContextChanged += (_, __) => dataContextChangedCounter++;
147+
comboBox.RegisterPropertyChangedCallback(ItemsControl.ItemsSourceProperty, (_, __) => itemsSourceChangedCounter++);
148+
149+
page.SpawnedButtonHost.SpawnButton();
150+
Assert.IsNotNull(comboBox);
151+
152+
await WindowHelper.WaitForLoaded(comboBox);
153+
Assert.AreEqual("Froot", comboBox.SelectedItem);
154+
Assert.AreEqual(1, dataContextChangedCounter);
155+
Assert.AreEqual(1, itemsSourceChangedCounter);
156+
}
157+
135158
private class SignInViewModel
136159
{
137160
public string UserName { get; set; } = "Steve";

src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ public virtual object Content
119119
typeof(ContentControl),
120120
new FrameworkPropertyMetadata(
121121
defaultValue: null,
122+
// Don't propagate DataContext to Content qua Content, only propagate it via the visual tree. Prevents spurious
123+
// propagation in case that default style and template is only applied once the control enters the visual tree
124+
// (ie if created in code by new SomeControl())
125+
// NOTE: There's a case we currently don't support: if the Content is a DependencyObject but *not* a FrameworkElement, then
126+
// the DataContext won't get propagated and any bindings won't get updated.
127+
FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext,
122128
propertyChangedCallback: (s, e) => ((ContentControl)s)?.OnContentChanged(e.OldValue, e.NewValue)
123129
)
124130
);

0 commit comments

Comments
 (0)