Include headers/footers in EmptySource count so they show up when ItemsSource is null (#15979)

* Include headers/footers in EmptySource count so they show up when ItemsSource is null
Fixes #8934

* More tests
This commit is contained in:
E.Z. Hart 2023-07-10 08:55:40 -06:00 коммит произвёл GitHub
Родитель 05b741dba5
Коммит b9f64e153d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 108 добавлений и 9 удалений

Просмотреть файл

@ -10,12 +10,12 @@ namespace Microsoft.Maui.Controls.Handlers.Items
{ {
public class EmptyViewAdapter : RecyclerView.Adapter public class EmptyViewAdapter : RecyclerView.Adapter
{ {
int _headerHeight; double _headerHeight;
int _headerViewType; int _headerViewType;
object _headerView; object _headerView;
DataTemplate _headerViewTemplate; DataTemplate _headerViewTemplate;
int _footerHeight; double _footerHeight;
int _footerViewType; int _footerViewType;
object _footerView; object _footerView;
DataTemplate _footerViewTemplate; DataTemplate _footerViewTemplate;
@ -301,18 +301,25 @@ namespace Microsoft.Maui.Controls.Handlers.Items
if (item == null) if (item == null)
return; return;
var sizeRequest = new SizeRequest(new Size(0, 0)); var size = Size.Zero;
if (item is View view) if (item is IView view)
sizeRequest = view.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins); {
if (view.Handler == null)
{
TemplateHelpers.GetHandler(view as View, ItemsView.FindMauiContext());
}
size = view.Measure(double.PositiveInfinity, double.PositiveInfinity);
}
if (item is DataTemplate dataTemplate) if (item is DataTemplate dataTemplate)
{ {
var content = dataTemplate.CreateContent() as View; var content = dataTemplate.CreateContent() as IView;
sizeRequest = content.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins); size = content.Measure(double.PositiveInfinity, double.PositiveInfinity);
} }
var itemHeight = (int)sizeRequest.Request.Height; var itemHeight = size.Height;
if (isHeader) if (isHeader)
_headerHeight = itemHeight; _headerHeight = itemHeight;

Просмотреть файл

@ -3,7 +3,7 @@ namespace Microsoft.Maui.Controls.Handlers.Items
{ {
sealed internal class EmptySource : IItemsViewSource sealed internal class EmptySource : IItemsViewSource
{ {
public int Count => 0; public int Count => (HasHeader? 1 : 0) + (HasFooter? 1 : 0);
public bool HasHeader { get; set; } public bool HasHeader { get; set; }
public bool HasFooter { get; set; } public bool HasFooter { get; set; }

Просмотреть файл

@ -50,5 +50,37 @@ namespace Microsoft.Maui.DeviceTests
// Without Exceptions here, the test has passed. // Without Exceptions here, the test has passed.
Assert.Equal(0, (rootPage as IPageContainer<Page>).CurrentPage.Navigation.ModalStack.Count); Assert.Equal(0, (rootPage as IPageContainer<Page>).CurrentPage.Navigation.ModalStack.Count);
} }
[Fact]
public async Task NullItemsSourceDisplaysHeaderFooterAndEmptyView()
{
SetupBuilder();
var emptyView = new Label { Text = "Empty" };
var header = new Label { Text = "Header" };
var footer = new Label { Text = "Footer" };
var collectionView = new CollectionView
{
ItemsSource = null,
EmptyView = emptyView,
Header = header,
Footer = footer
};
ContentPage contentPage = new ContentPage() { Content = collectionView };
var frame = collectionView.Frame;
await CreateHandlerAndAddToWindow<IWindowHandler>(contentPage,
async (_) =>
{
await WaitForUIUpdate(frame, collectionView);
Assert.True(emptyView.Height > 0, "EmptyView should be laid out");
Assert.True(header.Height > 0, "Header should be laid out");
Assert.True(footer.Height > 0, "Footer should be laid out");
});
}
} }
} }

Просмотреть файл

@ -170,6 +170,66 @@ namespace Microsoft.Maui.DeviceTests
}); });
} }
[Theory]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
[InlineData(true, true, true)]
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
public async Task CollectionViewStructuralItems(bool hasHeader, bool hasFooter, bool hasData)
{
SetupBuilder();
double containerHeight = 500;
double containerWidth = 500;
var layout = new Grid() { IgnoreSafeArea = true, HeightRequest = containerHeight, WidthRequest = containerWidth };
Label headerLabel = hasHeader ? new Label { Text = "header" } : null;
Label footerLabel = hasFooter ? new Label { Text = "footer" } : null;
var collectionView = new CollectionView
{
ItemsLayout = LinearItemsLayout.Vertical,
ItemTemplate = new DataTemplate(() => new Label() { HeightRequest = 20, WidthRequest = 20 }),
Header = headerLabel,
Footer = footerLabel,
ItemsSource = hasData ? null : new ObservableCollection<string> { "data" }
};
layout.Add(collectionView);
var frame = collectionView.Frame;
await CreateHandlerAndAddToWindow<LayoutHandler>(layout, async handler =>
{
await WaitForUIUpdate(frame, collectionView);
frame = collectionView.Frame;
#if WINDOWS
// On Windows, the ListView pops in and changes the frame, then actually
// loads in the data, which updates it again. So we need to wait for the second
// update before checking the size
await WaitForUIUpdate(frame, collectionView);
frame = collectionView.Frame;
#endif
if (hasHeader)
{
Assert.True(headerLabel.Height > 0);
Assert.True(headerLabel.Width > 0);
}
if (hasFooter)
{
Assert.True(footerLabel.Height > 0);
Assert.True(footerLabel.Width > 0);
}
});
}
public static IEnumerable<object[]> GenerateLayoutOptionsCombos() public static IEnumerable<object[]> GenerateLayoutOptionsCombos()
{ {
var layoutOptions = new LayoutOptions[] { LayoutOptions.Center, LayoutOptions.Start, LayoutOptions.End, LayoutOptions.Fill }; var layoutOptions = new LayoutOptions[] { LayoutOptions.Center, LayoutOptions.Start, LayoutOptions.End, LayoutOptions.Fill };