diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/StyleFlowToPopup.xaml b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/StyleFlowToPopup.xaml new file mode 100644 index 000000000000..7ce45c2e4ff9 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/StyleFlowToPopup.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/StyleFlowToPopup.xaml.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/StyleFlowToPopup.xaml.cs new file mode 100644 index 000000000000..785a59c2e583 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Controls/StyleFlowToPopup.xaml.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using Windows.Foundation; +using Windows.Foundation.Collections; + +namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml.Controls; + +public sealed partial class StyleFlowToPopup : Page +{ + public StyleFlowToPopup() + { + this.InitializeComponent(); + } + + public void ShowPopup() => TestPopup.IsOpen = true; + + public TextBlock GridTextBlock => InnerTextBlock; + + public TextBlock PopupTextBlock => PopupContentTextBlock; +} diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_Style.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_Style.cs index 11747ce677ae..c2ff6f954032 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_Style.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_Style.cs @@ -1,17 +1,15 @@ using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; -using Uno.UI.Extensions; +using Microsoft.UI; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Markup; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.UI.Xaml.Media; using Private.Infrastructure; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Data; -using Uno.UI.RuntimeTests.Helpers; using SamplesApp.UITests; -using Microsoft.UI.Xaml.Media; +using Uno.UI.RuntimeTests.Helpers; +using Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml.Controls; namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml { @@ -88,5 +86,24 @@ public async Task When_ImplicitStyle() Assert.AreEqual(HorizontalAlignment.Left, cc.HorizontalContentAlignment); } + + [TestMethod] + [RunsOnUIThread] + public async Task When_Style_Flows_To_Popup() + { + var page = new StyleFlowToPopup(); + TestServices.WindowHelper.WindowContent = page; + await UITestHelper.Load(page); + + var foreground = (SolidColorBrush)page.GridTextBlock.Foreground; + Assert.AreEqual(Microsoft.UI.Colors.Red, foreground.Color); + + page.ShowPopup(); + + await TestServices.WindowHelper.WaitFor(() => VisualTreeHelper.GetOpenPopupsForXamlRoot(TestServices.WindowHelper.XamlRoot).Count > 0); + + var popupForeground = (SolidColorBrush)page.PopupTextBlock.Foreground; + Assert.AreEqual(Microsoft.UI.Colors.Red, popupForeground.Color); + } } } diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls_Primitives/Given_Popup.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls_Primitives/Given_Popup.cs index 313c35c5c8cd..afbe8bcd388e 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls_Primitives/Given_Popup.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls_Primitives/Given_Popup.cs @@ -78,29 +78,10 @@ public void When_Closed_Immediately() } [TestMethod] - public async Task When_Child_Visual_Parents_Does_Not_Include_Popup() + public async Task When_Child_Visual_Parents_Do_Not_Include_Popup() { - var popup = new Popup(); - popup.XamlRoot = TestServices.WindowHelper.XamlRoot; - var button = new Button() - { - Content = "test" - }; - popup.Child = button; - popup.IsOpen = true; - await WindowHelper.WaitForLoaded(button); - bool found = false; - DependencyObject current = popup.Child; - while (current != null) - { - if (current == popup) - { - found = true; - break; - } - - current = VisualTreeHelper.GetParent(current); - } + var popup = await LoadAndOpenPopupWithButtonAsync(); + bool found = SearchPopupChildAscendants(popup, element => element == popup, element => VisualTreeHelper.GetParent(element)); Assert.IsFalse(found); @@ -110,6 +91,70 @@ public async Task When_Child_Visual_Parents_Does_Not_Include_Popup() [TestMethod] public async Task When_Child_Logical_Parents_Include_Popup() + { + var popup = await LoadAndOpenPopupWithButtonAsync(); + bool found = SearchPopupChildAscendants(popup, element => element == popup, element => (element as FrameworkElement)?.Parent); + + Assert.IsTrue(found); + + // Should not throw + popup.IsOpen = false; + } + + [TestMethod] + public async Task When_Child_Visual_Parent_Is_Canvas() + { + var popup = await LoadAndOpenPopupWithButtonAsync(); + var child = (FrameworkElement)popup.Child; + var parent = VisualTreeHelper.GetParent(child); + Assert.IsInstanceOfType(parent, typeof(Canvas)); +#if HAS_UNO // It is actually a PopupRoot, but it is internal in WinUI + Assert.IsInstanceOfType(parent, typeof(PopupRoot)); +#endif + + // Should not throw + popup.IsOpen = false; + } + + [TestMethod] + public async Task When_Child_Logical_Parent_Is_Popup() + { + var popup = await LoadAndOpenPopupWithButtonAsync(); + var child = (FrameworkElement)popup.Child; + + Assert.AreEqual(popup, child.Parent); + + // Should not throw + popup.IsOpen = false; + } + +#if HAS_UNO // PopupPanel is Uno-specific + [TestMethod] + public async Task When_Child_Visual_Parents_Do_Not_Include_PopupPanel() + { + var popup = await LoadAndOpenPopupWithButtonAsync(); + bool found = SearchPopupChildAscendants(popup, element => element is PopupPanel, VisualTreeHelper.GetParent); + + Assert.IsFalse(found); + + // Should not throw + popup.IsOpen = false; + } + + [TestMethod] + public async Task When_Child_Logical_Parents_Do_Not_Include_PopupPanel() + { + var popup = await LoadAndOpenPopupWithButtonAsync(); + bool found = SearchPopupChildAscendants(popup, element => element is PopupPanel, element => (element as FrameworkElement)?.Parent); + + Assert.IsFalse(found); + + // Should not throw + popup.IsOpen = false; + } +#endif + + private async Task LoadAndOpenPopupWithButtonAsync() { var popup = new Popup(); popup.XamlRoot = TestServices.WindowHelper.XamlRoot; @@ -120,23 +165,23 @@ public async Task When_Child_Logical_Parents_Include_Popup() popup.Child = button; popup.IsOpen = true; await WindowHelper.WaitForLoaded(button); - bool found = false; - DependencyObject current = button; + return popup; + } + + private bool SearchPopupChildAscendants(Popup popup, Predicate predicate, Func getParent) + { + DependencyObject current = popup.Child; while (current != null) { - if (current == popup) + if (predicate(current)) { - found = true; - break; + return true; } - current = (current as FrameworkElement)?.Parent; + current = getParent(current); } - Assert.IsTrue(found); - - // Should not throw - popup.IsOpen = false; + return false; } [TestMethod] diff --git a/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.Base.cs b/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.Base.cs index aeb6cd78ee3c..ca5d31241540 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.Base.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.Base.cs @@ -138,6 +138,15 @@ partial void OnIsOpenChangedPartial(bool oldIsOpen, bool newIsOpen) partial void OnChildChangedPartial(UIElement oldChild, UIElement newChild) { + if (oldChild is FrameworkElement oldChildFe && oldChildFe.LogicalParentOverride == this) + { + oldChildFe.SetLogicalParent(null); + } + if (newChild is FrameworkElement newChildFe) + { + newChildFe.SetLogicalParent(this); + } + if (oldChild is IDependencyObjectStoreProvider provider && provider.Store.ReadLocalValue(provider.Store.DataContextProperty) != DependencyProperty.UnsetValue) { diff --git a/src/Uno.UI/UI/Xaml/Controls/Popup/PopupPanel.cs b/src/Uno.UI/UI/Xaml/Controls/Popup/PopupPanel.cs index 08c78e23372b..5fd44c548318 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Popup/PopupPanel.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Popup/PopupPanel.cs @@ -231,9 +231,6 @@ Popup.PlacementTarget is not null private protected override void OnLoaded() { base.OnLoaded(); - // Set Parent to the Popup, to obtain the same behavior as UWP that the Popup (and therefore the rest of the main visual tree) - // is reachable by scaling the combined Parent/GetVisualParent() hierarchy. - this.SetLogicalParent(Popup); this.XamlRoot.Changed += XamlRootChanged; } @@ -241,7 +238,6 @@ private protected override void OnLoaded() private protected override void OnUnloaded() { base.OnUnloaded(); - this.SetLogicalParent(null); if (XamlRoot is { } xamlRoot) { diff --git a/src/Uno.UI/UI/Xaml/Controls/Popup/PopupRoot.cs b/src/Uno.UI/UI/Xaml/Controls/Popup/PopupRoot.cs index 83f7fc9d572e..f0fabfeb3c9c 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Popup/PopupRoot.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Popup/PopupRoot.cs @@ -17,7 +17,7 @@ namespace Microsoft.UI.Xaml.Controls.Primitives; -internal partial class PopupRoot : Panel +internal partial class PopupRoot : Canvas { private readonly List _openPopups = new(); diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 54e44e504678..ab4423b7eabc 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -19,6 +19,7 @@ using Windows.ApplicationModel.Calls; using Microsoft.UI.Xaml.Controls; using System.Diagnostics.CodeAnalysis; +using Microsoft.UI.Xaml.Media; @@ -1637,9 +1638,13 @@ internal IEnumerable GetResourceDictionaries( { yield return fe.Resources; } - } - candidate = candidate.GetParent() as DependencyObject; + candidate = fe.Parent as FrameworkElement; + } + else + { + candidate = VisualTreeHelper.GetParent(candidate) as DependencyObject; + } } if (includeAppResources && Application.Current != null) diff --git a/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs b/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs index 7fabfc05fa15..4c73029f4e64 100644 --- a/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs +++ b/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs @@ -270,6 +270,12 @@ private static IReadOnlyList GetOpenPopups(VisualTree visualTree) return uiElement.GetVisualTreeParent() as DependencyObject; } + if (realParent is PopupPanel) + { + // Skip the popup panel and go to PopupRoot instead. + realParent = GetParent(realParent); + } + return realParent; }