Skip to content

Commit

Permalink
fix(listview): [iOS] Fix outer ElementName bindings not available on …
Browse files Browse the repository at this point in the history
…reload

Bindings by ElementName to an element outside of a ListViewItem template were getting detached in some cases after the list was unloaded from the window and reloaded, because the ListViewItem's logical Parent was incorrectly getting set to the visual-tree parent (which is a native UIView imposed by UICollectionView). Ensure the logical Parent is correctly restored.
  • Loading branch information
davidjohnoliver committed Nov 9, 2020
1 parent 370ff72 commit 4cf28e4
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -436,5 +436,36 @@ public async Task NoItemSelectedSingle()

Assert.AreEqual(list.SelectedIndex, -1);
}

[TestMethod]
[RunsOnUIThread]
public async Task When_Outer_ElementName_Binding()
{
var page = new ListViewPages.ListViewTemplateOuterBindingPage();

for (int _ = 0; _ < 5; _++)
{
WindowHelper.WindowContent = page;
await WindowHelper.WaitForIdle();

var list = page.FindFirstChild<ListView>();
Assert.IsNotNull(list);

for (int i = 0; i < 3; i++)
{
ListViewItem lvi = null;
await WindowHelper.WaitFor(() => (lvi = list.ContainerFromItem(i) as ListViewItem) != null);
var sp = lvi.FindFirstChild<StackPanel>();
var tb = sp?.FindFirstChild<TextBlock>();
Assert.IsNotNull(tb);
Assert.AreEqual("OuterContextText", tb.Text);
}

WindowHelper.WindowContent = null; // Unload page+list
await WindowHelper.WaitForIdle();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Page x:Class="Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ListViewPages.ListViewTemplateOuterBindingPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ListViewPages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="OuterPage"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
<ListView x:Name="TargetListView"
Grid.Row="1"
Background="Beige"
Width="200"
Height="300"
ItemsSource="{Binding Items}">

<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="OuterBoundTextBlock"
Text="{Binding DataContext.TextFromOuterContext, ElementName=OuterPage}" />
<TextBlock Text="{Binding }" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls.ListViewPages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class ListViewTemplateOuterBindingPage : Page
{
public ListViewTemplateOuterBindingPage()
{
this.InitializeComponent();

DataContext = new MyViewModel();
}

private class MyViewModel
{
public int[] Items { get; } = Enumerable.Range(0, 3).ToArray();

public string TextFromOuterContext => "OuterContextText";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public override UICollectionViewCell GetCell(UICollectionView collectionView, NS

// Ensure the item has a parent, since it's added to the native collection view
// which does not automatically sets the parent DependencyObject.
selectorItem.SetParent(Owner?.XamlParent?.InternalItemsPanelRoot);
selectorItem.SetParentOverride(Owner?.XamlParent?.InternalItemsPanelRoot);
}
else if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
{
Expand Down
38 changes: 38 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/Primitives/SelectorItem.iOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using Uno.Disposables;
using System.Text;
using Windows.UI.Xaml.Input;
using Windows.UI.Core;
using System.Threading.Tasks;
using Uno.UI;
using Uno.Extensions;
using UIKit;

namespace Windows.UI.Xaml.Controls.Primitives
{
public partial class SelectorItem
{
private WeakReference<UIView> _parentOverride;

/// <summary>
/// Set a view which should be used as the DP parent for this container. Used from <see cref="NativeListViewBase"/>, because <see cref="UICollectionView"/>
/// wraps items in native UIViews which interrupts binding propagation.
/// </summary>
/// <param name="parentOverride">The logical parent of this container.</param>
internal void SetParentOverride(UIView parentOverride)
{
_parentOverride = new WeakReference<UIView>(parentOverride);
this.SetParent(parentOverride);
}

internal override void OnLoading()
{
if (_parentOverride?.GetTarget() is { } parentOverride && !(this.GetParent() is DependencyObject))
{
this.SetParent(parentOverride);
}
base.OnLoading();
}
}
}

0 comments on commit 4cf28e4

Please sign in to comment.