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;
}