Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions src/Controls/src/Core/BindableLayout/BindableLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ void CreateChildren()
return;
}

layout.Clear();
ClearChildren(layout);

UpdateEmptyView(layout);

Expand All @@ -326,6 +326,23 @@ void CreateChildren()
}
}

void ClearChildren(IBindableLayout layout)
{
var index = layout.Children.Count;
while (--index >= 0)
{
var child = (View)layout.Children[index]!;
layout.RemoveAt(index);

// Empty view inherits the BindingContext automatically,
// we don't want to mess up with automatic inheritance.
if (child == _currentEmptyView) continue;

// Given that we've set BindingContext manually on children we have to clear it on removal.
child.BindingContext = null;
}
}

void UpdateEmptyView(IBindableLayout layout)
{
if (_currentEmptyView == null)
Expand Down Expand Up @@ -369,7 +386,6 @@ View CreateEmptyView(object emptyView, DataTemplate dataTemplate)
if (dataTemplate != null)
{
var view = (View)dataTemplate.CreateContent();
view.BindingContext = (layout as BindableObject).BindingContext;
return view;
}

Expand All @@ -390,7 +406,15 @@ void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArg

e.Apply(
insert: (item, index, _) => layout.Insert(CreateItemView(item, layout), index),
removeAt: (item, index) => layout.RemoveAt(index),
removeAt: (item, index) =>
{
var child = (View)layout.Children[index]!;
layout.RemoveAt(index);

// It's our responsibility to clear the BindingContext for the children
// Given that we've set them manually in CreateItemView
child.BindingContext = null;
},
reset: CreateChildren);

// UpdateEmptyView is called from within CreateChildren, therefor skip it for Reset
Expand Down
65 changes: 65 additions & 0 deletions src/Controls/tests/Core.UnitTests/BindableLayoutTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,71 @@ public void ValidateBindableProperties()
Assert.Equal(itemTemplateSelector, BindableLayout.GetItemTemplateSelector(layout));
Assert.Equal(itemTemplateSelector, layout.GetValue(BindableLayout.ItemTemplateSelectorProperty));
}

[Fact]
public async Task ItemViewBindingContextIsSetToNullOnClear()
{
var list = new ObservableCollection<string> { "Foo" };

var layout = new StackLayout { IsPlatformEnabled = true };
BindableLayout.SetItemTemplate(layout, new DataTemplate(() => new Label()));
BindableLayout.SetItemsSource(layout, list);

// Verify that the item view is bound to collection item
var itemView = layout.Children.FirstOrDefault() as Label;
Assert.NotNull(itemView);
Assert.Equal(list[0], itemView.BindingContext);

// Clear collection by setting it to null
BindableLayout.SetItemsSource(layout, null);

// Verify item view binding context is null
Assert.Null(itemView.BindingContext);
}

[Fact]
public async Task ItemViewBindingContextIsSetToNullOnRemove()
{
var list = new ObservableCollection<string> { "Foo" };

var layout = new StackLayout { IsPlatformEnabled = true };
BindableLayout.SetItemTemplate(layout, new DataTemplate(() => new Label()));
BindableLayout.SetItemsSource(layout, list);

// Verify that the item view is bound to collection item
var itemView = layout.Children.FirstOrDefault() as Label;
Assert.NotNull(itemView);
Assert.Equal(list[0], itemView.BindingContext);

// Remove the item
list.RemoveAt(0);

// Verify item view binding context is null
Assert.Null(itemView.BindingContext);
}

[Fact]
public async Task EmptyViewTemplateContentInheritsLayoutBindingContext()
{
var list = new ObservableCollection<string>();

var bindingContext = "Foo";

var layout = new StackLayout { IsPlatformEnabled = true, BindingContext = bindingContext };
BindableLayout.SetEmptyViewTemplate(layout, new DataTemplate(() => new Label()));
BindableLayout.SetItemsSource(layout, list);

// Verify that the empty view is bound to layout's binding context
var emptyView = layout.Children.FirstOrDefault() as Label;
Assert.NotNull(emptyView);
Assert.Equal(bindingContext, emptyView.BindingContext);

// Change binding context on layout
layout.BindingContext = bindingContext = "Bar";

// Verify empty view inherited the binding context
Assert.Equal(bindingContext, emptyView.BindingContext);
}

[Fact]
public async Task DoesNotLeak()
Expand Down