Program
File Menu options, steps for first step use:
- Resize window (did not test cold start)
- Select a folder with Jpegs
- Set the number columns and rows you prefer
- Set the sort option (Date, Name up/down, random) you prefer
- (Note that it is possible to apply a file name Filter, but the text filter can not be saved)
- Hide optional the extra tool bar with the search field
- Save needed (!!) to use current settings for the next startup
Introduction
I wanted to get a global impression of the performance (loading, scrolling, memory) of a WPF program that is able to scroll through a few thousands of images in one folder displaying multiple images at the screen.
I did a few small trial and error experiments. To make a little test program a little more usable for me, I did an extra iteration. Although not very polished or well developed, the program itself may be of some (minor) interest so I decided to publish it in this trick/tip. The discussion is rather informal from the hip and does not contain a real in depth discussion, comparing measurements or a generalization of the approach token.
I will discuss:
- The background of this informal test/experiment
- The solution chosen for this program using UI virtualization with a ScrollBar ViewModel (??!)
- Short discussion of some code (ScrollBar ViewModel and XAML code to bind it to a
ScrolBar
and a ItemsControl
, small sample of a ICollectionView
for sorting and filtering, mention updated border-less Window).
Background
I was exploring UWP on the basis of a simple scenario: use Windows Template Studio - ask for folder - display multiple images in several kinds of new grids and scrolling. For a few hundred images, performance is reasonable, but for a few thousand images, performance (scanning Folder, start up time, memory when loading all images, scrolling) seemed not so good.
I remembered that once I have written an WPF Image Viewer (one list of images on the side for scrolling through the set, and one Zoomable /Draggeble /Rotatable MainScreen) that had no problems with performance. So I decided to see as a reference how a WPF program would do by stripping down that Image Viewer, using only the list. Startup and scrolling (well, not so snappy as I thought) are OK.
I changed the ItemsPanel
of the ListBox
to a UniformGrid
. An ItemsPanel
handles the layout of the children, a Grid
for example places its children in rows and columns.
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="3" Columns="3" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
".. I Changed the ItemsPanel of the ListBox to a UniformGrid and everything seemed slower "
But when I changed the ItemsPanel
of the ListBox
to an Uniform Grid
, it all seemed to become slower!! What happens here? My working hypothesis is as follows. The standard panel of a ListBox
is the VirtualizingStackPanel.
For fast presentation in WPF, we can use Data and UI Virtualization. Loosely spoken, Data Virtualization means that not all elements in a List are fully computed at start. UI virtualization means loosely spoken that only the elements are realized in the visual tree that are visible in the current scope. The default VirtualizingStackPanel
for a ListBox
does that, but the UniformGrid
not. The under-the-hood-UI virtualization stuff takes some time to study what I did not, see for example.
So to keep things simple for me, all I wanted was a UniformGrid
or something like that that does UI virtualization. I Googled and did try some (some are left for reference in Folder "Virtualizing Panels Try" of the project, now not used in actual code). At first glance, startup delay seemed ok, but scrolling though a few thousand images still seemed slow. The low level computation of the realized items in scope does of course take some time.
I decided to test a solution that seems simple and fast. This is possible because all images are of equal size displayed in a Uniform Grid of given dimensions. So I used a ScrollBar ViewModel
that given the dataset
and UniformGrid
settings can compute the DisplayImages
actual visible.
The solution has some similarities with the IIncrementalSource
for the IncrementalLoadingCollection
(UWP) but in that approach, the size of the Collection
is unknown and no indication of the size of the Collection
is visible.
Some Code Details
Image1.cs
I previously used a kind of Data Virtualization by specifying the FileName of the Image, but computing the source in the getter of the property only when required. Now we have a class Image1Info
for the ICollectionView
for sorting and filtering and Image1
with a bitmap for the DisplayFiles
that are now actually displayed. For smaller number of images, scrolling may be faster by loading the bitmap of all images at startup.
MainResource.xaml
Definitions for round buttons of TitleBar.xaml and horizontal slider. Also, style definition for border less Window. My former solution used for a border less Window resulted at some point in time in an extra white stroke at the top of the Window. Now I use another solution for the chrome, see this sample. For the maximized window in this new solution, the TitleBar
should be adapted.
CollectionView.cs
MainVm
used here uses some child-ViewModel
s, see interface below for DataView
child ViewModel
.
public interface IDataView
{
ICollectionView MyDataView { get; set; }
string FilterString { get; set; }
void SetFilter();
DataView.SortPropOption SortProp { get; set; }
void SetSortDescr();
}
MyDataView
is loaded from thumbs (ObservableCollection<Image1Info>
), see MainVM
. Here, an ObservableCollection
is not strictly needed for ICollectionView
I think. SortDirection
and Filter
can be set. The sorted filtered thumbs can be retrieved using MyDataView.Cast<Model.ImageInfo1>
.
ScrollBarVm.cs
The interface of this child view model is:
public interface IScrollBarVm
{
ObservableCollection<Image1> DisplayFiles { get; set; }
int NGrid { get; set; }
int NGridX { get; set; }
int NGridY { get; set; }
double ScrollMax { get; set; }
double ScrollValue { get; set; }
double ScrollViewportSize { get; set; }
ICommand SetIFile { get; }
void OnRefreshDisplayFiles();
ICommand SetPage { get; }
void OnSetPage(object x);
}
MainWindow.xaml
See XAML below for two parts that bind to the ScrollBarVm
, the ScrollBar
and the ItemsControl
. There is some code behind.
<ScrollBar
....
Maximum="{Binding ScrollBarVm1.ScrollMax}"
ViewportSize="{Binding ScrollBarVm1.ScrollViewportSize}"
Value="{Binding ScrollBarVm1.ScrollValue}"
SmallChange="{Binding ScrollBarVm1.NGridX}"
LargeChange="{Binding ScrollBarVm1.NGrid}"
mvvm:EventToCommand.Event="UIElement.PreviewMouseUp"
mvvm:EventToCommand.Command="{Binding ScrollBarVm1.SetIFile}"
Scroll="ScrollBar_Scroll"
MouseWheel="scrollBar1_MouseWheel"
>
</ScrollBar>
<ItemsControl ItemsSource="{Binding ScrollBarVm1.DisplayFiles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding ScrollBarVm1.NGridX}" Rows="{Binding ScrollBarVm1.NGridY}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Image}" Stretch="Uniform" HorizontalAlignment="Center">
<Image.ToolTip>
<StackPanel Orientation="Vertical">
<Image Source="{Binding Image}"
Stretch="Uniform" Height="600"/>
<TextBlock Text="{Binding Comment}" />
</StackPanel>
</Image.ToolTip>
</Image>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note that we can specify with the ItemsPanelTemplate
how all Items / Children are presented and with the ItemTemplate
how a single Item is presented.
The ToolTip is a larger image, I did not test influence of simplifying the ToolTip on performance. At first visual inspection, thumb height is not quite correct. For this program, I did not bother, but one thing to check is the minimal Thumb Height. With large collections, small ViewportSize
the thumb height can become too small from a visual viewpoint.
We used the following approach for the window placement. A new program start does restore the saved windows placement, however restoring windows placement after the reset menu command is not implemented.
Points of Interest
- Program not very mature, UI not appealing, some to dos. Check Thumb height.
- Loading time reasonable.
- Scrolling for a few thousand images not snappy, just acceptable, see for yourself.
- This method can also be used to limit memory resources when scrolling in a similar way through a folder of larger moving Gifs (to guarantee that no more than the current available memory is used requires more work).
History
- Link to image corrected after publication