0

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.

Octopus
  • 661
  • 4
  • 21
  • I used your code to select a folder which contains 20+ files and set fileInformationFactory.GetVirtualizedFilesVector to the source of ListView, it displayed well as the second listView, it didn't extend beyond the grid container. I can't reproduce this issue. Were my steps correct? And what's your target version and the version of visual studio? – Faywang - MSFT Jan 22 '20 at 06:22
  • Thanks Faywang, I am using VS Version 16.4.3 and the target version is 1903 (10.0, Build 18362). The issue should appear if you copy paste the code above, which I assume you did? and if your Item.Count is large enough to exceed the bounds of the grid. I have this issue on two projects. – Octopus Jan 22 '20 at 07:59
  • Faywang, I confirm this issue on a a third project I just created from scratch. Which version are you using?? – Octopus Jan 22 '20 at 08:06
  • Yes, I used the same version as yours, VS2019 16.4.3 and the target version is 1903 (10.0, Build 18362). And I copied your code above to test it. This time I selected 100+ pictures but still worked well. Can you provide a simple sample that can be reproduced for us to test? – Faywang - MSFT Jan 22 '20 at 08:31
  • Faywang, here it goes https://github.com/Ponant/ItemsStackPanelUWPBug , let me know if it shows the bug. I am astonished you do not see it. – Octopus Jan 22 '20 at 09:34
  • Faywang, there is a higher problem, the problem does not occur for a few files, I have 30k files. I incorrectly said 100+ in post because I sought it was not an issue. But the number of items seems important to have this problem popup. Check a folder that has a huge amount of files, I will try to figure out the treshold, but it surely occurs with 30k files on a folder in my desktop. – Octopus Jan 22 '20 at 09:41
  • OK I confirm this is not an issue of the FileInformfactory, I added 30k Items to the ObservableCollection in the ctor and the problem now occurs for the second row. No need to scan or invoke the FileInformation factory for this issue to occur. I update my post. – Octopus Jan 22 '20 at 09:45
  • Treshold is 24k Items in the observable collection for my laptop... – Octopus Jan 22 '20 at 09:48
  • The problem also occurs in a Vertical orientation but you need more items (100k). – Octopus Jan 22 '20 at 10:15

1 Answers1

0

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

Octopus
  • 661
  • 4
  • 21