Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ImageWallViewer

0.00/5 (No votes)
29 Dec 2017 1  
Show UniformGrid and scroll through a few thousand images, UI virtualization

Program

ImageWall 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

Snippet of ImageWallProj1 solution

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-ViewModels, 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
{
   // Only the selected images for display
   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();

   // Keyboard and right mouse click menu
   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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here