diff --git a/src/SamplesApp/SamplesApp.Windows/Properties/launchSettings.json b/src/SamplesApp/SamplesApp.Windows/Properties/launchSettings.json index 6bb66c67c18f..2a617360fcd5 100644 --- a/src/SamplesApp/SamplesApp.Windows/Properties/launchSettings.json +++ b/src/SamplesApp/SamplesApp.Windows/Properties/launchSettings.json @@ -1,10 +1,12 @@ { "profiles": { "SamplesApp.Windows (Package)": { - "commandName": "MsixPackage" + "commandName": "MsixPackage", + "nativeDebugging": true }, "SamplesApp.Windows (Unpackaged)": { - "commandName": "Project" + "commandName": "Project", + "nativeDebugging": true } } -} \ No newline at end of file +} diff --git a/src/Uno.UI.RuntimeTests/Extensions/ControlExtensions.cs b/src/Uno.UI.RuntimeTests/Extensions/ControlExtensions.cs new file mode 100644 index 000000000000..2e6c291542f9 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Extensions/ControlExtensions.cs @@ -0,0 +1,22 @@ +using System.Runtime.CompilerServices; +using Microsoft.UI.Xaml.Controls; + +namespace Microsoft.UI.Xaml; + +internal static class ControlExtensions +{ +#if WINAPPSDK + public static T GetTemplateChild(this Control control, string childName) where T : DependencyObject + { + return UnsafeGetTemplateChild(control, childName) as T; + } + + public static DependencyObject GetTemplateChild(this Control control, string childName) + { + return UnsafeGetTemplateChild(control, childName); + } + + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "GetTemplateChild")] + extern static DependencyObject UnsafeGetTemplateChild(Control control, string childName); +#endif +} diff --git a/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/Expander/ExpanderTests_APITests.cs b/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/Expander/ExpanderTests_APITests.cs index 755bd51cd1c6..fdbf2009e92f 100644 --- a/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/Expander/ExpanderTests_APITests.cs +++ b/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/Expander/ExpanderTests_APITests.cs @@ -9,7 +9,7 @@ using Microsoft.UI.Xaml.Markup; using Microsoft.UI.Xaml.Media; using Common; -#if !HAS_UNO_WINUI +#if !HAS_UNO_WINUI && !WINAPPSDK using Microsoft/* UWP don't rename */.UI.Xaml.Controls; #endif using MUXControlsTestApp.Utilities; diff --git a/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/NavigationView/NavigationViewTests.uno.cs b/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/NavigationView/NavigationViewTests.uno.cs index af5fddfe3991..8a80db744925 100644 --- a/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/NavigationView/NavigationViewTests.uno.cs +++ b/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/NavigationView/NavigationViewTests.uno.cs @@ -6,6 +6,7 @@ using MUXControlsTestApp.Utilities; using Uno.Extensions; using Uno.UI.RuntimeTests.Helpers; +using Uno.UI.Toolkit.Extensions; using static Private.Infrastructure.TestServices; using NavigationView = Microsoft/* UWP don't rename */.UI.Xaml.Controls.NavigationView; using NavigationViewItem = Microsoft/* UWP don't rename */.UI.Xaml.Controls.NavigationViewItem; diff --git a/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/TabView/TabViewTests.Uno.cs b/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/TabView/TabViewTests.Uno.cs index 37aac3896ff1..530d25275646 100644 --- a/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/TabView/TabViewTests.Uno.cs +++ b/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/TabView/TabViewTests.Uno.cs @@ -9,9 +9,13 @@ namespace Microsoft.UI.Xaml.Tests.MUXControls.ApiTests { +#if !HAS_UNO + // For Uno, there is another partial that has the attribute + // The attribute has AllowMultiple = false so only one partial should have it. + [TestClass] +#endif public partial class TabViewTests { -#if HAS_UNO [TestMethod] #if __IOS__ [Ignore("Currently fails on iOS")] @@ -53,6 +57,5 @@ await RunOnUIThread.ExecuteAsync(async () => Assert.AreEqual(1, containerContentChangingCounter); }); } -#endif } } diff --git a/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/TeachingTip/TeachingTipTests.cs b/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/TeachingTip/TeachingTipTests.cs index 402a0fa96e8d..10b3b4e63467 100644 --- a/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/TeachingTip/TeachingTipTests.cs +++ b/src/Uno.UI.RuntimeTests/MUX/Microsoft_UI_Xaml_Controls/TeachingTip/TeachingTipTests.cs @@ -30,7 +30,7 @@ using Microsoft/* UWP don't rename */.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Shapes; -#if !HAS_UNO_WINUI +#if !HAS_UNO_WINUI && !WINAPPSDK using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; #endif @@ -38,7 +38,10 @@ using Private.Infrastructure; using System.Threading.Tasks; using Uno.UI.RuntimeTests.Helpers; + +#if !WINAPPSDK using Microsoft.UI.Private.Controls; +#endif namespace Microsoft.UI.Xaml.Tests.MUXControls.ApiTests; @@ -93,6 +96,7 @@ public async Task TeachingTipBackgroundTest() teachingTip.Background = blueBrush; Verify.AreSame(blueBrush, teachingTip.Background); +#if !WINAPPSDK { var popup = TeachingTipTestHooks.GetPopup(teachingTip); var child = popup.Child; @@ -137,12 +141,14 @@ void VerifyLightDismissTipBackground(Brush brush, string uiPart) } } } +#endif teachingTip.IsLightDismissEnabled = true; }); await TestServices.WindowHelper.WaitForIdle(); +#if !WINAPPSDK RunOnUIThread.Execute(() => { Verify.AreEqual(blueBrush.Color, ((SolidColorBrush)teachingTip.Background).Color); @@ -174,6 +180,7 @@ void VerifyBackgroundChanged(Brush brush, string uiPart) } } }); +#endif } [TestMethod] diff --git a/src/Uno.UI.RuntimeTests/MUX/Utilities/RunOnUIThread.cs b/src/Uno.UI.RuntimeTests/MUX/Utilities/RunOnUIThread.cs index da7f257ed5bf..d336d45d4891 100644 --- a/src/Uno.UI.RuntimeTests/MUX/Utilities/RunOnUIThread.cs +++ b/src/Uno.UI.RuntimeTests/MUX/Utilities/RunOnUIThread.cs @@ -8,6 +8,8 @@ using Windows.UI.Core; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; +using Private.Infrastructure; +using Microsoft.UI.Dispatching; #if USING_TAEF using WEX.TestExecution; @@ -26,14 +28,13 @@ public class RunOnUIThread { public static void Execute(Action action) { - Execute(CoreApplication.MainView, action); + Execute(TestServices.WindowHelper.CurrentTestWindow.DispatcherQueue, action); } - public static void Execute(CoreApplicationView whichView, Action action) + public static void Execute(DispatcherQueue dispatcherQueue, Action action) { Exception exception = null; - var dispatcher = whichView.Dispatcher; - if (dispatcher.HasThreadAccess) + if (dispatcherQueue.HasThreadAccess) { action(); } @@ -47,7 +48,7 @@ public static void Execute(CoreApplicationView whichView, Action action) #endif { // If the Splash screen dismissal happens on the UI thread, run the action right now. - if (dispatcher.HasThreadAccess) + if (dispatcherQueue.HasThreadAccess) { try { @@ -66,7 +67,7 @@ public static void Execute(CoreApplicationView whichView, Action action) else { // Otherwise queue the work to the UI thread and then set the completion event on that thread. - var ignore = dispatcher.RunAsync(CoreDispatcherPriority.Normal, + var ignore = dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () => { try @@ -98,27 +99,26 @@ public static void Execute(CoreApplicationView whichView, Action action) } public static async Task ExecuteAsync(Action action) => - await ExecuteAsync(CoreApplication.MainView, () => + await ExecuteAsync(TestServices.WindowHelper.CurrentTestWindow.DispatcherQueue, () => { action(); return Task.CompletedTask; }); public static async Task ExecuteAsync(Func task) => - await ExecuteAsync(CoreApplication.MainView, task); + await ExecuteAsync(TestServices.WindowHelper.CurrentTestWindow.DispatcherQueue, task); - public static async Task ExecuteAsync(CoreApplicationView whichView, Action action) => - await ExecuteAsync(whichView, () => + public static async Task ExecuteAsync(DispatcherQueue dispatcherQueue, Action action) => + await ExecuteAsync(dispatcherQueue, () => { action(); return Task.CompletedTask; }); - public static async Task ExecuteAsync(CoreApplicationView whichView, Func task) + public static async Task ExecuteAsync(DispatcherQueue dispatcherQueue, Func task) { Exception exception = null; - var dispatcher = whichView.Dispatcher; - if (dispatcher.HasThreadAccess) + if (dispatcherQueue.HasThreadAccess) { await task(); } @@ -132,7 +132,7 @@ public static async Task ExecuteAsync(CoreApplicationView whichView, Func #endif { // If the Splash screen dismissal happens on the UI thread, run the action right now. - if (dispatcher.HasThreadAccess) + if (dispatcherQueue.HasThreadAccess) { try { @@ -151,7 +151,7 @@ public static async Task ExecuteAsync(CoreApplicationView whichView, Func else { // Otherwise queue the work to the UI thread and then set the completion event on that thread. - await dispatcher.RunAsync(CoreDispatcherPriority.Normal, + dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, async () => { try 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 58da3dc95c43..46517b95a880 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 @@ -101,7 +101,7 @@ public void When_Content_Presenter_Empty() sut.emptyTestRoot.DataContext = "43"; - Assert.AreEqual("43", GetTextBlockText(sut, "emptyTest")); + Assert.AreEqual("", GetTextBlockText(sut, "emptyTest")); } [TestMethod] @@ -111,16 +111,16 @@ public void When_Content_Presenter_Priority() TestServices.WindowHelper.WindowContent = sut; - Assert.AreEqual("43", GetTextBlockText(sut, "priorityTest")); + Assert.AreEqual("", GetTextBlockText(sut, "priorityTest")); sut.priorityTestRoot.DataContext = "44"; - Assert.AreEqual("44", GetTextBlockText(sut, "priorityTest")); + Assert.AreEqual("", GetTextBlockText(sut, "priorityTest")); sut.priorityTestRoot.Content = "45"; - Assert.AreEqual("45", GetTextBlockText(sut, "priorityTest")); + Assert.AreEqual("", GetTextBlockText(sut, "priorityTest")); sut.priorityTestRoot.DataContext = "46"; - Assert.AreEqual("46", GetTextBlockText(sut, "priorityTest")); + Assert.AreEqual("", GetTextBlockText(sut, "priorityTest")); } [TestMethod] @@ -130,7 +130,7 @@ public void When_Content_Presenter_SameValue() TestServices.WindowHelper.WindowContent = sut; - Assert.AreEqual("42", GetTextBlockText(sut, "sameValueTest")); + Assert.AreEqual("", GetTextBlockText(sut, "sameValueTest")); } [TestMethod] @@ -140,19 +140,19 @@ public void When_Content_Presenter_Inheritance() TestServices.WindowHelper.WindowContent = sut; - Assert.AreEqual("DataContext", GetTextBlockText(sut, "inheritanceTest")); + Assert.AreEqual("", GetTextBlockText(sut, "inheritanceTest")); sut.inheritanceTestRoot.DataContext = "46"; - Assert.AreEqual("46", GetTextBlockText(sut, "inheritanceTest")); + Assert.AreEqual("", GetTextBlockText(sut, "inheritanceTest")); sut.inheritanceTestRoot.DataContext = "47"; - Assert.AreEqual("47", GetTextBlockText(sut, "inheritanceTest")); + Assert.AreEqual("", GetTextBlockText(sut, "inheritanceTest")); sut.inheritanceTestInner.DataContext = "48"; - Assert.AreEqual("48", GetTextBlockText(sut, "inheritanceTest")); + Assert.AreEqual("", GetTextBlockText(sut, "inheritanceTest")); sut.inheritanceTestRoot.DataContext = "49"; - Assert.AreEqual("48", GetTextBlockText(sut, "inheritanceTest")); + Assert.AreEqual("", GetTextBlockText(sut, "inheritanceTest")); } [TestMethod] @@ -167,7 +167,7 @@ public async Task When_Inside_ContentControl_Template() var border1 = presenter1.FindVisualChildByType(); var tb1 = presenter1.FindVisualChildByType(); - Assert.AreEqual(new SolidColorBrush(Microsoft.UI.Colors.LightGreen), border1.Background); + Assert.AreEqual(Microsoft.UI.Colors.LightGreen, ((SolidColorBrush)border1.Background).Color); Assert.AreEqual("Item 1", tb1.Text); var cc2 = control.FindName("CCWithContentTemplate") as ContentControl; @@ -175,7 +175,7 @@ public async Task When_Inside_ContentControl_Template() var border2 = presenter2.FindVisualChildByType(); var tb2 = presenter2.FindVisualChildByType(); - Assert.AreEqual(new SolidColorBrush(Microsoft.UI.Colors.LightPink), border2.Background); + Assert.AreEqual(Microsoft.UI.Colors.LightPink, ((SolidColorBrush)border2.Background).Color); Assert.AreEqual("Item 2", tb2.Text); var cc3 = control.FindName("CCWithContentTemplateAndContent") as ContentControl; @@ -183,7 +183,7 @@ public async Task When_Inside_ContentControl_Template() var border3 = presenter3.FindVisualChildByType(); var tb3 = presenter3.FindVisualChildByType(); - Assert.AreEqual(new SolidColorBrush(Microsoft.UI.Colors.LightGreen), border3.Background); + Assert.AreEqual(Microsoft.UI.Colors.LightGreen, ((SolidColorBrush)border3.Background).Color); Assert.AreEqual("Item 3", tb3.Text); var cc4 = control.FindName("CCWithContent") as ContentControl; @@ -201,7 +201,7 @@ public void When_Content_Presenter_SameValue_Changing() TestServices.WindowHelper.WindowContent = sut; - Assert.AreEqual("DataContext", GetTextBlockText(sut, "sameValueChangingTest")); + Assert.AreEqual("", GetTextBlockText(sut, "sameValueChangingTest")); } [TestMethod] @@ -211,7 +211,7 @@ public void When_Content_Presenter_Null_Content_Changed() TestServices.WindowHelper.WindowContent = sut; - Assert.AreEqual("42", GetTextBlockText(sut, "nullContentChanged")); + Assert.AreEqual("", GetTextBlockText(sut, "nullContentChanged")); } static string GetTextBlockText(FrameworkElement sut, string v) @@ -344,7 +344,7 @@ public async Task When_Content_Unset_Release() TestServices.WindowHelper.WindowContent = SUT; var wref = SetContent(); - Assert.AreEqual(wref.Target, SUT.DataContext); + Assert.AreEqual(null, SUT.DataContext); SUT.Content = null; diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Pivot.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Pivot.cs index aad9f8473787..ec8a4d91276f 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Pivot.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_Pivot.cs @@ -76,9 +76,13 @@ public async Task Check_Binding() tbs2.Should().NotBeNull(); - // For some reason, the count is 0 in Windows. So this doesn't currently match Windows. +#if WINAPPSDK || UNO_HAS_ENHANCED_LIFECYCLE + tbs2.Should().HaveCount(0); +#else + // For some reason, the count is 0 in Windows. So this doesn't currently match Windows on some platforms. tbs2.Should().HaveCount(1); items[1].Content.Should().Be(tbs2.ElementAt(0).Text); +#endif } #if !WINAPPSDK // GetTemplateChild is protected in UWP while public in Uno. diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/HyperlinkButtonTests/Given_HyperlinkButton.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/HyperlinkButtonTests/Given_HyperlinkButton.cs index b2bc00ae2a8c..5ae031d35fc2 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/HyperlinkButtonTests/Given_HyperlinkButton.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/HyperlinkButtonTests/Given_HyperlinkButton.cs @@ -2,18 +2,18 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; using Microsoft.VisualStudio.TestTools.UnitTesting; using Private.Infrastructure; using Windows.UI.Text; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Media; namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.HyperlinkButtonTests { [TestClass] public class Given_HyperlinkButton { -#if !WINAPPSDK // GetTemplateChild is protected in UWP while public in Uno. [TestMethod] [RunsOnUIThread] public async Task When_HyperlinkButton_With_Implicit_Content_Should_Be_Underlined() @@ -22,14 +22,23 @@ public async Task When_HyperlinkButton_With_Implicit_Content_Should_Be_Underline TestServices.WindowHelper.WindowContent = SUT; await TestServices.WindowHelper.WaitForIdle(); - var underlinedImplicitTextBlock = VisualTreeHelper.GetChild(SUT.ShouldBeUnderlinedHyperlinkButton.GetTemplateChild("ContentPresenter"), 0) as TextBlock; - Assert.IsInstanceOfType(underlinedImplicitTextBlock, typeof(ImplicitTextBlock)); + var underlinedContentPresenter = (ContentPresenter)SUT.ShouldBeUnderlinedHyperlinkButton.GetTemplateChild("ContentPresenter"); + Assert.IsNotNull(underlinedContentPresenter); +#if HAS_UNO + Assert.IsTrue(underlinedContentPresenter.IsUsingDefaultTemplate); +#endif + var underlinedImplicitTextBlock = (TextBlock)VisualTreeHelper.GetChild(underlinedContentPresenter, 0); + Assert.IsNotNull(underlinedImplicitTextBlock); Assert.AreEqual(TextDecorations.Underline, underlinedImplicitTextBlock.TextDecorations); - var notUnderlinedTextBlock = VisualTreeHelper.GetChild(SUT.ShouldNotBeUnderlinedHyperlinkButton.GetTemplateChild("ContentPresenter"), 0) as TextBlock; - Assert.IsNotInstanceOfType(notUnderlinedTextBlock, typeof(ImplicitTextBlock)); + var notUnderlinedContentPresenter = (ContentPresenter)SUT.ShouldNotBeUnderlinedHyperlinkButton.GetTemplateChild("ContentPresenter"); + Assert.IsNotNull(notUnderlinedContentPresenter); +#if HAS_UNO + Assert.IsFalse(notUnderlinedContentPresenter.IsUsingDefaultTemplate); +#endif + var notUnderlinedTextBlock = (TextBlock)VisualTreeHelper.GetChild(notUnderlinedContentPresenter, 0); + Assert.IsNotNull(notUnderlinedTextBlock); Assert.AreEqual(TextDecorations.None, notUnderlinedTextBlock.TextDecorations); } -#endif } } diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Data/Given_BindableNullableValueType.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Data/Given_BindableNullableValueType.cs index a024b2222c0d..ca18d074bbb9 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Data/Given_BindableNullableValueType.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Data/Given_BindableNullableValueType.cs @@ -16,7 +16,11 @@ public void When_BindableNullableValueTypeTestPage() var x = new BindableNullableValueTypeTestPage(); var tb = x.textBlock; tb.Tag = "10"; +#if WINAPPSDK + Assert.AreEqual(null, x.MyProperty); +#else Assert.AreEqual(10, x.MyProperty); +#endif tb.Tag = null; Assert.AreEqual(null, x.MyProperty); } diff --git a/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Windows.csproj b/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Windows.csproj index 5c9faeab737d..ceea93529a0e 100644 --- a/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Windows.csproj +++ b/src/Uno.UI.RuntimeTests/Uno.UI.RuntimeTests.Windows.csproj @@ -66,6 +66,9 @@ + + + diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.cs b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.cs index eb16c8f0f482..2ad1c8494043 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.cs @@ -1,535 +1,34 @@ -#nullable enable +using Microsoft.UI.Xaml.Markup; -using System; -using System.Collections.Generic; -using System.Text; -using Uno.Extensions; -using Uno.Foundation.Logging; -using Uno.UI; -using Uno.UI.DataBinding; -using Microsoft.UI.Xaml.Media.Animation; -using System.Collections; -using System.Linq; -using Microsoft.UI.Xaml.Data; -using Microsoft.UI.Xaml.Markup; -using Uno; +namespace Microsoft.UI.Xaml.Controls; -#if __ANDROID__ -using View = Android.Views.View; -using ViewGroup = Android.Views.ViewGroup; -using Font = Android.Graphics.Typeface; -using Android.Graphics; -#elif __IOS__ -using View = UIKit.UIView; -using ViewGroup = UIKit.UIView; -using Color = UIKit.UIColor; -using Font = UIKit.UIFont; -using UIKit; -#elif __MACOS__ -using View = AppKit.NSView; -using ViewGroup = AppKit.NSView; -using Color = AppKit.NSColor; -using Font = AppKit.NSFont; -using AppKit; -#else -using View = Microsoft.UI.Xaml.UIElement; -#endif - -namespace Microsoft.UI.Xaml.Controls +[ContentProperty(Name = nameof(Content))] +public partial class ContentControl : Control { - [ContentProperty(Name = nameof(Content))] - public partial class ContentControl : Control, IEnumerable + public override string GetAccessibilityInnerText() { - private View? _contentTemplateRoot; - - /// - /// Will be set to either the result of ContentTemplateSelector or to ContentTemplate, depending on which is used - /// - private DataTemplate? _dataTemplateUsedLastUpdate; - - private bool _canCreateTemplateWithoutParent; - - /// - /// Flag to determine if the current content has been overridden. - /// This is only in use when is true. - /// - private bool _localContentDataContextOverride; - - protected override bool CanCreateTemplateWithoutParent { get { return _canCreateTemplateWithoutParent; } } - -#nullable disable // Public members should stay nullable-oblivious for now to stay consistent with WinUI - public ContentControl() - { - DefaultStyleKey = typeof(ContentControl); - - InitializePartial(); - } - - partial void InitializePartial(); - - #region Content DependencyProperty - - public object Content - { - get - { - if (this.IsDependencyPropertySet(ContentProperty)) - { - return GetValue(ContentProperty); - } - else if (ContentTemplate != null) - { - return DataContext; - } - else - { - // Return null to be sure that the Content will be empty and prevent the type to be dispayed. - return null; - } - } - set { SetValue(ContentProperty, value); } - } - - public static DependencyProperty ContentProperty { get; } = - DependencyProperty.Register( - nameof(Content), - typeof(object), - 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 | FrameworkPropertyMetadataOptions.AffectsMeasure, - propertyChangedCallback: (s, e) => ((ContentControl)s)?.OnContentChanged(e.OldValue, e.NewValue) - ) - ); - - #endregion - - #region ContentTemplate DependencyProperty - - public DataTemplate ContentTemplate - { - get { return (DataTemplate)GetValue(ContentTemplateProperty); } - set { SetValue(ContentTemplateProperty, value); } - } - - // Using a DependencyProperty as the backing store for ContentTemplate. This enables animation, styling, binding, etc... - public static DependencyProperty ContentTemplateProperty { get; } = - DependencyProperty.Register( - nameof(ContentTemplate), - typeof(DataTemplate), - typeof(ContentControl), - new FrameworkPropertyMetadata( - null, - FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext | FrameworkPropertyMetadataOptions.AffectsMeasure, - (s, e) => ((ContentControl)s)?.OnContentTemplateChanged(e.OldValue as DataTemplate, e.NewValue as DataTemplate) - ) - ); - #endregion - - #region ContentTemplateSelector DependencyProperty - - public DataTemplateSelector ContentTemplateSelector - { - get { return (DataTemplateSelector)GetValue(ContentTemplateSelectorProperty); } - set { SetValue(ContentTemplateSelectorProperty, value); } - } - - public static DependencyProperty ContentTemplateSelectorProperty { get; } = - DependencyProperty.Register( - "ContentTemplateSelector", - typeof(DataTemplateSelector), - typeof(ContentControl), - new FrameworkPropertyMetadata( - null, - (s, e) => ((ContentControl)s)?.OnContentTemplateSelectorChanged(e.OldValue as DataTemplateSelector, e.NewValue as DataTemplateSelector) - ) - ); - #endregion - - protected virtual void OnContentChanged(object oldContent, object newContent) - { - if (IsContentPresenterBypassEnabled) - { - if (newContent is View - // This case is to support the ability for the content - // control to be templated while having a Content as a view. - // The template then needs to TemplateBind to the Content to function - // properly. - && Template == null - ) - { - // If the content is a view, no need to delay the inclusion in the visual tree - ContentTemplateRoot = newContent as View; - } - else if (oldContent != null && newContent == null) - { - // The content is being reset, remove the existing content properly. - ContentTemplateRoot = null; - } - - if (newContent != null) - { - SetUpdateTemplate(); - } - } - } - - protected virtual void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) - { - if (IsContentPresenterBypassEnabled) - { - if (ContentTemplateRoot != null) - { - ContentTemplateRoot = null; - } - - SetUpdateTemplate(); - } - else if (CanCreateTemplateWithoutParent) - { - ApplyTemplate(); - } - } - - protected virtual void OnContentTemplateSelectorChanged(DataTemplateSelector oldContentTemplateSelector, DataTemplateSelector newContentTemplateSelector) - { - if (IsContentPresenterBypassEnabled) - { - // In case there's code that happen to be here. - } - } -#nullable enable - - private void SetUpdateTemplate() + switch (Content) { - if (this.HasParent() || CanCreateTemplateWithoutParent) - { - UpdateContentTemplateRoot(); - SyncDataContext(); - this.InvalidateMeasure(); - } + case string str: + return str; + case IFrameworkElement frameworkElement: + return frameworkElement.GetAccessibilityInnerText(); + case object content: + return content.ToString(); + default: + return null; } - - partial void UnregisterContentTemplateRoot(); - -#nullable disable // Public members should stay nullable-oblivious for now to stay consistent with WinUI - public View ContentTemplateRoot - { - get - { - return _contentTemplateRoot; - } - - protected set - { - var previousValue = _contentTemplateRoot; - - if (previousValue != null) - { - ResetContentDataContextOverride(); - UnregisterContentTemplateRoot(); - - UpdateContentTransitions(this.ContentTransitions, null); - } - - _contentTemplateRoot = value; - - if (_contentTemplateRoot != null) - { - RegisterContentTemplateRoot(); - - UpdateContentTransitions(null, this.ContentTransitions); - } - - OnContentTemplateRootSet(); - } - } - - private protected virtual void OnContentTemplateRootSet() - { - } - - #region Transitions Dependency Property - - public TransitionCollection ContentTransitions - { - get { return (TransitionCollection)this.GetValue(ContentTransitionsProperty); } - set { this.SetValue(ContentTransitionsProperty, value); } - } - - public static DependencyProperty ContentTransitionsProperty { get; } = - DependencyProperty.Register("ContentTransitions", typeof(TransitionCollection), typeof(ContentControl), new FrameworkPropertyMetadata(null, OnContentTransitionsChanged)); + } #nullable enable - private static void OnContentTransitionsChanged(object dependencyObject, DependencyPropertyChangedEventArgs args) - { - var control = dependencyObject as ContentControl; - - if (control != null) - { - var oldValue = (TransitionCollection)args.OldValue; - var newValue = (TransitionCollection)args.NewValue; - - control.UpdateContentTransitions(oldValue, newValue); - } - } - - #endregion - - private void UpdateContentTransitions(TransitionCollection? oldValue, TransitionCollection? newValue) - { - var contentRoot = this.ContentTemplateRoot as IFrameworkElement; - - if (contentRoot == null) - { - return; - } - - if (oldValue != null) - { - foreach (var item in oldValue) - { - item.DetachFromElement(contentRoot); - } - } - - if (newValue != null) - { - foreach (var item in newValue) - { - item.AttachToElement(contentRoot); - } - } - } - - private protected override void OnLoaded() - { - base.OnLoaded(); - - if (IsContentPresenterBypassEnabled) - { - SetUpdateTemplate(); - } - } - - protected override void OnVisibilityChanged(Visibility oldValue, Visibility newValue) - { - base.OnVisibilityChanged(oldValue, newValue); - - if (oldValue == Visibility.Collapsed && newValue == Visibility.Visible) - { - SetUpdateTemplate(); - } - } - - public void UpdateContentTemplateRoot() - { - if (Visibility == Visibility.Collapsed) - { - return; - } - - //If ContentTemplateRoot is null, it must be updated even if the templates haven't changed - if (ContentTemplateRoot == null) - { - _dataTemplateUsedLastUpdate = null; - } - - //ContentTemplate/ContentTemplateSelector will only be applied to a control with no Template, normally the innermost element - if (IsContentPresenterBypassEnabled) - { - - var dataTemplate = this.ResolveContentTemplate(); - - //Only apply template if it has changed - if (!object.Equals(dataTemplate, _dataTemplateUsedLastUpdate)) - { - _dataTemplateUsedLastUpdate = dataTemplate; - - ContentTemplateRoot = - // Typically the ContentTemplate subtree should all have the ContentPresenter as templated-parent, - // but because we are doing without it, let's be explicit here. - // Generally, this is fine since we don't usually template-bind from a DataTemplate. - dataTemplate?.LoadContentCached(templatedParent: null) ?? - Content as View; - } - - if (Content != null - && !(Content is View) - && ContentTemplateRoot == null - && dataTemplate == null - && ContentTemplate == null - ) - { - SetContentTemplateRootToPlaceholder(); - } - - if (ContentTemplateRoot == null && Content is View contentView && dataTemplate == null) - { - // No template and Content is a View, set it directly as root - ContentTemplateRoot = contentView as View; - } - } - - if (ContentTemplateRoot != null) - { - OnApplyTemplate(); - } - } - - private void SetContentTemplateRootToPlaceholder() - { - if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) - { - this.Log().DebugFormat("No ContentTemplate was specified for {0} and content is not a UIView, defaulting to TextBlock.", GetType().Name); - } - - ContentTemplateRoot = new ImplicitTextBlock(this) - .Binding("Text", "") - .Binding("HorizontalAlignment", new Binding { Path = "HorizontalContentAlignment", Source = this, Mode = BindingMode.OneWay }) - .Binding("VerticalAlignment", new Binding { Path = "VerticalContentAlignment", Source = this, Mode = BindingMode.OneWay }); - } - - partial void RegisterContentTemplateRoot(); - - internal protected override void OnDataContextChanged(DependencyPropertyChangedEventArgs e) - { - base.OnDataContextChanged(e); - - if (IsContentPresenterBypassEnabled) - { - SyncDataContext(); - } - } - - protected virtual void SyncDataContext() - { - if (IsContentPresenterBypassEnabled) - { - // This case is to support the ability for the content - // control to be templated while having a Content as a view. - // The template then needs to TemplateBind to the Content to function - // properly. - if (Content is View) - { - ResetContentDataContextOverride(); - } - else - { - if ((ContentTemplateRoot is IDependencyObjectStoreProvider provider) && - // The DataContext may be set directly on the template root - (_localContentDataContextOverride || !(provider as DependencyObject).IsDependencyPropertyLocallySet(provider.Store.DataContextProperty)) - ) - { - _localContentDataContextOverride = true; - provider.Store.SetValue(provider.Store.DataContextProperty, Content, DependencyPropertyValuePrecedences.Local); - } - } - } - else - { - ResetContentDataContextOverride(); - } - } - - private void ResetContentDataContextOverride() - { - if (_localContentDataContextOverride && ContentTemplateRoot is IDependencyObjectStoreProvider provider) - { - _localContentDataContextOverride = false; - provider.Store.ClearValue(provider.Store.DataContextProperty, DependencyPropertyValuePrecedences.Local); - } - } - - /// - /// This property determines if the current instance is not providing a Control - /// Template, to allow for the ContentControl to avoid using a ContentPresenter. This extra layer - /// is a problem on Android, where the stack size is severely limited (32KB at most) - /// on version 4.4 and earlier. - /// Android 5.0 does not have this limitation, because ART requires greater stack sizes. - /// - /// - /// If the default style for the current type has a Template property, - /// we know that the IsContentPresenterBypassEnabled will be false once the style has been set. - /// Return false in this case, even if the Template is null. - /// - internal bool IsContentPresenterBypassEnabled => Template == null && !HasDefaultTemplate(GetDefaultStyleKey()); - - /// - /// Gets whether the default style for the given type sets a non-null Template. - /// - private static Func HasDefaultTemplate = - Funcs.CreateMemoized((Type type) => - Style.GetDefaultStyleForType(type) is Style defaultStyle - && defaultStyle - .Flatten(s => s.BasedOn!) - .SelectMany(s => s.Setters) - .OfType() - .Any(s => s.Property == TemplateProperty && s.Value != null) - ); - - /// - /// Creates a ContentControl which can be measured without being added to the visual tree (eg as container in virtualized lists). - /// - internal static ContentControl CreateItemContainer() - { - return new ContentControl - { - _canCreateTemplateWithoutParent = true, - IsGeneratedContainer = true - }; - } - -#nullable disable // Public members should stay nullable-oblivious for now to stay consistent with WinUI -#if __ANDROID__ - // Support for the C# collection initializer style. - public void Add(View view) - { - Content = view; - } - - public IEnumerator GetEnumerator() + internal void ClearContentPresenterBypass() + { + if (Content is UIElement contentAsUIE && ContentTemplateRoot == contentAsUIE) { - if (Content != null) - { - return new[] { Content }.GetEnumerator(); - } - else - { - return Enumerable.Empty().GetEnumerator(); - } - } + RemoveChild(contentAsUIE); +#if !UNO_HAS_ENHANCED_LIFECYCLE + ContentTemplateRoot = null; #endif - - public override string GetAccessibilityInnerText() - { - switch (Content) - { - case string str: - return str; - case IFrameworkElement frameworkElement: - return frameworkElement.GetAccessibilityInnerText(); - case object content: - return content.ToString(); - default: - return null; - } - } -#nullable enable - - internal void ClearContentPresenterBypass() - { - if (Content is UIElement contentAsUIE && ContentTemplateRoot == contentAsUIE) - { - - RemoveChild(contentAsUIE); - ContentTemplateRoot = null; - } } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.mux.lifecycle.cs b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.mux.lifecycle.cs new file mode 100644 index 000000000000..f89394fef167 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.mux.lifecycle.cs @@ -0,0 +1,315 @@ +#if UNO_HAS_ENHANCED_LIFECYCLE + +using System; +using System.Text; +using Microsoft.UI.Xaml.Markup; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; + +namespace Microsoft.UI.Xaml.Controls; + +public partial class ContentControl : Control +{ + public ContentControl() + { + DefaultStyleKey = typeof(ContentControl); + } + + public object Content + { + get => GetValue(ContentProperty); + set => SetValue(ContentProperty, value); + } + + public static DependencyProperty ContentProperty { get; } = + DependencyProperty.Register( + nameof(Content), + typeof(object), + typeof(ContentControl), + new FrameworkPropertyMetadata( + defaultValue: null, + FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext | FrameworkPropertyMetadataOptions.AffectsMeasure, + propertyChangedCallback: (s, e) => ((ContentControl)s)?.OnContentChanged(e.OldValue, e.NewValue) + ) + ); + + protected virtual void OnContentChanged(object oldContent, object newContent) + { + Invalidate( + (Template is null) && + (oldContent is UIElement || newContent is UIElement) + ); + + if (newContent is not null) + { + DataTemplate contentTemplate = ContentTemplate; + if (contentTemplate is null) + { + var contentTemplateSelector = ContentTemplateSelector; + if (contentTemplateSelector is not null) + { + contentTemplate = RefreshSelectedTemplate(contentTemplateSelector, newContent, reloadContent: false); + } + + SelectedContentTemplate = contentTemplate; + } + } + } + + #region ContentTemplate DependencyProperty + + public DataTemplate ContentTemplate + { + get => (DataTemplate)GetValue(ContentTemplateProperty); + set => SetValue(ContentTemplateProperty, value); + } + + public static DependencyProperty ContentTemplateProperty { get; } = + DependencyProperty.Register( + nameof(ContentTemplate), + typeof(DataTemplate), + typeof(ContentControl), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext | FrameworkPropertyMetadataOptions.AffectsMeasure, + (s, e) => ((ContentControl)s)?.OnContentTemplateChanged(e.OldValue as DataTemplate, e.NewValue as DataTemplate) + ) + ); + + private void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) + { + Invalidate(Template is null); + + if (newContentTemplate == null) + { + DataTemplate contentTemplate = null; + if (ContentTemplateSelector is { } contentTemplateSelector) + { + contentTemplate = RefreshSelectedTemplate(contentTemplateSelector, content: null, reloadContent: true); + } + + SelectedContentTemplate = contentTemplate; + } + + } + #endregion + + + #region ContentTemplateSelector DependencyProperty + + public DataTemplateSelector ContentTemplateSelector + { + get => (DataTemplateSelector)GetValue(ContentTemplateSelectorProperty); + set => SetValue(ContentTemplateSelectorProperty, value); + } + + public static DependencyProperty ContentTemplateSelectorProperty { get; } = + DependencyProperty.Register( + nameof(ContentTemplateSelector), + typeof(DataTemplateSelector), + typeof(ContentControl), + new FrameworkPropertyMetadata( + null, + (s, e) => ((ContentControl)s)?.OnContentTemplateSelectorChanged(e.OldValue as DataTemplateSelector, e.NewValue as DataTemplateSelector) + ) + ); + + private void OnContentTemplateSelectorChanged(DataTemplateSelector oldContentTemplateSelector, DataTemplateSelector newContentTemplateSelector) + { + var contentTemplate = ContentTemplate; + if (contentTemplate is null) + { + if (newContentTemplateSelector is not null) + { + contentTemplate = RefreshSelectedTemplate(newContentTemplateSelector, content: null, reloadContent: true); + } + + SelectedContentTemplate = contentTemplate; + } + } + #endregion + + #region SelectedContentTemplate DependencyProperty + + public DataTemplate SelectedContentTemplate + { + get => (DataTemplate)GetValue(SelectedContentTemplateProperty); + set => SetValue(SelectedContentTemplateProperty, value); + } + + public static DependencyProperty SelectedContentTemplateProperty { get; } = + DependencyProperty.Register( + nameof(SelectedContentTemplate), + typeof(DataTemplate), + typeof(ContentControl), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext, + (s, e) => ((ContentControl)s)?.OnSelectedContentTemplateChanged(e.OldValue as DataTemplate, e.NewValue as DataTemplate) + ) + ); + + private void OnSelectedContentTemplateChanged(DataTemplate oldSelectedContentTemplate, DataTemplate newSelectedContentTemplate) + { + if (ContentTemplate is null) + { + Invalidate(Template is null); + } + } + #endregion + + private DataTemplate RefreshSelectedTemplate(DataTemplateSelector contentTemplateSelector, object content, bool reloadContent) + { + return contentTemplateSelector.SelectTemplate(reloadContent ? Content : content, this); + } + + private protected override void OnTemplateChanged(DependencyPropertyChangedEventArgs e) + { + if (Content is UIElement contentAsUIElement && + contentAsUIElement.GetUIElementAdjustedParentInternal() is { } parent) + { + parent.RemoveChild(contentAsUIElement); + } + + base.OnTemplateChanged(e); + } + + private void Invalidate(bool clearChildren) + { + if (clearChildren && GetChildren() is { Count: > 0 }) + { + ClearChildren(); + // only clear the suggested cp if we actually removed all children! + m_pTemplatePresenter = null; + } + + InvalidateMeasure(); + } + + private bool _inOnApplyTemplate; + private protected override void ApplyTemplate(out bool addedVisuals) + { + base.ApplyTemplate(out addedVisuals); + if (_inOnApplyTemplate) + { + return; + } + + _inOnApplyTemplate = true; + + if (VisualTreeHelper.GetChildrenCount(this) == 0 && Content is { } content) + { + CreateDefaultVisuals(this, content as DependencyObject); + addedVisuals = VisualTreeHelper.GetChildrenCount(this) > 0; + } + + _inOnApplyTemplate = false; + } + + private void CreateDefaultVisuals(ContentControl parent, DependencyObject content) + { + if (content is UIElement ui) + { + parent.AddChild(ui); + } + else + { + parent.Template = CreateDefaultTemplate(parent); + parent.ApplyTemplate(out _); + } + } + + private const string c_strTextTemplateStorage = """ + + + + + + """; + + internal static ControlTemplate CreateDefaultTemplate(FrameworkElement parent) + { + var template = (ControlTemplate)XamlReader.Load(c_strTextTemplateStorage); + template.TargetType = parent.GetType(); + return template; + } + + public WeakReference m_pTemplatePresenter; + + public UIElement ContentTemplateRoot + { + get + { + UIElement templateRoot = null; + + if (m_pTemplatePresenter?.TryGetTarget(out var pTemplatePresenter) == true) + { + templateRoot = VisualTreeHelper.GetChild(pTemplatePresenter, 0) as UIElement; + } + + return templateRoot; + } + } + + // gets called when a contentpresenter in the template of a contentcontrol is used. It will call back + // offering its content to compare to the cc's content. If they are the same, we consider that + // contentpresenter our templateroot. + internal void ConsiderContentPresenterForContentTemplateRoot(ContentPresenter candidate, object value) + { + if (Content == value) + { + m_pTemplatePresenter = new WeakReference(candidate); + } + } + + #region Transitions Dependency Property + + public TransitionCollection ContentTransitions + { + get => (TransitionCollection)this.GetValue(ContentTransitionsProperty); + set => this.SetValue(ContentTransitionsProperty, value); + } + + public static DependencyProperty ContentTransitionsProperty { get; } = + DependencyProperty.Register(nameof(ContentTransitions), typeof(TransitionCollection), typeof(ContentControl), new FrameworkPropertyMetadata(null, OnContentTransitionsChanged)); +#nullable enable + + private static void OnContentTransitionsChanged(object dependencyObject, DependencyPropertyChangedEventArgs args) + { + var control = dependencyObject as ContentControl; + + if (control != null) + { + var oldValue = (TransitionCollection)args.OldValue; + var newValue = (TransitionCollection)args.NewValue; + + control.UpdateContentTransitions(oldValue, newValue); + } + } + + private void UpdateContentTransitions(TransitionCollection? oldValue, TransitionCollection? newValue) + { + var contentRoot = this.ContentTemplateRoot as IFrameworkElement; + if (contentRoot == null) + { + return; + } + if (oldValue != null) + { + foreach (var item in oldValue) + { + item.DetachFromElement(contentRoot); + } + } + if (newValue != null) + { + foreach (var item in newValue) + { + item.AttachToElement(contentRoot); + } + } + } + + #endregion +} +#endif diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.nonlifecycle.cs b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.nonlifecycle.cs new file mode 100644 index 000000000000..543776bf1832 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.nonlifecycle.cs @@ -0,0 +1,511 @@ +#if !UNO_HAS_ENHANCED_LIFECYCLE +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text; +using Uno.Extensions; +using Uno.Foundation.Logging; +using Uno.UI; +using Uno.UI.DataBinding; +using Microsoft.UI.Xaml.Media.Animation; +using System.Collections; +using System.Linq; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Markup; +using Uno; + +#if __ANDROID__ +using View = Android.Views.View; +using ViewGroup = Android.Views.ViewGroup; +using Font = Android.Graphics.Typeface; +using Android.Graphics; +#elif __IOS__ +using View = UIKit.UIView; +using ViewGroup = UIKit.UIView; +using Color = UIKit.UIColor; +using Font = UIKit.UIFont; +using UIKit; +#elif __MACOS__ +using View = AppKit.NSView; +using ViewGroup = AppKit.NSView; +using Color = AppKit.NSColor; +using Font = AppKit.NSFont; +using AppKit; +#else +using View = Microsoft.UI.Xaml.UIElement; +#endif + +namespace Microsoft.UI.Xaml.Controls +{ + public partial class ContentControl : Control, IEnumerable + { + private View? _contentTemplateRoot; + + /// + /// Will be set to either the result of ContentTemplateSelector or to ContentTemplate, depending on which is used + /// + private DataTemplate? _dataTemplateUsedLastUpdate; + + private bool _canCreateTemplateWithoutParent; + + /// + /// Flag to determine if the current content has been overridden. + /// This is only in use when is true. + /// + private bool _localContentDataContextOverride; + + protected override bool CanCreateTemplateWithoutParent { get { return _canCreateTemplateWithoutParent; } } + +#nullable disable // Public members should stay nullable-oblivious for now to stay consistent with WinUI + public ContentControl() + { + DefaultStyleKey = typeof(ContentControl); + + InitializePartial(); + } + + partial void InitializePartial(); + + #region Content DependencyProperty + + public object Content + { + get + { + if (this.IsDependencyPropertySet(ContentProperty)) + { + return GetValue(ContentProperty); + } + else if (ContentTemplate != null) + { + return DataContext; + } + else + { + // Return null to be sure that the Content will be empty and prevent the type to be dispayed. + return null; + } + } + set { SetValue(ContentProperty, value); } + } + + public static DependencyProperty ContentProperty { get; } = + DependencyProperty.Register( + nameof(Content), + typeof(object), + 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 | FrameworkPropertyMetadataOptions.AffectsMeasure, + propertyChangedCallback: (s, e) => ((ContentControl)s)?.OnContentChanged(e.OldValue, e.NewValue) + ) + ); + + #endregion + + #region ContentTemplate DependencyProperty + + public DataTemplate ContentTemplate + { + get { return (DataTemplate)GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } + } + + // Using a DependencyProperty as the backing store for ContentTemplate. This enables animation, styling, binding, etc... + public static DependencyProperty ContentTemplateProperty { get; } = + DependencyProperty.Register( + nameof(ContentTemplate), + typeof(DataTemplate), + typeof(ContentControl), + new FrameworkPropertyMetadata( + null, + FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext | FrameworkPropertyMetadataOptions.AffectsMeasure, + (s, e) => ((ContentControl)s)?.OnContentTemplateChanged(e.OldValue as DataTemplate, e.NewValue as DataTemplate) + ) + ); + #endregion + + #region ContentTemplateSelector DependencyProperty + + public DataTemplateSelector ContentTemplateSelector + { + get { return (DataTemplateSelector)GetValue(ContentTemplateSelectorProperty); } + set { SetValue(ContentTemplateSelectorProperty, value); } + } + + public static DependencyProperty ContentTemplateSelectorProperty { get; } = + DependencyProperty.Register( + "ContentTemplateSelector", + typeof(DataTemplateSelector), + typeof(ContentControl), + new FrameworkPropertyMetadata( + null, + (s, e) => ((ContentControl)s)?.OnContentTemplateSelectorChanged(e.OldValue as DataTemplateSelector, e.NewValue as DataTemplateSelector) + ) + ); + #endregion + + protected virtual void OnContentChanged(object oldContent, object newContent) + { + if (IsContentPresenterBypassEnabled) + { + if (newContent is View + // This case is to support the ability for the content + // control to be templated while having a Content as a view. + // The template then needs to TemplateBind to the Content to function + // properly. + && Template == null + ) + { + // If the content is a view, no need to delay the inclusion in the visual tree + ContentTemplateRoot = newContent as View; + } + else if (oldContent != null && newContent == null) + { + // The content is being reset, remove the existing content properly. + ContentTemplateRoot = null; + } + + if (newContent != null) + { + SetUpdateTemplate(); + } + } + } + + protected virtual void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) + { + if (IsContentPresenterBypassEnabled) + { + if (ContentTemplateRoot != null) + { + ContentTemplateRoot = null; + } + + SetUpdateTemplate(); + } + else if (CanCreateTemplateWithoutParent) + { + ApplyTemplate(); + } + } + + protected virtual void OnContentTemplateSelectorChanged(DataTemplateSelector oldContentTemplateSelector, DataTemplateSelector newContentTemplateSelector) + { + if (IsContentPresenterBypassEnabled) + { + // In case there's code that happen to be here. + } + } +#nullable enable + + private void SetUpdateTemplate() + { + if (this.HasParent() || CanCreateTemplateWithoutParent) + { + UpdateContentTemplateRoot(); + SyncDataContext(); + this.InvalidateMeasure(); + } + } + + partial void UnregisterContentTemplateRoot(); + +#nullable disable // Public members should stay nullable-oblivious for now to stay consistent with WinUI + public View ContentTemplateRoot + { + get + { + return _contentTemplateRoot; + } + + protected set + { + var previousValue = _contentTemplateRoot; + + if (previousValue != null) + { + ResetContentDataContextOverride(); + UnregisterContentTemplateRoot(); + + UpdateContentTransitions(this.ContentTransitions, null); + } + + _contentTemplateRoot = value; + + if (_contentTemplateRoot != null) + { + RegisterContentTemplateRoot(); + + UpdateContentTransitions(null, this.ContentTransitions); + } + + OnContentTemplateRootSet(); + } + } + + private protected virtual void OnContentTemplateRootSet() + { + } + + #region Transitions Dependency Property + + public TransitionCollection ContentTransitions + { + get { return (TransitionCollection)this.GetValue(ContentTransitionsProperty); } + set { this.SetValue(ContentTransitionsProperty, value); } + } + + public static DependencyProperty ContentTransitionsProperty { get; } = + DependencyProperty.Register("ContentTransitions", typeof(TransitionCollection), typeof(ContentControl), new FrameworkPropertyMetadata(null, OnContentTransitionsChanged)); +#nullable enable + + private static void OnContentTransitionsChanged(object dependencyObject, DependencyPropertyChangedEventArgs args) + { + var control = dependencyObject as ContentControl; + + if (control != null) + { + var oldValue = (TransitionCollection)args.OldValue; + var newValue = (TransitionCollection)args.NewValue; + + control.UpdateContentTransitions(oldValue, newValue); + } + } + + #endregion + + private void UpdateContentTransitions(TransitionCollection? oldValue, TransitionCollection? newValue) + { + var contentRoot = this.ContentTemplateRoot as IFrameworkElement; + + if (contentRoot == null) + { + return; + } + + if (oldValue != null) + { + foreach (var item in oldValue) + { + item.DetachFromElement(contentRoot); + } + } + + if (newValue != null) + { + foreach (var item in newValue) + { + item.AttachToElement(contentRoot); + } + } + } + + private protected override void OnLoaded() + { + base.OnLoaded(); + + if (IsContentPresenterBypassEnabled) + { + SetUpdateTemplate(); + } + } + + protected override void OnVisibilityChanged(Visibility oldValue, Visibility newValue) + { + base.OnVisibilityChanged(oldValue, newValue); + + if (oldValue == Visibility.Collapsed && newValue == Visibility.Visible) + { + SetUpdateTemplate(); + } + } + + public void UpdateContentTemplateRoot() + { + if (Visibility == Visibility.Collapsed) + { + return; + } + + //If ContentTemplateRoot is null, it must be updated even if the templates haven't changed + if (ContentTemplateRoot == null) + { + _dataTemplateUsedLastUpdate = null; + } + + //ContentTemplate/ContentTemplateSelector will only be applied to a control with no Template, normally the innermost element + if (IsContentPresenterBypassEnabled) + { + + var dataTemplate = this.ResolveContentTemplate(); + + //Only apply template if it has changed + if (!object.Equals(dataTemplate, _dataTemplateUsedLastUpdate)) + { + _dataTemplateUsedLastUpdate = dataTemplate; + + ContentTemplateRoot = + // Typically the ContentTemplate subtree should all have the ContentPresenter as templated-parent, + // but because we are doing without it, let's be explicit here. + // Generally, this is fine since we don't usually template-bind from a DataTemplate. + dataTemplate?.LoadContentCached(templatedParent: null) ?? + Content as View; + } + + if (Content != null + && !(Content is View) + && ContentTemplateRoot == null + && dataTemplate == null + && ContentTemplate == null + ) + { + SetContentTemplateRootToPlaceholder(); + } + + if (ContentTemplateRoot == null && Content is View contentView && dataTemplate == null) + { + // No template and Content is a View, set it directly as root + ContentTemplateRoot = contentView as View; + } + } + + if (ContentTemplateRoot != null) + { + OnApplyTemplate(); + } + } + + private void SetContentTemplateRootToPlaceholder() + { + if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) + { + this.Log().DebugFormat("No ContentTemplate was specified for {0} and content is not a UIView, defaulting to TextBlock.", GetType().Name); + } + + ContentTemplateRoot = new ImplicitTextBlock(this) + .Binding("Text", "") + .Binding("HorizontalAlignment", new Binding { Path = "HorizontalContentAlignment", Source = this, Mode = BindingMode.OneWay }) + .Binding("VerticalAlignment", new Binding { Path = "VerticalContentAlignment", Source = this, Mode = BindingMode.OneWay }); + } + + partial void RegisterContentTemplateRoot(); + + internal protected override void OnDataContextChanged(DependencyPropertyChangedEventArgs e) + { + base.OnDataContextChanged(e); + + if (IsContentPresenterBypassEnabled) + { + SyncDataContext(); + } + } + + protected virtual void SyncDataContext() + { + if (IsContentPresenterBypassEnabled) + { + // This case is to support the ability for the content + // control to be templated while having a Content as a view. + // The template then needs to TemplateBind to the Content to function + // properly. + if (Content is View) + { + ResetContentDataContextOverride(); + } + else + { + if ((ContentTemplateRoot is IDependencyObjectStoreProvider provider) && + // The DataContext may be set directly on the template root + (_localContentDataContextOverride || !(provider as DependencyObject).IsDependencyPropertyLocallySet(provider.Store.DataContextProperty)) + ) + { + _localContentDataContextOverride = true; + provider.Store.SetValue(provider.Store.DataContextProperty, Content, DependencyPropertyValuePrecedences.Local); + } + } + } + else + { + ResetContentDataContextOverride(); + } + } + + private void ResetContentDataContextOverride() + { + if (_localContentDataContextOverride && ContentTemplateRoot is IDependencyObjectStoreProvider provider) + { + _localContentDataContextOverride = false; + provider.Store.ClearValue(provider.Store.DataContextProperty, DependencyPropertyValuePrecedences.Local); + } + } + + /// + /// This property determines if the current instance is not providing a Control + /// Template, to allow for the ContentControl to avoid using a ContentPresenter. This extra layer + /// is a problem on Android, where the stack size is severely limited (32KB at most) + /// on version 4.4 and earlier. + /// Android 5.0 does not have this limitation, because ART requires greater stack sizes. + /// + /// + /// If the default style for the current type has a Template property, + /// we know that the IsContentPresenterBypassEnabled will be false once the style has been set. + /// Return false in this case, even if the Template is null. + /// + internal bool IsContentPresenterBypassEnabled => Template == null && !HasDefaultTemplate(GetDefaultStyleKey()); + + /// + /// Gets whether the default style for the given type sets a non-null Template. + /// + private static Func HasDefaultTemplate = + Funcs.CreateMemoized((Type type) => + Style.GetDefaultStyleForType(type) is Style defaultStyle + && defaultStyle + .Flatten(s => s.BasedOn!) + .SelectMany(s => s.Setters) + .OfType() + .Any(s => s.Property == TemplateProperty && s.Value != null) + ); + + /// + /// Creates a ContentControl which can be measured without being added to the visual tree (eg as container in virtualized lists). + /// + internal static ContentControl CreateItemContainer() + { + return new ContentControl + { + _canCreateTemplateWithoutParent = true, + IsGeneratedContainer = true + }; + } + +#nullable disable // Public members should stay nullable-oblivious for now to stay consistent with WinUI +#if __ANDROID__ + // Support for the C# collection initializer style. + public void Add(View view) + { + Content = view; + } + + public IEnumerator GetEnumerator() + { + if (Content != null) + { + return new[] { Content }.GetEnumerator(); + } + else + { + return Enumerable.Empty().GetEnumerator(); + } + } +#endif +#nullable enable + } +} +#endif diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.reference.cs b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.reference.cs index 4486d3d0316b..f49e00b35196 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.reference.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.reference.cs @@ -6,7 +6,6 @@ namespace Microsoft.UI.Xaml.Controls { public partial class ContentControl { - internal void SetUpdateControlTemplate() { } private bool HasParent() => true; partial void RegisterContentTemplateRoot() diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.skia.cs b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.skia.cs index 26eef7c2913d..fd211a558d50 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.skia.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ContentControl/ContentControl.skia.cs @@ -15,16 +15,6 @@ namespace Microsoft.UI.Xaml.Controls { public partial class ContentControl { - partial void RegisterContentTemplateRoot() - { - AddChild(ContentTemplateRoot); - } - - partial void UnregisterContentTemplateRoot() - { - RemoveChild(ContentTemplateRoot); - } - protected override Size MeasureOverride(Size availableSize) => base.MeasureOverride(availableSize); } } diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs index e97e77de9573..ed468fcc816a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs @@ -54,7 +54,7 @@ namespace Microsoft.UI.Xaml.Controls; /// but the ContentSource property is not available, because there are ControlTemplates for now. /// [ContentProperty(Name = nameof(Content))] -public partial class ContentPresenter : FrameworkElement, IFrameworkTemplatePoolAware +public partial class ContentPresenter : FrameworkElement #if !__CROSSRUNTIME__ && !IS_UNIT_TESTS , ICustomClippingElement #endif @@ -63,14 +63,12 @@ public partial class ContentPresenter : FrameworkElement, IFrameworkTemplatePool private readonly BorderLayerRenderer _borderRenderer; #endif - private bool _firstLoadResetDone; private View _contentTemplateRoot; - private bool _appliedTemplate; /// /// Will be set to either the result of ContentTemplateSelector or to ContentTemplate, depending on which is used /// - private DataTemplate _dataTemplateUsedLastUpdate; + private FrameworkTemplate _dataTemplateUsedLastUpdate; public ContentPresenter() { @@ -110,8 +108,6 @@ public ContentPresenter() /// This is used to alter the propagation of the templated parent. internal bool IsNativeHost { get; set; } - internal DataTemplate SelectedContentTemplate => _dataTemplateUsedLastUpdate; - protected override bool IsSimpleLayout => true; #region Content DependencyProperty @@ -129,7 +125,7 @@ public object Content typeof(ContentPresenter), new FrameworkPropertyMetadata( defaultValue: null, - options: FrameworkPropertyMetadataOptions.AffectsMeasure, + options: ContentPropertyOptions, propertyChangedCallback: (s, e) => ((ContentPresenter)s)?.OnContentChanged(e.OldValue, e.NewValue) ) ); @@ -647,90 +643,6 @@ private void OnCornerRadiusChanged(CornerRadius oldValue, CornerRadius newValue) #endregion - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - // Applying the template will not delete existing visuals. This will be done conditionally - // when the template is invalidated. - // Uno specific: since we don't call this early enough, we have to comment out the condition - // if (GetChildren().Count == 0) - { - ContentControl pTemplatedParent = GetTemplatedParent() as ContentControl; - - // Only ContentControl has the two properties below. Other parents would just fail to bind since they don't have these - // two content related properties. - if (pTemplatedParent != null -#if ANDROID || __IOS__ - && this is not NativeCommandBarPresenter // Uno specific: NativeCommandBarPresenter breaks if you inherit from the TP -#endif - ) - { - // bool needsRefresh = false; - DependencyProperty pdpTarget; - - // By default Content and ContentTemplate are template are bound. - // If no template binding exists already then hook them up now - // pdpTarget = GetPropertyByIndexInline(KnownPropertyIndex::ContentPresenter_SelectedContentTemplate); - // IFCEXPECT(pdpTarget); - // if (IsPropertyDefault(pdpTarget) && !IsPropertyTemplateBound(pdpTarget)) - // { - // const CDependencyProperty* pdpSource = pTemplatedParent->GetPropertyByIndexInline(KnownPropertyIndex::ContentControl_SelectedContentTemplate); - // IFCEXPECT(pdpSource); - // - // IFC(SetTemplateBinding(pdpTarget, pdpSource)); - // needsRefresh = true; - // } - - // UNO Specific: SelectedContentTemplate is not implemented, we hook ContentTemplateSelector instead - pdpTarget = ContentPresenter.ContentTemplateSelectorProperty; - global::System.Diagnostics.Debug.Assert(pdpTarget is { }); - var store = ((IDependencyObjectStoreProvider)this).Store; - if (store.GetCurrentHighestValuePrecedence(pdpTarget) == DependencyPropertyValuePrecedences.DefaultValue && - !store.IsPropertyTemplateBound(pdpTarget)) - { - DependencyProperty pdpSource = ContentControl.ContentTemplateSelectorProperty; - global::System.Diagnostics.Debug.Assert(pdpSource is { }); - - store.SetTemplateBinding(pdpTarget, pdpSource); - // needsRefresh = true; - } - - pdpTarget = ContentPresenter.ContentTemplateProperty; - global::System.Diagnostics.Debug.Assert(pdpTarget is { }); - if (store.GetCurrentHighestValuePrecedence(pdpTarget) == DependencyPropertyValuePrecedences.DefaultValue && - !store.IsPropertyTemplateBound(pdpTarget)) - { - DependencyProperty pdpSource = ContentControl.ContentTemplateProperty; - global::System.Diagnostics.Debug.Assert(pdpSource is { }); - - store.SetTemplateBinding(pdpTarget, pdpSource); - // needsRefresh = true; - } - - pdpTarget = ContentPresenter.ContentProperty; - global::System.Diagnostics.Debug.Assert(pdpTarget is { }); - if (store.GetCurrentHighestValuePrecedence(pdpTarget) == DependencyPropertyValuePrecedences.DefaultValue && - !store.IsPropertyTemplateBound(pdpTarget)) - { - DependencyProperty pdpSource = ContentControl.ContentProperty; - global::System.Diagnostics.Debug.Assert(pdpSource is { }); - - store.SetTemplateBinding(pdpTarget, pdpSource); - // needsRefresh = true; - } - - // Uno specific: uno bindings don't work this way - // Setting up the binding doesn't get you the values. We need to call refresh to get the latest value - // for m_pContentTemplate, SelectedContentTemplate and/or m_pContent for the tests below. - // if (needsRefresh) - // { - // IFC(pTemplatedParent->RefreshTemplateBindings(TemplateBindingsRefreshType::All)); - // } - } - } - } - protected virtual void OnForegroundColorChanged(Brush oldValue, Brush newValue) { OnForegroundColorChangedPartial(oldValue, newValue); @@ -775,55 +687,74 @@ private protected virtual void OnFontStretchChanged(FontStretch oldValue, FontSt protected virtual void OnContentChanged(object oldValue, object newValue) { +#if UNO_HAS_ENHANCED_LIFECYCLE + if (GetTemplatedParent() is ContentControl contentControl) + { + contentControl.ConsiderContentPresenterForContentTemplateRoot(this, newValue); + } +#endif if (oldValue is View || newValue is View) { // Make sure not to reuse the previous Content as a ContentTemplateRoot (i.e., in case there's no data template) // If setting Content to a new View, recreate the template ContentTemplateRoot = null; +#if UNO_HAS_ENHANCED_LIFECYCLE + Invalidate(true); +#endif } +#if !UNO_HAS_ENHANCED_LIFECYCLE TrySetDataContextFromContent(newValue); TryRegisterNativeElement(oldValue, newValue); SetUpdateTemplate(); +#endif } private void TrySetDataContextFromContent(object value) { - if (value == null) + if (value == null || value is View) { this.ClearValue(DataContextProperty, DependencyPropertyValuePrecedences.Local); } else { - if (!(value is View)) - { - // If the content is not a view, we apply the content as the - // DataContext of the materialized content. - DataContext = value; - } - else - { - // Restore DataContext propagation if the content is a view - this.ClearValue(DataContextProperty, DependencyPropertyValuePrecedences.Local); - } + DataContext = value; } } protected virtual void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) { +#if UNO_HAS_ENHANCED_LIFECYCLE + Invalidate(true); +#else if (ContentTemplateRoot != null) { ContentTemplateRoot = null; } SetUpdateTemplate(); +#endif } protected virtual void OnContentTemplateSelectorChanged(DataTemplateSelector oldContentTemplateSelector, DataTemplateSelector newContentTemplateSelector) { +#if UNO_HAS_ENHANCED_LIFECYCLE + var contentTemplate = ContentTemplate; + if (contentTemplate is null) + { + if (newContentTemplateSelector is not null) + { + var content = Content; + contentTemplate = newContentTemplateSelector.SelectTemplate(content); + } + + SelectedContentTemplate = contentTemplate; + } +#else SetUpdateTemplate(); +#endif } partial void UnregisterContentTemplateRoot(); @@ -841,9 +772,11 @@ protected set if (previousValue != null) { +#if !UNO_HAS_ENHANCED_LIFECYCLE CleanupView(previousValue); UnregisterContentTemplateRoot(); +#endif UpdateContentTransitions(this.ContentTransitions, null); } @@ -852,7 +785,9 @@ protected set if (_contentTemplateRoot != null) { +#if !UNO_HAS_ENHANCED_LIFECYCLE RegisterContentTemplateRoot(); +#endif UpdateContentTransitions(null, this.ContentTransitions); } @@ -897,44 +832,11 @@ private void CleanupView(View previousValue) } } -#if UNO_HAS_ENHANCED_LIFECYCLE - internal override void EnterImpl(EnterParams @params, int depth) - { - base.EnterImpl(@params, depth); - - if (ResetDataContextOnFirstLoad() || ContentTemplateRoot == null) - { - SetUpdateTemplate(); - } - -#if !UNO_HAS_BORDER_VISUAL - UpdateBorder(); -#endif - - // We do this in Enter not Loaded since Loaded is a lot more tricky - // (e.g. you can have Unloaded without Loaded, you can have multiple loaded events without unloaded in between, etc.) - if (IsNativeHost) - { - AttachNativeElement(); - } - } - - internal override void LeaveImpl(LeaveParams @params) - { - base.LeaveImpl(@params); - - if (IsNativeHost) - { - DetachNativeElement(Content); - } - } -#endif - private protected override void OnLoaded() { base.OnLoaded(); - +#if !UNO_HAS_ENHANCED_LIFECYCLE // WinUI has some special handling for ContentPresenter and ContentControl where even though they aren't Controls, // they use OnApplyTemplate. As a workaround for now, we just call OnApplyTemplate here. if (!_appliedTemplate) @@ -943,15 +845,11 @@ private protected override void OnLoaded() OnApplyTemplate(); } - -#if !UNO_HAS_ENHANCED_LIFECYCLE if (ResetDataContextOnFirstLoad() || ContentTemplateRoot == null) { SetUpdateTemplate(); } -#endif -#if !UNO_HAS_ENHANCED_LIFECYCLE UpdateBorder(); if (IsNativeHost) @@ -973,121 +871,16 @@ private protected override void OnUnloaded() #endif } - private bool ResetDataContextOnFirstLoad() - { - if (!_firstLoadResetDone) - { - _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; - } - - return false; - } - - void IFrameworkTemplatePoolAware.OnTemplateRecycled() - { - // This needs to be cleared on recycle, to prevent - // SetUpdateTemplate from being skipped in OnLoaded. - _firstLoadResetDone = false; - } - protected override void OnVisibilityChanged(Visibility oldValue, Visibility newValue) { base.OnVisibilityChanged(oldValue, newValue); +#if !UNO_HAS_ENHANCED_LIFECYCLE if (oldValue == Visibility.Collapsed && newValue == Visibility.Visible) { SetUpdateTemplate(); } - } - - public void UpdateContentTemplateRoot() - { - if (Visibility == Visibility.Collapsed) - { - return; - } - - //If ContentTemplateRoot is null, it must be updated even if the templates haven't changed - if (ContentTemplateRoot == null) - { - _dataTemplateUsedLastUpdate = null; - } - - //ContentTemplate/ContentTemplateSelector will only be applied to a control with no Template, normally the innermost element - var dataTemplate = this.ResolveContentTemplate(); - - //Only apply template if it has changed - if (!object.Equals(dataTemplate, _dataTemplateUsedLastUpdate)) - { - _dataTemplateUsedLastUpdate = dataTemplate; - ContentTemplateRoot = dataTemplate?.LoadContentCached(this) ?? Content as View; - if (ContentTemplateRoot != null) - { - IsUsingDefaultTemplate = false; - } - } - - if (Content != null - && !(Content is View) - && ContentTemplateRoot == null - ) - { - // Use basic default root for non-View Content if no template is supplied - SetContentTemplateRootToPlaceholder(); - } - - if (ContentTemplateRoot == null && Content is View contentView && dataTemplate == null) - { - // No template and Content is a View, set it directly as root - ContentTemplateRoot = contentView as View; - } - - IsUsingDefaultTemplate = ContentTemplateRoot is ImplicitTextBlock; - } - - private void SetContentTemplateRootToPlaceholder() - { - if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) - { - this.Log().DebugFormat("No ContentTemplate was specified for {0} and content is not a UIView, defaulting to TextBlock.", GetType().Name); - } - - var textBlock = new ImplicitTextBlock(this); - textBlock.SetTemplatedParent(this); - - if (!IsNativeHost) - { - TemplateBind(TextBlock.TextProperty, nameof(Content)); - TemplateBind(TextBlock.HorizontalAlignmentProperty, nameof(HorizontalContentAlignment)); - TemplateBind(TextBlock.VerticalAlignmentProperty, nameof(VerticalContentAlignment)); - TemplateBind(TextBlock.TextWrappingProperty, nameof(TextWrapping)); - TemplateBind(TextBlock.MaxLinesProperty, nameof(MaxLines)); - TemplateBind(TextBlock.TextAlignmentProperty, nameof(TextAlignment)); - - void TemplateBind(DependencyProperty property, string path) => - textBlock.SetBinding(property, new Binding(path) - { - RelativeSource = RelativeSource.TemplatedParent - }); - } - - ContentTemplateRoot = textBlock; - IsUsingDefaultTemplate = true; +#endif } private bool _isBoundImplicitlyToContent; @@ -1333,14 +1126,6 @@ protected override Size MeasureOverride(Size size) private void UpdateBorder() => _borderRenderer.Update(); #endif - private void SetUpdateTemplate() - { - UpdateContentTemplateRoot(); - SetUpdateTemplatePartial(); - } - - partial void SetUpdateTemplatePartial(); - internal string GetTextBlockText() { if (IsUsingDefaultTemplate && ContentTemplateRoot is ImplicitTextBlock tb) diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.lifecycle.cs b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.lifecycle.cs new file mode 100644 index 000000000000..4cec8b276788 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.lifecycle.cs @@ -0,0 +1,310 @@ +#if UNO_HAS_ENHANCED_LIFECYCLE +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media; +using Uno.UI.Xaml; + +namespace Microsoft.UI.Xaml.Controls; + +partial class ContentPresenter +{ + private bool _inOnApplyTemplate; + private bool _dataContextInvalid = true; + private const FrameworkPropertyMetadataOptions ContentPropertyOptions = FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext | FrameworkPropertyMetadataOptions.AffectsMeasure; + + public FrameworkTemplate SelectedContentTemplate + { + get => (FrameworkTemplate)GetValue(SelectedContentTemplateProperty); + set => SetValue(SelectedContentTemplateProperty, value); + } + + public static DependencyProperty SelectedContentTemplateProperty { get; } = DependencyProperty.Register( + nameof(SelectedContentTemplate), + typeof(FrameworkTemplate), + typeof(ContentPresenter), + new FrameworkPropertyMetadata(null, OnSelectedContentTemplateChanged)); + + private static void OnSelectedContentTemplateChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) + { + var @this = (ContentPresenter)dependencyObject; + if (@this.ContentTemplate is null) + { + @this.Invalidate(true); + } + } + + private protected override FrameworkTemplate GetTemplate() => (ContentTemplate ?? SelectedContentTemplate) ?? GetDefaultContentPresenterTemplate(); + + private static DataTemplate _defaultContentPresenterTemplate; + + private static DataTemplate GetDefaultContentPresenterTemplate() + { + return _defaultContentPresenterTemplate ??= new DisplayMemberTemplate(); + } + + private protected override void ApplyTemplate(out bool addedVisuals) + { + addedVisuals = false; + + if (_inOnApplyTemplate) + { + base.ApplyTemplate(out addedVisuals); + _dataTemplateUsedLastUpdate = GetTemplate(); + + goto Cleanup; + } + + var store = ((IDependencyObjectStoreProvider)this).Store; + + // Applying the template will not delete existing visuals. This will be done conditionally + // when the template is invalidated. + if (!HasTemplateChild()) + { + var templatedParent = GetTemplatedParent(); + if (templatedParent is not null) + { + bool needsRefresh = false; + DependencyProperty dpTarget = ContentPresenter.SelectedContentTemplateProperty; + if (store.GetCurrentHighestValuePrecedence(dpTarget) == DependencyPropertyValuePrecedences.DefaultValue && !store.IsPropertyTemplateBound(dpTarget)) + { + DependencyProperty dpSource = ContentControl.SelectedContentTemplateProperty; + store.SetTemplateBinding(dpTarget, dpSource); + needsRefresh = true; + } + + dpTarget = ContentPresenter.ContentTemplateProperty; + if (store.GetCurrentHighestValuePrecedence(dpTarget) == DependencyPropertyValuePrecedences.DefaultValue && !store.IsPropertyTemplateBound(dpTarget)) + { + DependencyProperty dpSource = ContentControl.ContentTemplateProperty; + store.SetTemplateBinding(dpTarget, dpSource); + needsRefresh = true; + } + + dpTarget = ContentPresenter.ContentProperty; + if (store.GetCurrentHighestValuePrecedence(dpTarget) == DependencyPropertyValuePrecedences.DefaultValue && !store.IsPropertyTemplateBound(dpTarget)) + { + DependencyProperty dpSource = ContentControl.ContentProperty; + store.SetTemplateBinding(dpTarget, dpSource); + needsRefresh = true; + } + + // Setting up the binding doesn't get you the values. We need to call refresh to get the latest value + // for m_pContentTemplate, SelectedContentTemplate and/or m_pContent for the tests below. + if (needsRefresh) + { + //templatedParent.RefreshTemplateBindings(TemplateBindingsRefreshType.All); + } + } + + _inOnApplyTemplate = true; + TrySetDataContextFromContent(Content); + + if (ContentTemplate is not null || SelectedContentTemplate is not null) + { + // Expand the template. + base.ApplyTemplate(out addedVisuals); + } + // if ContentTemplate is empty control template + // we don't want ContentPresenter to create visuals + else if (Content is { } content) + { + if (content is UIElement ui) + { + AddChild(ui); + } + else + { + TextBlock textBlockChildOfDefaultTemplate; + base.ApplyTemplate(out addedVisuals); + + // We have a default(secret) Data template for ContentPresenter that should have its TextBlock present in all the UIA views by default. + // But at the same time we want to mitigate this behavior specifically for Controls like Button where the TextBlock would represent redundant data. + // At the same time we want to provide a mechanism for other controls if they want to opt-in this behavior. So if any control doesn't want these + // secret TextBlocks to be present in a certain view they can set AutomationProperties.AccessibilityView="Raw" on the corresponding ContentPresenter, + // we here exceptionally make sure the set property gets reflected on the secret TextBlock if the default template is getting used. + var value = AutomationProperties.GetAccessibilityView(this); + + textBlockChildOfDefaultTemplate = GetTextBlockChildOfDefaultTemplate(allowNullContent: false); + if (textBlockChildOfDefaultTemplate is not null) + { + if (value != AccessibilityView.Content) + { + AutomationProperties.SetAccessibilityView(textBlockChildOfDefaultTemplate, value); + } + //if (store.GetCurrentHighestValuePrecedence(ContentPresenter.OpticalMarginAlignmentProperty) == DependencyPropertyValuePrecedences.Local) + //{ + // var tempValue = OpticalMarginAlignment; ; + // if (tempValue != OpticalMarginAlignment.None) + // { + // textBlockChildOfDefaultTemplate.OpticalMarginAlignment = tempValue; + // } + //} + //if (store.GetCurrentHighestValuePrecedence(ContentPresenter.TextLineBoundsProperty) == DependencyPropertyValuePrecedences.Local) + //{ + // var tempValue = TextLineBounds; + // if (tempValue != TextLineBounds.Full) + // { + // textBlockChildOfDefaultTemplate.TextLineBounds = tempValue; + // } + //} + if (store.GetCurrentHighestValuePrecedence(ContentPresenter.TextWrappingProperty) == DependencyPropertyValuePrecedences.Local) + { + var tempValue = TextWrapping; + if (tempValue != TextWrapping.NoWrap) + { + textBlockChildOfDefaultTemplate.TextWrapping = tempValue; + } + } + //if (store.GetCurrentHighestValuePrecedence(ContentPresenter.LineStackingStrategyProperty) == DependencyPropertyValuePrecedences.Local) + //{ + // var tempValue = LineStackingStrategy; + // if (tempValue != LineStackingStrategy.MaxHeight) + // { + // textBlockChildOfDefaultTemplate.LineStackingStrategy = tempValue; + // } + //} + if (store.GetCurrentHighestValuePrecedence(ContentPresenter.MaxLinesProperty) == DependencyPropertyValuePrecedences.Local) + { + var tempValue = MaxLines; + if (tempValue != 0) + { + textBlockChildOfDefaultTemplate.MaxLines = tempValue; + } + } + //if (store.GetCurrentHighestValuePrecedence(ContentPresenter.LineHeightProperty) == DependencyPropertyValuePrecedences.Local) + //{ + // var tempValue = LineHeight; + // if (tempValue > 0) + // { + // textBlockChildOfDefaultTemplate.LineHeight = tempValue; + // } + //} + } + } + } + + addedVisuals = HasTemplateChild(); + _dataTemplateUsedLastUpdate = GetTemplate(); + } + else if (_dataContextInvalid) + { + TrySetDataContextFromContent(Content); + } + + Cleanup: + // Uno-specific + ContentTemplateRoot = VisualTreeHelper.GetChild(this, 0) as UIElement; + _dataContextInvalid = false; + _inOnApplyTemplate = false; + } + + // Fetches the child TextBlock of the default template if we are using the default template; null otherwise. + private TextBlock GetTextBlockChildOfDefaultTemplate(bool allowNullContent) + { + var content = Content; + // Make sure we are indeed using the default template (i.e. content is non-null and is not a UIElement). + if (allowNullContent || (content is not null && content is not UIElement)) + { + var children = this.GetChildren(); + if (children is { Count: >= 1 }) + { + var child = children[0]; + if (child is not null) + { + // The TextBlock can now be the first child of the ContentPresenter + if (child is TextBlock childTb) + { + return childTb; + } + else + { + // Old template with the Grid + children = child.GetChildren(); + if (children is { Count: 1 }) + { + child = children[0]; + if (child is TextBlock childTb2) + { + return childTb2; + } + } + } + } + } + } + + return null; + } + + + private void Invalidate(bool clearChildren) + { + if (clearChildren) + { + ClearChildren(); + + IsUsingDefaultTemplate = false; + } + else + { + _dataContextInvalid = true; + } + + InvalidateMeasure(); + } + + internal override void EnterImpl(EnterParams @params, int depth) + { + base.EnterImpl(@params, depth); + +#if !UNO_HAS_BORDER_VISUAL + UpdateBorder(); +#endif + // We do this in Enter not Loaded since Loaded is a lot more tricky + // (e.g. you can have Unloaded without Loaded, you can have multiple loaded events without unloaded in between, etc.) + if (IsNativeHost) + { + AttachNativeElement(); + } + } + + internal override void LeaveImpl(LeaveParams @params) + { + base.LeaveImpl(@params); + + if (IsNativeHost) + { + DetachNativeElement(Content); + } + } + + public void UpdateContentTemplateRoot() + { + } + + internal TextBlock CreateDefaultContent() + { + var textBlock = new TextBlock(); + // Act as if the TextBlock was the result of a template expansion + textBlock.SetTemplatedParent(this); + textBlock.HorizontalAlignment = HorizontalAlignment.Left; + textBlock.VerticalAlignment = VerticalAlignment.Top; + BindDefaultTextBlock(textBlock); + + // Uno-specific + ContentTemplateRoot = textBlock; + + IsUsingDefaultTemplate = true; + + return textBlock; + } + + private void BindDefaultTextBlock(TextBlock textBlock) + { + var binding = new Binding(); + binding.Mode = BindingMode.OneWay; + textBlock.SetBinding(TextBlock.TextProperty, binding); + } +} +#endif diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.nonlifecycle.cs b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.nonlifecycle.cs new file mode 100644 index 000000000000..1ea5155d9b81 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.nonlifecycle.cs @@ -0,0 +1,215 @@ +#if !UNO_HAS_ENHANCED_LIFECYCLE +namespace Microsoft.UI.Xaml.Controls; + +partial class ContentPresenter : IFrameworkTemplatePoolAware +{ + private bool _firstLoadResetDone; + private bool _appliedTemplate; + private const FrameworkPropertyMetadataOptions ContentPropertyOptions = FrameworkPropertyMetadataOptions.AffectsMeasure; + + void IFrameworkTemplatePoolAware.OnTemplateRecycled() + { + // This needs to be cleared on recycle, to prevent + // SetUpdateTemplate from being skipped in OnLoaded. + _firstLoadResetDone = false; + } + + internal DataTemplate SelectedContentTemplate => _dataTemplateUsedLastUpdate; + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + // Applying the template will not delete existing visuals. This will be done conditionally + // when the template is invalidated. + // Uno specific: since we don't call this early enough, we have to comment out the condition + // if (GetChildren().Count == 0) + { + ContentControl pTemplatedParent = GetTemplatedParent() as ContentControl; + + // Only ContentControl has the two properties below. Other parents would just fail to bind since they don't have these + // two content related properties. + if (pTemplatedParent != null +#if ANDROID || __IOS__ + && this is not NativeCommandBarPresenter // Uno specific: NativeCommandBarPresenter breaks if you inherit from the TP +#endif + ) + { + // bool needsRefresh = false; + DependencyProperty pdpTarget; + + // By default Content and ContentTemplate are template are bound. + // If no template binding exists already then hook them up now + // pdpTarget = GetPropertyByIndexInline(KnownPropertyIndex::ContentPresenter_SelectedContentTemplate); + // IFCEXPECT(pdpTarget); + // if (IsPropertyDefault(pdpTarget) && !IsPropertyTemplateBound(pdpTarget)) + // { + // const CDependencyProperty* pdpSource = pTemplatedParent->GetPropertyByIndexInline(KnownPropertyIndex::ContentControl_SelectedContentTemplate); + // IFCEXPECT(pdpSource); + // + // IFC(SetTemplateBinding(pdpTarget, pdpSource)); + // needsRefresh = true; + // } + + // UNO Specific: SelectedContentTemplate is not implemented, we hook ContentTemplateSelector instead + pdpTarget = ContentPresenter.ContentTemplateSelectorProperty; + global::System.Diagnostics.Debug.Assert(pdpTarget is { }); + var store = ((IDependencyObjectStoreProvider)this).Store; + if (store.GetCurrentHighestValuePrecedence(pdpTarget) == DependencyPropertyValuePrecedences.DefaultValue && + !store.IsPropertyTemplateBound(pdpTarget)) + { + DependencyProperty pdpSource = ContentControl.ContentTemplateSelectorProperty; + global::System.Diagnostics.Debug.Assert(pdpSource is { }); + + store.SetTemplateBinding(pdpTarget, pdpSource); + // needsRefresh = true; + } + + pdpTarget = ContentPresenter.ContentTemplateProperty; + global::System.Diagnostics.Debug.Assert(pdpTarget is { }); + if (store.GetCurrentHighestValuePrecedence(pdpTarget) == DependencyPropertyValuePrecedences.DefaultValue && + !store.IsPropertyTemplateBound(pdpTarget)) + { + DependencyProperty pdpSource = ContentControl.ContentTemplateProperty; + global::System.Diagnostics.Debug.Assert(pdpSource is { }); + + store.SetTemplateBinding(pdpTarget, pdpSource); + // needsRefresh = true; + } + + pdpTarget = ContentPresenter.ContentProperty; + global::System.Diagnostics.Debug.Assert(pdpTarget is { }); + if (store.GetCurrentHighestValuePrecedence(pdpTarget) == DependencyPropertyValuePrecedences.DefaultValue && + !store.IsPropertyTemplateBound(pdpTarget)) + { + DependencyProperty pdpSource = ContentControl.ContentProperty; + global::System.Diagnostics.Debug.Assert(pdpSource is { }); + + store.SetTemplateBinding(pdpTarget, pdpSource); + // needsRefresh = true; + } + + // Uno specific: uno bindings don't work this way + // Setting up the binding doesn't get you the values. We need to call refresh to get the latest value + // for m_pContentTemplate, SelectedContentTemplate and/or m_pContent for the tests below. + // if (needsRefresh) + // { + // IFC(pTemplatedParent->RefreshTemplateBindings(TemplateBindingsRefreshType::All)); + // } + } + } + } + + private bool ResetDataContextOnFirstLoad() + { + if (!_firstLoadResetDone) + { + _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; + } + + return false; + } + + private void SetUpdateTemplate() + { + UpdateContentTemplateRoot(); + SetUpdateTemplatePartial(); + } + + partial void SetUpdateTemplatePartial(); + + public void UpdateContentTemplateRoot() + { + if (Visibility == Visibility.Collapsed) + { + return; + } + + //If ContentTemplateRoot is null, it must be updated even if the templates haven't changed + if (ContentTemplateRoot == null) + { + _dataTemplateUsedLastUpdate = null; + } + + //ContentTemplate/ContentTemplateSelector will only be applied to a control with no Template, normally the innermost element + var dataTemplate = this.ResolveContentTemplate(); + + //Only apply template if it has changed + if (!object.Equals(dataTemplate, _dataTemplateUsedLastUpdate)) + { + _dataTemplateUsedLastUpdate = dataTemplate; + ContentTemplateRoot = dataTemplate?.LoadContentCached() ?? Content as View; + if (ContentTemplateRoot != null) + { + IsUsingDefaultTemplate = false; + } + } + + if (Content != null + && !(Content is View) + && ContentTemplateRoot == null + ) + { + // Use basic default root for non-View Content if no template is supplied + SetContentTemplateRootToPlaceholder(); + } + + if (ContentTemplateRoot == null && Content is View contentView && dataTemplate == null) + { + // No template and Content is a View, set it directly as root + ContentTemplateRoot = contentView as View; + } + + IsUsingDefaultTemplate = ContentTemplateRoot is ImplicitTextBlock; + } + + private void SetContentTemplateRootToPlaceholder() + { + if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) + { + this.Log().DebugFormat("No ContentTemplate was specified for {0} and content is not a UIView, defaulting to TextBlock.", GetType().Name); + } + + var textBlock = new ImplicitTextBlock(this); + + void setBinding(DependencyProperty property, string path) + => textBlock.SetBinding( + property, + new Binding + { + Path = new PropertyPath(path), + Source = this, + Mode = BindingMode.OneWay + } + ); + + if (!IsNativeHost) + { + setBinding(TextBlock.TextProperty, nameof(Content)); + setBinding(TextBlock.HorizontalAlignmentProperty, nameof(HorizontalContentAlignment)); + setBinding(TextBlock.VerticalAlignmentProperty, nameof(VerticalContentAlignment)); + setBinding(TextBlock.TextWrappingProperty, nameof(TextWrapping)); + setBinding(TextBlock.MaxLinesProperty, nameof(MaxLines)); + setBinding(TextBlock.TextAlignmentProperty, nameof(TextAlignment)); + } + + ContentTemplateRoot = textBlock; + IsUsingDefaultTemplate = true; + } +} +#endif diff --git a/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs b/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs index af4264ee7983..792d92c18291 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs @@ -200,7 +200,7 @@ public ControlTemplate Template typeof(Control), new FrameworkPropertyMetadata( null, - FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext, // WinUI also has AffectsMeasure here, but we only do this conditionally in SetUpdateControlTemplate. + FrameworkPropertyMetadataOptions.ValueDoesNotInheritDataContext | FrameworkPropertyMetadataOptions.AffectsMeasure, (s, e) => ((Control)s)?.OnTemplateChanged(e))); private protected virtual void OnTemplateChanged(DependencyPropertyChangedEventArgs e) diff --git a/src/Uno.UI/UI/Xaml/Controls/GridView/GridView.cs b/src/Uno.UI/UI/Xaml/Controls/GridView/GridView.cs index 0b3b871a3f06..c3eee5a9245a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/GridView/GridView.cs +++ b/src/Uno.UI/UI/Xaml/Controls/GridView/GridView.cs @@ -21,9 +21,11 @@ protected override DependencyObject GetContainerForItemOverride() return new GridViewItem() { IsGeneratedContainer = true }; } +#if __ANDROID__ || __IOS__ internal override ContentControl GetGroupHeaderContainer(object groupHeader) { return new GridViewHeaderItem() { IsGeneratedContainer = true }; } +#endif } } diff --git a/src/Uno.UI/UI/Xaml/Controls/ItemsControl/ItemsControl.cs b/src/Uno.UI/UI/Xaml/Controls/ItemsControl/ItemsControl.cs index 0ecdb0577cd5..ed07dae43f04 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ItemsControl/ItemsControl.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ItemsControl/ItemsControl.cs @@ -1628,6 +1628,7 @@ internal bool IsIndexItsOwnContainer(int index) return IsItemItsOwnContainer(ItemFromIndex(index)); } +#if __ANDROID__ || __IOS__ /// /// Return control which acts as container for group header for this ItemsControl subtype. /// @@ -1635,6 +1636,7 @@ internal virtual ContentControl GetGroupHeaderContainer(object groupHeader) { return ContentControl.CreateItemContainer(); } +#endif public static ItemsControl ItemsControlFromItemContainer(DependencyObject container) { diff --git a/src/Uno.UI/UI/Xaml/Controls/ListView/ListView.cs b/src/Uno.UI/UI/Xaml/Controls/ListView/ListView.cs index 6bc522e1c588..cbf5c7b8e4ee 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ListView/ListView.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ListView/ListView.cs @@ -21,9 +21,11 @@ protected override DependencyObject GetContainerForItemOverride() return new ListViewItem() { IsGeneratedContainer = true }; } +#if __ANDROID__ || __IOS__ internal override ContentControl GetGroupHeaderContainer(object groupHeader) { return new ListViewHeaderItem() { IsGeneratedContainer = true }; } +#endif } } diff --git a/src/Uno.UI/UI/Xaml/Controls/Primitives/ButtonBase/ButtonBase.cs b/src/Uno.UI/UI/Xaml/Controls/Primitives/ButtonBase/ButtonBase.cs index 9ef64b7bb57f..c1b19ebc87ab 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Primitives/ButtonBase/ButtonBase.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Primitives/ButtonBase/ButtonBase.cs @@ -159,8 +159,9 @@ private protected override object CoerceIsEnabled(object baseValue, DependencyPr return base.CoerceIsEnabled(baseValue, precedence); } +#if __ANDROID__ || __IOS__ private protected override void OnContentTemplateRootSet() => RegisterEvents(); - +#endif // Might be changed if the method does not conflict in UnoViewGroup. internal override bool IsViewHit() diff --git a/src/Uno.UI/UI/Xaml/Controls/UserControl/UserControl.cs b/src/Uno.UI/UI/Xaml/Controls/UserControl/UserControl.cs index 16115a53a482..e434be7bc275 100644 --- a/src/Uno.UI/UI/Xaml/Controls/UserControl/UserControl.cs +++ b/src/Uno.UI/UI/Xaml/Controls/UserControl/UserControl.cs @@ -12,5 +12,22 @@ public UserControl() // This mimics UWP private protected override Type GetDefaultStyleKey() => null; + +#if UNO_HAS_ENHANCED_LIFECYCLE + protected override void OnContentChanged(object oldContent, object newContent) + { + // NOTE: In WinUI, this logic is in CUserControl::SetContent (which is in Control.cpp - Yes, not UserControl_Partial.cpp) + // In Uno, we incorrectly inherit from ContentControl, so we override OnContentChange + if (oldContent is UIElement oldUIElement) + { + RemoveChild(oldUIElement); + } + + if (newContent is UIElement newUIElement) + { + AddChild(newUIElement); + } + } +#endif } } diff --git a/src/Uno.UI/UI/Xaml/DisplayMemberTemplate.cs b/src/Uno.UI/UI/Xaml/DisplayMemberTemplate.cs new file mode 100644 index 000000000000..f058cc79e06d --- /dev/null +++ b/src/Uno.UI/UI/Xaml/DisplayMemberTemplate.cs @@ -0,0 +1,21 @@ +#nullable enable + +using Microsoft.UI.Xaml.Controls; + +namespace Microsoft.UI.Xaml; + +internal partial class DisplayMemberTemplate : DataTemplate +{ + internal override UIElement? LoadContent(FrameworkElement templatedParent) + { + if (templatedParent is ContentPresenter cp) + { + return cp.CreateDefaultContent(); + } + else + { + var template = ContentControl.CreateDefaultTemplate(templatedParent); + return template.LoadContent(templatedParent); + } + } +} diff --git a/src/Uno.UI/UI/Xaml/FrameworkTemplate.cs b/src/Uno.UI/UI/Xaml/FrameworkTemplate.cs index 93caf872e476..41a1325d0255 100644 --- a/src/Uno.UI/UI/Xaml/FrameworkTemplate.cs +++ b/src/Uno.UI/UI/Xaml/FrameworkTemplate.cs @@ -152,6 +152,9 @@ private FrameworkTemplate(object? owner, NewFrameworkTemplateBuilder? factory, D } } + internal virtual View? LoadContent(FrameworkElement templatedParent) + => LoadContentCachedCore(templatedParent); + public override bool Equals(object? obj) { var other = obj as FrameworkTemplate;