Skip to content

Commit

Permalink
fix(listview): [iOS] Fix ItemTemplateSelector called at wrong time
Browse files Browse the repository at this point in the history
Fix ItemTemplateSelector being called when list is reloaded for items that are not currently materialized, by modifying the behaviour of ContentPresenter to only update its template root on loading when actually necessary.
  • Loading branch information
davidjohnoliver committed Jan 19, 2022
1 parent fe3d76d commit 5a9d994
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ public partial class Given_ListViewBase
private DataTemplate GreenSelectableTemplate => _testsResources["GreenSelectableTemplate"] as DataTemplate;
private DataTemplate BeigeSelectableTemplate => _testsResources["BeigeSelectableTemplate"] as DataTemplate;

private DataTemplate SelectableBoundTemplateA => _testsResources["SelectableBoundTemplateA"] as DataTemplate;
private DataTemplate SelectableBoundTemplateB => _testsResources["SelectableBoundTemplateB"] as DataTemplate;

private DataTemplate BoundHeightItemTemplate => _testsResources["BoundHeightItemTemplate"] as DataTemplate;

[TestInitialize]
Expand Down Expand Up @@ -1640,6 +1643,68 @@ public async Task When_Unmaterialized_Item_Size_Changed()
Assert.AreEqual(listBounds.Y, itemBounds.Y); // Top of first item should align with top of list
}

[TestMethod]
public async Task When_TemplateSelector_And_List_Reloaded()
{
var itemsSource = new ObservableCollection<SourceAwareItem>();
var selector = new SourceAwareSelector(itemsSource, SelectableBoundTemplateA, SelectableBoundTemplateB);
var counter = 0;

AddItem();
AddItem();
AddItem();

var list = new ListView()
{
Width = 200,
Height = 300,
ItemsSource = itemsSource,
ItemTemplateSelector = selector
};

WindowHelper.WindowContent = list;

await WindowHelper.WaitForLoaded(list);

AddItem();
AddItem();
AddItem();

RemoveItem();
RemoveItem();
RemoveItem();

await WindowHelper.WaitFor(() =>
{
var firstContainer = (list.ContainerFromIndex(0) as ListViewItem);
return firstContainer?.Content == itemsSource[0];
});

WindowHelper.WindowContent = null; // Unload list

await Task.Delay(100);

WindowHelper.WindowContent = list;

await WindowHelper.WaitForLoaded(list);

if (selector.Exception is { } ex)
{
throw ex;
}

void AddItem()
{
itemsSource.Add(new SourceAwareItem { No = counter });
counter++;
}

void RemoveItem()
{
itemsSource.RemoveAt(0);
}
}

private bool ApproxEquals(double value1, double value2) => Math.Abs(value1 - value2) <= 2;

private class When_Removed_From_Tree_And_Selection_TwoWay_Bound_DataContext : System.ComponentModel.INotifyPropertyChanged
Expand Down Expand Up @@ -1837,4 +1902,53 @@ public double ItemHeight

public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
}

public class SourceAwareItem
{
public int No { get; set; }

public override string ToString() => $"Item {No}";
}

public class SourceAwareSelector : DataTemplateSelector
{
private readonly IList<SourceAwareItem> _itemsSource;
public DataTemplate _dataTemplateA;
private readonly DataTemplate _dataTemplateB;

public Exception Exception { get; private set; }

public SourceAwareSelector(IList<SourceAwareItem> itemsSource, DataTemplate dataTemplateA, DataTemplate dataTemplateB)
{
_itemsSource = itemsSource;
_dataTemplateA = dataTemplateA;
_dataTemplateB = dataTemplateB;
}

protected override DataTemplate SelectTemplateCore(object item)
{
if (
#if __IOS__
// On iOS, the template selector may be invoked with a null item. This is arguably also a bug, but not presently under test here.
item != null &&
#endif
!_itemsSource.Contains(item)
)
{
var ex = new InvalidOperationException($"Selector called for item not in source ({item})");
Exception = Exception ?? ex;
throw ex;
}

if (item is SourceAwareItem dataItem && dataItem.No > 2)
{
return _dataTemplateB;
}

else
{
return _dataTemplateA;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,21 @@
</Border>
</DataTemplate>

<DataTemplate x:Key="SelectableBoundTemplateA">
<Border BorderBrush="Green"
BorderThickness="2"
CornerRadius="5">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
<DataTemplate x:Key="SelectableBoundTemplateB">
<Border BorderBrush="Red"
BorderThickness="2"
CornerRadius="5">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>

<DataTemplate x:Key="BoundHeightItemTemplate">
<Border x:Name="ItemBorder"
MinWidth="130"
Expand Down
17 changes: 11 additions & 6 deletions src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -786,9 +786,10 @@ private protected override void OnLoaded()
{
base.OnLoaded();

ResetDataContextOnFirstLoad();

SetUpdateTemplate();
if (ResetDataContextOnFirstLoad() || ContentTemplateRoot == null)
{
SetUpdateTemplate();
}

// When the control is loaded, set the TemplatedParent
// as it may have been reset during the last unload.
Expand All @@ -797,7 +798,7 @@ private protected override void OnLoaded()
UpdateBorder();
}

private void ResetDataContextOnFirstLoad()
private bool ResetDataContextOnFirstLoad()
{
if (!_firstLoadResetDone)
{
Expand All @@ -808,7 +809,11 @@ private void ResetDataContextOnFirstLoad()
this.ClearValue(DataContextProperty, DependencyPropertyValuePrecedences.Local);

TrySetDataContextFromContent(Content);

return true;
}

return false;
}

protected override void OnVisibilityChanged(Visibility oldValue, Visibility newValue)
Expand Down Expand Up @@ -846,7 +851,7 @@ public void UpdateContentTemplateRoot()
{
IsUsingDefaultTemplate = false;
}
}
}

if (Content != null
&& !(Content is View)
Expand All @@ -855,7 +860,7 @@ public void UpdateContentTemplateRoot()
{
// Use basic default root for non-View Content if no template is supplied
SetContentTemplateRootToPlaceholder();
}
}

if (ContentTemplateRoot == null && Content is View contentView && dataTemplate == null)
{
Expand Down

0 comments on commit 5a9d994

Please sign in to comment.