listviewuwpvirtualizingstackpanel

ItemsStackPanel extends beyond Grid container for a large source


Update info Ok my apologies I had to review my question and title. But the narrowing of the problem was not easy to track down, despite the several testing we have made. You will find below the new code leading to an unexplained bug, and the older version of the question left here for legacy reasons. The issue is trivial to reproduce.

Updated code Take a Listview bound to an ObservableCollection and populate it with a large amount of items, exceeding 30k or so in our case. The end result is that as you increase the number of items, the ItemsStackPanel in the ListView expands beyond the containing Grid. It happens in both orientation of the ItemsStackPanel but more easily when the orientation is set to Horizontal.

Our question is how can we solve this problem, i.e. avoid flowing out of the containing grid without loosing virtualization?

Please notice that we need to deal with such amount of files as we are building a photos app where it is common to have tens of thousands of images/videos to display. The UWP app actually responds very well to this in all matters except for this bug.

Thank you

XAML

    <Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <Grid Grid.Row="1" Background="LightGreen">
        <ListView
          ItemsSource="{x:Bind Items}"
          ScrollViewer.HorizontalScrollMode="Enabled"
          ScrollViewer.HorizontalScrollBarVisibility="Visible" HorizontalAlignment="Left">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsStackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="Not working"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Grid>

C#

    public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        Items = new ObservableCollection<string>();
        for (int i = 0; i < 30000; i++)//Increase length here
        {
            Items.Add($"Item 1");
        }
    }
    public ObservableCollection<string> Items { get; }
}

OLD CODE (skip it)

The following code display 2 ListView, with only one difference:

The first ListView binds the ItemsSource to a CollectionViewSource.View for which the source is set to a FileInformationFactory. The UI below presents a button to pick up 100+ pictures to fill this ListView (see Edit section).

The second ListView binds the ItemsSource to another CollectionViewSource.View for which the source is set to a trivial ObservableCollection.

Result

The first ListView extends beyond the grid container whenever the ItemsStackPanel's orientation is set to Horizontal. Switching ItemsSource does not solve this problem, hence indicating that this problem is specific to the FileInformationFactory.GetVirtualizedFilesVector() method.

enter image description here

Question

How can I solve this problem without loosing virtualization?

Thanks

XAML

    <Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <StackPanel Grid.ColumnSpan="1" Orientation="Horizontal">
        <AppBarButton Icon="Folder"
                      LabelPosition="Collapsed"
                      Click="FolderPickerButton_Click"/>
    </StackPanel>
    <Grid Grid.Row="1" Background="LightGreen">
        <ListView
              ItemsSource="{x:Bind CollectionViewSource.View, Mode=OneWay}"
              ScrollViewer.HorizontalScrollMode="Enabled"
              ScrollViewer.HorizontalScrollBarVisibility="Visible" HorizontalAlignment="Left">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsStackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="Not working" TextWrapping="WrapWholeWords"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
    <Grid Grid.Row="2" Background="Yellow">
        <ListView
              ItemsSource="{x:Bind WorksCollectionViewSource.View, Mode=OneWay}"
              ScrollViewer.HorizontalScrollMode="Enabled"
              ScrollViewer.HorizontalScrollBarVisibility="Visible" HorizontalAlignment="Left">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsStackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="Working" TextWrapping="WrapWholeWords"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Grid>

C#

    public sealed partial class Scenario5 : Page
{
    public Scenario5()
    {
        this.InitializeComponent();
        Items = new ObservableCollection<string>();
        for (int i = 0; i < 30000; i++)
        {
            Items.Add($"Item 1");
        }
        CollectionViewSource = new CollectionViewSource();
        WorksCollectionViewSource = new CollectionViewSource();
    }

    private StorageFolder _folder;
    private QueryOptions _queryOptions;
    private StorageFileQueryResult _query;
    private FileInformationFactory _fileInformationFactory;

    public CollectionViewSource CollectionViewSource { get; set; }

    public CollectionViewSource WorksCollectionViewSource { get; set; }

    public ObservableCollection<string> Items { get; }

    private async void FolderPickerButton_Click(object sender, RoutedEventArgs e)
    {
        var _pickedFolder = await PickFolderAsync();
        if (_pickedFolder == null)
        {
            return;
        }

        _folder = _pickedFolder;
        _queryOptions = new QueryOptions
        {
            FolderDepth = FolderDepth.Deep,
            IndexerOption = IndexerOption.UseIndexerWhenAvailable,
        };

        _query = _folder.CreateFileQueryWithOptions(_queryOptions);

        _fileInformationFactory = new FileInformationFactory(_query, ThumbnailMode.SingleItem, 160,
            ThumbnailOptions.UseCurrentScale, delayLoad: false);

        var _vector = _fileInformationFactory.GetVirtualizedFilesVector();

        CollectionViewSource.Source = _vector;

        WorksCollectionViewSource.Source = Items;
    }

    private static async Task<StorageFolder> PickFolderAsync()
    {
        var folderPicker = new FolderPicker
        {
            SuggestedStartLocation = PickerLocationId.Desktop,
            ViewMode = PickerViewMode.Thumbnail
        };

        folderPicker.FileTypeFilter.Add("*");

        var _pickedFolder = await folderPicker.PickSingleFolderAsync();
        return _pickedFolder;
    }

}

Edit

The problem has been narrowed. This is not an issue of the FileInformationFactory. The problem occurs also with an ObservableCollection with a large amount of items, circa 30k in my case, see the ctor in the code behind. Below this number the all items are within the grid, and above they get out of the parent container.


Solution

  • To circumvent this issue with a ListView one can Clip the Visual as such

     var visual = ElementCompositionPreview.GetElementVisual(listView);
                visual.Clip = Window.Current.Compositor.CreateInsetClip();
    

    where listView is the name of the ListView.

    Credits go to ranjeshj on Github https://github.com/microsoft/microsoft-ui-xaml/issues/1876