提交 6c2d89aa 编写于 作者: J Jerome Laban

fix(ContentPresenter): Avoid DataContext temporary reset

This change fixes a visible effect of TwoWay binding on ComboBox.SelectedValue being reset to null when being loaded.
上级 51fc6bfa
<UserControl
x:Class="Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentPresenterPages.ContentPresenter_Content_DataContext"
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.ContentPresenterPages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<StackPanel DataContext="{x:Null}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Empty with DataContext" />
<!-- The DataContext is completely ignored if the ContentPresenter doesn't have a ContentTemplate. -->
<ContentPresenter DataContext="DataContext" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Should be empty:" />
<!-- Change "DataContext" to "DataContext2": "DataContext2" -->
<!-- The DataContext is taken into account if the ContentPresenter has a ContentTemplate. -->
<ContentPresenter x:Name="emptyTestRoot"
x:FieldModifier="public"
DataContext="42">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock x:Name="emptyTest"
Text="{Binding}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Should be 43:" />
<!-- Initial: "42" -->
<!-- Change "42" to "44": "44" -->
<!-- Change "43" to "45": "45" -->
<!-- The latest of either DataContext or Content being set takes priority. -->
<ContentPresenter x:Name="priorityTestRoot"
x:FieldModifier="public"
DataContext="42"
Content="43">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock x:Name="priorityTest"
Text="{Binding}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Should be 42:" />
<!-- Initial: "42" -->
<!-- Setting Content on ContentPresenter also sets the same value to DataContext locally. -->
<ContentPresenter x:Name="MyContentPresenter"
Content="42">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<!-- Empty -->
<Border />
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
<TextBlock x:Name="sameValueTest"
Text="{Binding DataContext, ElementName=MyContentPresenter}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Should be DataContext:" />
<!-- Initial: "DataContext" -->
<!-- Change "DataContext" to "DataContext3": "DataContext3" -->
<!-- Change "DataContext2" to "DataContext4": "DataContext4" -->
<!-- Change "DataContext3" to "DataContext5": "DataContext4" -->
<!-- The inherited DataContext takes priority over the local DataContext initially. -->
<!-- The inherited DataContext is forever ignored if the local DataContext is changed. -->
<Grid x:Name="inheritanceTestRoot"
x:FieldModifier="public"
DataContext="DataContext">
<ContentPresenter x:Name="inheritanceTestInner"
x:FieldModifier="public"
DataContext="DataContext2">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock x:Name="inheritanceTest"
Text="{Binding}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Grid>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Should be DataContext:" />
<!-- Initial: "DataContext" -->
<!-- Change "DataContext" to "DataContext3": "DataContext3" -->
<!-- Change "DataContext2" to "DataContext4": "DataContext4" -->
<!-- Change "DataContext3" to "DataContext5": "DataContext4" -->
<!-- The inherited DataContext takes priority over the local DataContext initially. -->
<!-- The inherited DataContext is forever ignored if the local DataContext is changed. -->
<Grid DataContext="DataContext">
<ContentPresenter DataContext="DataContext2">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock x:Name="sameValueChangingTest"
Text="{Binding}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Grid>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Should be 42:" />
<!-- Initial: "DataContext" -->
<!-- The inherited DataContext takes priority over a null Content. -->
<Grid DataContext="42">
<ContentPresenter Content="{x:Null}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBlock x:Name="nullContentChanged"
Text="{Binding}" />
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Grid>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
using Windows.UI.Xaml.Controls;
namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentPresenterPages
{
public sealed partial class ContentPresenter_Content_DataContext : UserControl
{
public ContentPresenter_Content_DataContext()
{
this.InitializeComponent();
}
}
}
......@@ -12,6 +12,7 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using static Private.Infrastructure.TestServices;
using System.Collections.ObjectModel;
using Windows.UI.Xaml.Data;
#if NETFX_CORE
using Uno.UI.Extensions;
......@@ -611,6 +612,68 @@ namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls
}
#endif
[TestMethod]
public void When_SelectedItem_TwoWay_Binding()
{
var itemsControl = new ItemsControl()
{
ItemsPanel = new ItemsPanelTemplate(() => new StackPanel()),
ItemTemplate = new DataTemplate(() =>
{
var comboBox = new ComboBox();
comboBox.Name = "combo";
comboBox.SetBinding(ComboBox.ItemsSourceProperty, new Binding { Path = new("Numbers") });
comboBox.SetBinding(
ComboBox.SelectedItemProperty,
new Binding { Path = new("SelectedNumber"), Mode = BindingMode.TwoWay });
return comboBox;
})
};
var test = new[] {
new TwoWayBindingItem()
};
WindowHelper.WindowContent = itemsControl;
itemsControl.ItemsSource = test;
var comboBox = itemsControl.FindName("combo") as ComboBox;
Assert.AreEqual(3, test[0].SelectedNumber);
Assert.AreEqual(3, comboBox.SelectedItem);
}
public class TwoWayBindingItem : System.ComponentModel.INotifyPropertyChanged
{
private int _selectedNumber;
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public TwoWayBindingItem()
{
Numbers = new List<int> { 1, 2, 3, 4 };
SelectedNumber = 3;
}
public List<int> Numbers { get; }
public int SelectedNumber
{
get
{
return _selectedNumber;
}
set
{
_selectedNumber = value;
PropertyChanged?.Invoke(this, new(nameof(Item)));
}
}
}
public class ItemModel
{
public string Text { get; set; }
......
......@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Private.Infrastructure;
using Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ContentPresenterPages;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
......@@ -35,6 +36,113 @@ public class Given_ContentPresenter
Assert.AreEqual(HorizontalAlignment.Stretch, border.HorizontalAlignment);
}
[TestMethod]
public void When_Binding_And_DataContext_Same_As_Content()
{
var SUT = new ContentPresenter();
var dataContextChangedCount = 0;
SUT.DataContextChanged += (s, e) => dataContextChangedCount++;
SUT.DataContext = new object();
SUT.Content = SUT.DataContext;
TestServices.WindowHelper.WindowContent = SUT;
// This test ensures that the ContentPresenter does not reset
// the DataContext to null and then back to the content and have
// two-way bindings propagating the null value back to the source.
Assert.AreEqual(1, dataContextChangedCount);
}
[TestMethod]
public void When_Content_Presenter_Empty()
{
var sut = new ContentPresenter_Content_DataContext();
TestServices.WindowHelper.WindowContent = sut;
Assert.AreEqual("", GetTextBlockText(sut, "emptyTest"));
sut.emptyTestRoot.DataContext = "43";
Assert.AreEqual("43", GetTextBlockText(sut, "emptyTest"));
}
[TestMethod]
public void When_Content_Presenter_Priority()
{
var sut = new ContentPresenter_Content_DataContext();
TestServices.WindowHelper.WindowContent = sut;
Assert.AreEqual("43", GetTextBlockText(sut, "priorityTest"));
sut.priorityTestRoot.DataContext = "44";
Assert.AreEqual("44", GetTextBlockText(sut, "priorityTest"));
sut.priorityTestRoot.Content = "45";
Assert.AreEqual("45", GetTextBlockText(sut, "priorityTest"));
sut.priorityTestRoot.DataContext = "46";
Assert.AreEqual("46", GetTextBlockText(sut, "priorityTest"));
}
[TestMethod]
public void When_Content_Presenter_SameValue()
{
var sut = new ContentPresenter_Content_DataContext();
TestServices.WindowHelper.WindowContent = sut;
Assert.AreEqual("42", GetTextBlockText(sut, "sameValueTest"));
}
[TestMethod]
public void When_Content_Presenter_Inheritance()
{
var sut = new ContentPresenter_Content_DataContext();
TestServices.WindowHelper.WindowContent = sut;
Assert.AreEqual("DataContext", GetTextBlockText(sut, "inheritanceTest"));
sut.inheritanceTestRoot.DataContext = "46";
Assert.AreEqual("46", GetTextBlockText(sut, "inheritanceTest"));
sut.inheritanceTestRoot.DataContext = "47";
Assert.AreEqual("47", GetTextBlockText(sut, "inheritanceTest"));
sut.inheritanceTestInner.DataContext = "48";
Assert.AreEqual("48", GetTextBlockText(sut, "inheritanceTest"));
sut.inheritanceTestRoot.DataContext = "49";
Assert.AreEqual("48", GetTextBlockText(sut, "inheritanceTest"));
}
[TestMethod]
public void When_Content_Presenter_SameValue_Changing()
{
var sut = new ContentPresenter_Content_DataContext();
TestServices.WindowHelper.WindowContent = sut;
Assert.AreEqual("DataContext", GetTextBlockText(sut, "sameValueChangingTest"));
}
[TestMethod]
public void When_Content_Presenter_Null_Content_Changed()
{
var sut = new ContentPresenter_Content_DataContext();
TestServices.WindowHelper.WindowContent = sut;
Assert.AreEqual("42", GetTextBlockText(sut, "nullContentChanged"));
}
static string GetTextBlockText(FrameworkElement sut, string v)
=> (sut.FindName(v) as TextBlock)?.Text ?? "";
public static IEnumerable<object[]> GetAlignments()
{
var configurations = new List<AlignmentTestConfiguration>();
......
......@@ -8,7 +8,8 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml;
using Uno.UI.Tests.App.Xaml;
using System.Windows.Data;
using Uno.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Uno.UI.Tests.ComboBoxTests
{
......@@ -121,5 +122,78 @@ namespace Uno.UI.Tests.ComboBoxTests
comboBox.Items.RemoveAt(2);
Assert.AreEqual<int>(-1, comboBox.SelectedIndex);
}
[TestMethod]
public void When_SelectedItem_TwoWay_Binding()
{
var itemsControl = new ItemsControl()
{
ItemsPanel = new ItemsPanelTemplate(() => new StackPanel()),
ItemTemplate = new DataTemplate(() =>
{
var comboBox = new ComboBox();
comboBox.Name = "combo";
comboBox.SetBinding(
ComboBox.ItemsSourceProperty,
new Binding { Path = new("Numbers")
});
comboBox.SetBinding(
ComboBox.SelectedItemProperty,
new Binding { Path = new("SelectedNumber"), Mode = BindingMode.TwoWay });
return comboBox;
})
};
var test = new[] {
new TwoWayBindingItem()
};
itemsControl.ForceLoaded();
itemsControl.ItemsSource = test;
var comboBox = itemsControl.FindName("combo") as ComboBox;
Assert.AreEqual(3, test[0].SelectedNumber);
Assert.AreEqual(3, comboBox.SelectedItem);
}
public class TwoWayBindingItem : System.ComponentModel.INotifyPropertyChanged
{
private int _selectedNumber;
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public TwoWayBindingItem()
{
Numbers = new List<int> { 1, 2, 3, 4 };
SelectedNumber = 3;
}
public List<int> Numbers { get; }
public int SelectedNumber
{
get
{
return _selectedNumber;
}
set
{
_selectedNumber = value;
PropertyChanged?.Invoke(this, new(nameof(SelectedNumber)));
}
}
}
public class ItemModel
{
public string Text { get; set; }
public override string ToString() => Text;
}
}
}
......@@ -820,11 +820,18 @@ namespace Windows.UI.Xaml.Controls
{
_firstLoadResetDone = true;
// This test avoids the ContentPresenter from resetting
// the DataContext to null (or the inherited value) and then back to
// the content and have two-way bindings propagating the null value
// back to the source.
if (!ReferenceEquals(DataContext, Content))
{
// On first load UWP clears the local value of a ContentPresenter.
// The reason for this behavior is unknown.
this.ClearValue(DataContextProperty, DependencyPropertyValuePrecedences.Local);
TrySetDataContextFromContent(Content);
}
return true;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册