diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_Content_DataContext.xaml b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_Content_DataContext.xaml new file mode 100644 index 0000000000000000000000000000000000000000..0b3a40a3a8ae057d70cfd596dc6aef6fc2b86e33 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_Content_DataContext.xaml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_Content_DataContext.xaml.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_Content_DataContext.xaml.cs new file mode 100644 index 0000000000000000000000000000000000000000..8814e561a27a0d796d3cf19bd7bbfbe86792b6e7 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/ContentPresenter/ContentPresenter_Content_DataContext.xaml.cs @@ -0,0 +1,12 @@ +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(); + } + } +} diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ComboBox.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ComboBox.cs index 121ccf70fd50d97280efbbab211b8a18eff70bb9..5cdc940d4b70990aae25d5c5f65a9bf0066e3ac7 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ComboBox.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ComboBox.cs @@ -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 { 1, 2, 3, 4 }; + SelectedNumber = 3; + } + + public List 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; } diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ContentPresenter.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ContentPresenter.cs index fa0b6aec650e56e8d196ce2d495b7f268cdccf14..693f42355ad68417e3cce5381e0608a3425b6fe8 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ContentPresenter.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ContentPresenter.cs @@ -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 GetAlignments() { var configurations = new List(); diff --git a/src/Uno.UI.Tests/Windows_UI_XAML_Controls/ComboBoxTests/Given_ComboBox.cs b/src/Uno.UI.Tests/Windows_UI_XAML_Controls/ComboBoxTests/Given_ComboBox.cs index a47f2dc8a0b33da33293d9ad5166f05730d4ba54..80918aa8e5efde4bb0cb6697260141a715e60d19 100644 --- a/src/Uno.UI.Tests/Windows_UI_XAML_Controls/ComboBoxTests/Given_ComboBox.cs +++ b/src/Uno.UI.Tests/Windows_UI_XAML_Controls/ComboBoxTests/Given_ComboBox.cs @@ -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(-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 { 1, 2, 3, 4 }; + SelectedNumber = 3; + } + + public List 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; + } + } } diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs index fbe19480691b08a54330ddefd873f0309cb436af..b2b16e41d7dfffff6f89eea25efca60c8ac5022c 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs @@ -820,11 +820,18 @@ namespace Windows.UI.Xaml.Controls { _firstLoadResetDone = true; - // On first load UWP clears the local value of a ContentPresenter. - // The reason for this behavior is unknown. - this.ClearValue(DataContextProperty, DependencyPropertyValuePrecedences.Local); + // 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); + TrySetDataContextFromContent(Content); + } return true; }