Skip to content

Commit fc15fff

Browse files
authored
Merge pull request #8185 from MartinZikmund/dev/mazi/improve-parent-walk-listview
fix: Always set `ItemsControlFromItemContainer`, `VisualTreeHelper.GetParent` must be able to reach `ItemsControl`
2 parents 4b9321b + c1e0f70 commit fc15fff

File tree

15 files changed

+298
-34
lines changed

15 files changed

+298
-34
lines changed

src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ListViewBase.cs

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Collections.Generic;
33
using System.Collections.ObjectModel;
44
using System.Linq;
5-
using System.Text;
65
using System.Threading.Tasks;
76
using Microsoft.VisualStudio.TestTools.UnitTesting;
87
using Private.Infrastructure;
@@ -212,6 +211,41 @@ public async Task When_IsItsOwnItemContainer_FromSource()
212211
Assert.AreEqual("item 1", si.Content);
213212
}
214213

214+
[TestMethod]
215+
[RunsOnUIThread]
216+
public async Task When_Item_Parents_Include_ListView()
217+
{
218+
var SUT = new ListView()
219+
{
220+
ItemContainerStyle = BasicContainerStyle,
221+
SelectionMode = ListViewSelectionMode.Single,
222+
};
223+
224+
WindowHelper.WindowContent = SUT;
225+
await WindowHelper.WaitForIdle();
226+
227+
var source = new[] {
228+
new ListViewItem(){ Content = "item 1" },
229+
new ListViewItem(){ Content = "item 2" },
230+
new ListViewItem(){ Content = "item 3" },
231+
new ListViewItem(){ Content = "item 4" },
232+
};
233+
234+
SUT.ItemsSource = source;
235+
236+
SelectorItem si = null;
237+
await WindowHelper.WaitFor(() => (si = SUT.ContainerFromItem(source[0]) as SelectorItem) != null);
238+
239+
Assert.IsNull(si.Parent);
240+
var parent = VisualTreeHelper.GetParent(si);
241+
while (parent is not null && parent is not ListView listView)
242+
{
243+
parent = VisualTreeHelper.GetParent(parent);
244+
}
245+
246+
Assert.IsInstanceOfType(parent, typeof(ListView));
247+
}
248+
215249

216250
[TestMethod]
217251
[RunsOnUIThread]
@@ -718,6 +752,105 @@ public async Task When_Items_Their_Own_Container()
718752
Assert.AreEqual(items[1], list.ItemFromContainer(items[1]));
719753
}
720754

755+
#if HAS_UNO
756+
[TestMethod]
757+
[RunsOnUIThread]
758+
public async Task When_Own_Container_ContainerFromItem_Owner()
759+
{
760+
var list = new ListView();
761+
var item = new ListViewItem();
762+
var items = new ObservableCollection<ListViewItem>()
763+
{
764+
item,
765+
};
766+
767+
list.ItemsSource = items;
768+
WindowHelper.WindowContent = list;
769+
await WindowHelper.WaitForLoaded(list);
770+
await WindowHelper.WaitFor(() => GetPanelChildren(list).Length == 1);
771+
772+
var container = list.ContainerFromItem(item);
773+
Assert.AreEqual(list, ItemsControl.ItemsControlFromItemContainer(container));
774+
}
775+
776+
[TestMethod]
777+
[RunsOnUIThread]
778+
public async Task When_Own_Container_ContainerFromIndex_Owner()
779+
{
780+
var list = new ListView();
781+
var item = new ListViewItem();
782+
var items = new ObservableCollection<ListViewItem>()
783+
{
784+
item,
785+
};
786+
787+
list.ItemsSource = items;
788+
WindowHelper.WindowContent = list;
789+
await WindowHelper.WaitForLoaded(list);
790+
await WindowHelper.WaitFor(() => GetPanelChildren(list).Length == 1);
791+
792+
var container = list.ContainerFromIndex(0);
793+
Assert.AreEqual(list, ItemsControl.ItemsControlFromItemContainer(container));
794+
}
795+
796+
[TestMethod]
797+
[RunsOnUIThread]
798+
public async Task When_Own_Container_Direct_Owner()
799+
{
800+
var list = new ListView();
801+
var item = new ListViewItem();
802+
var items = new ObservableCollection<ListViewItem>()
803+
{
804+
item,
805+
};
806+
807+
list.ItemsSource = items;
808+
WindowHelper.WindowContent = list;
809+
await WindowHelper.WaitForLoaded(list);
810+
await WindowHelper.WaitFor(() => GetPanelChildren(list).Length == 1);
811+
812+
Assert.AreEqual(list, ItemsControl.ItemsControlFromItemContainer(item));
813+
}
814+
815+
[TestMethod]
816+
[RunsOnUIThread]
817+
public async Task When_Not_Own_Container_ContainerFromItem_Owner()
818+
{
819+
var list = new ListView();
820+
var items = new ObservableCollection<int>()
821+
{
822+
1,
823+
};
824+
825+
list.ItemsSource = items;
826+
WindowHelper.WindowContent = list;
827+
await WindowHelper.WaitForLoaded(list);
828+
await WindowHelper.WaitFor(() => GetPanelChildren(list).Length == 1);
829+
830+
var container = list.ContainerFromItem(items[0]);
831+
Assert.AreEqual(list, ItemsControl.ItemsControlFromItemContainer(container));
832+
}
833+
834+
[TestMethod]
835+
[RunsOnUIThread]
836+
public async Task When_Not_Own_Container_ContainerFromIndex_Owner()
837+
{
838+
var list = new ListView();
839+
var items = new ObservableCollection<int>()
840+
{
841+
1,
842+
};
843+
844+
list.ItemsSource = items;
845+
WindowHelper.WindowContent = list;
846+
await WindowHelper.WaitForLoaded(list);
847+
await WindowHelper.WaitFor(() => GetPanelChildren(list).Length == 1);
848+
849+
var container = list.ContainerFromIndex(0);
850+
Assert.AreEqual(list, ItemsControl.ItemsControlFromItemContainer(container));
851+
}
852+
#endif
853+
721854
[TestMethod]
722855
[RunsOnUIThread]
723856
public async Task When_Items_Their_Own_Container_In_OnItemsChanged_Removal()
@@ -1723,7 +1856,7 @@ public async Task When_List_Given_More_Space()
17231856
ItemsPanel = NoCacheItemsStackPanel,
17241857
ItemTemplate = FixedSizeItemTemplate,
17251858
ItemContainerStyle = NoSpaceContainerStyle,
1726-
ItemsSource = Enumerable.Range(0,10).Select(i => $"Item {i}").ToArray()
1859+
ItemsSource = Enumerable.Range(0, 10).Select(i => $"Item {i}").ToArray()
17271860
};
17281861
var host = new Grid
17291862
{

src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/Given_FocusManager.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,42 @@ public async Task When_Page_Navigates_Back_With_Outer_After()
375375
await AssertNavigationFocusSequence(expectedSequence, navigationAction);
376376
}
377377

378+
[TestMethod]
379+
[RunsOnUIThread]
380+
[RequiresFullWindow]
381+
public async Task When_Page_Navigates_From_Page_ListViewItem()
382+
{
383+
var stackPanel = new StackPanel();
384+
var frame = new Frame();
385+
stackPanel.Children.Add(frame);
386+
stackPanel.Children.Add(new ToggleButton() { Name = "OuterButtonAfter" });
387+
TestServices.WindowHelper.WindowContent = stackPanel;
388+
frame.Navigate(typeof(ListViewItemPage));
389+
var sourcePage = (ListViewItemPage)frame.Content;
390+
await WaitForLoadedEvent(sourcePage);
391+
392+
await TestServices.WindowHelper.WaitFor(() => sourcePage.List.ContainerFromIndex(0) is not null);
393+
var container = sourcePage.List.ContainerFromIndex(0) as ListViewItem;
394+
container.Focus(FocusState.Programmatic);
395+
396+
await TestServices.WindowHelper.WaitForIdle();
397+
398+
var expectedSequence = new string[]
399+
{
400+
"OuterButtonAfter"
401+
};
402+
403+
Func<Task> navigationAction = async () =>
404+
{
405+
frame.Navigate(typeof(TwoButtonSecondPage));
406+
await WaitForLoadedEvent((TwoButtonSecondPage)frame.Content);
407+
408+
await TestServices.WindowHelper.WaitForIdle();
409+
};
410+
411+
await AssertNavigationFocusSequence(expectedSequence, navigationAction);
412+
}
413+
378414
[TestMethod]
379415
[RunsOnUIThread]
380416
[RequiresFullWindow]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<local:FocusNavigationPage
2+
x:Class="Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Input.TestPages.ListViewItemPage"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:local="using:Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Input.TestPages"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
7+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
8+
mc:Ignorable="d"
9+
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
10+
11+
<Grid>
12+
<ListView x:Name="TestListView">
13+
<ListView.ItemTemplate>
14+
<DataTemplate>
15+
<StackPanel>
16+
<Button>Test</Button>
17+
<Button>Test 2</Button>
18+
</StackPanel>
19+
</DataTemplate>
20+
</ListView.ItemTemplate>
21+
</ListView>
22+
</Grid>
23+
</local:FocusNavigationPage>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Threading.Tasks;
2+
using Windows.UI.Xaml.Controls;
3+
4+
namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Input.TestPages
5+
{
6+
public sealed partial class ListViewItemPage : FocusNavigationPage
7+
{
8+
public ListViewItemPage()
9+
{
10+
InitializeComponent();
11+
TestListView.ItemsSource = new int[] { 1, 2, 3 };
12+
}
13+
14+
public ListView List => TestListView;
15+
}
16+
}

src/Uno.UI/Extensions/UIViewExtensions.iOSmacOS.cs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,27 @@
1-
using Uno.Disposables;
2-
using Uno;
1+
using Uno;
32
using Uno.Extensions;
43
using Uno.UI;
54
using System;
65
using System.Collections.Generic;
7-
using System.Drawing;
86
using System.Linq;
97
using System.Text;
10-
using Uno.UI.DataBinding;
118
using Uno.UI.Extensions;
129
using Windows.UI.Xaml;
13-
using System.Diagnostics.CodeAnalysis;
1410
using Uno.Foundation.Logging;
15-
using Windows.UI.Core;
1611
using Uno.UI.Controls;
17-
using Windows.UI.Xaml.Controls;
18-
using Windows.UI.Xaml.Media;
1912

2013
#if NET6_0_OR_GREATER
2114
using ObjCRuntime;
2215
#endif
2316

2417
#if XAMARIN_IOS_UNIFIED
25-
using Foundation;
26-
using UIKit;
2718
using CoreGraphics;
2819
using _View = UIKit.UIView;
2920
using _Controller = UIKit.UIViewController;
3021
using _Responder = UIKit.UIResponder;
3122
using _Color = UIKit.UIColor;
3223
using _Event = UIKit.UIEvent;
24+
using System.Security.Principal;
3325
#elif __MACOS__
3426
using Foundation;
3527
using AppKit;
@@ -295,7 +287,21 @@ public static void AddChild(this _View parent, _View child)
295287
/// <summary>
296288
/// Get the parent view in the visual tree. This may differ from the logical <see cref="FrameworkElement.Parent"/>.
297289
/// </summary>
298-
public static _View GetVisualTreeParent(this _View child) => child?.Superview;
290+
public static _View GetVisualTreeParent(this _View child)
291+
{
292+
var visualParent = child?.Superview;
293+
294+
// In edge cases, the native Superview is null,
295+
// for example for list items. For those situations
296+
// we use our managed visual parent instead.
297+
if (visualParent is null &&
298+
child is FrameworkElement fw)
299+
{
300+
visualParent = fw.VisualParent;
301+
}
302+
303+
return visualParent;
304+
}
299305

300306
public static IEnumerable<T> FindSubviewsOfType<T>(this _View view, int maxDepth = 20) where T : class
301307
{

src/Uno.UI/Extensions/ViewExtensions.Android.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,21 @@ public static void AddChild(this ViewGroup parent, View child)
390390
/// <summary>
391391
/// Get the parent view in the visual tree. This may differ from the logical <see cref="FrameworkElement.Parent"/>.
392392
/// </summary>
393-
public static ViewGroup? GetVisualTreeParent(this View child) => child?.Parent as ViewGroup;
393+
public static ViewGroup? GetVisualTreeParent(this View child)
394+
{
395+
var visualParent = child?.Parent as ViewGroup;
396+
397+
// In edge cases, the native Parent is null,
398+
// for example for list items. For those situations
399+
// we use our managed visual parent instead.
400+
if (visualParent is null &&
401+
child is FrameworkElement fe)
402+
{
403+
visualParent = fe.VisualParent;
404+
}
405+
406+
return visualParent;
407+
}
394408

395409
/// <summary>
396410
/// Removes a child view from the specified view, and disposes it if the specified view is the owner.

src/Uno.UI/UI/Xaml/Controls/Frame/Frame.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,13 @@ private void MoveFocusFromCurrentContent()
488488
return;
489489
}
490490

491-
var inCurrentPage = focusedElement.GetParents().Any(p => p == this);
491+
var parent = VisualTreeHelper.GetParent(focusedElement);
492+
while (parent is not null && parent != this)
493+
{
494+
parent = VisualTreeHelper.GetParent(parent);
495+
}
496+
497+
var inCurrentPage = parent == this;
492498

493499
if (inCurrentPage)
494500
{

0 commit comments

Comments
 (0)