Search code examples
win-universal-apptemplate10

UWP App with Grid View Losing Images when scrolling


Rather new to UWP and XAML and I'm not sure how to debug this.

I have a UWP app based on the Template10 Hamburger template and the Template10 incremental loading sample, plus some bits from a sample photo viewer (Windows 8: Making a Simple Photo Viewer in C# and XAML).

I have modified the main page to display a Gridview of images from the Pictures folder, with the images being loaded incrementally. I also pulled some from a sample photo viewer (Windows 8: Making a Simple Photo Viewer in C# and XAML).

When the app starts, the images are displayed as expected, and as I scroll down, the images are loaded and displayed on demand. The problem is as I scroll back up the list, the images are not displayed any more. My gridview items are still there showing the file name and coloured item background but the image is no longer drawn.

To keep my memory footprint small, I am not storing the actual Bitmap image as part of my collection but a StorageItemThumbnail instead. I originally wanted to store just the image path but that doesn't work for anything in the Pictures library.

public class Picture
{
    public StorageItemThumbnail  ImageThumbNail {get; set;}
    public string Name {get; set;}
}

To display this, I use a converter class to create a stream to set the image source:

public object Convert(object value, Type targetType, object parameter, string language)
{

    BitmapImage image = null;

    if (value != null)
    {
        if (value.GetType() != typeof(StorageItemThumbnail))
        {
            throw new ArgumentException("Expected a StorageItemThumbnail as binding input.");
        }
        if (targetType != typeof(ImageSource))
        {
            throw new ArgumentException("Expected ImageSource as binding output.");
        }

        if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
        {
            image = new BitmapImage();
            image.UriSource = new Uri("ms-appx:///Assets/DesignModeThumbnailPlaceholder.png");
        }
        else
        {
            StorageItemThumbnail ThumbNailFile = (StorageItemThumbnail)value;

            if (ThumbNailFile == null)
                return image;

            image = new BitmapImage();
            IRandomAccessStream thumbnailStream = ThumbNailFile as IRandomAccessStream;
            image.SetSource(thumbnailStream);
        }

    }

    return (image);

}

And this is bound in my XAML as follows:

    <DataTemplate x:Name="PictureGridTemplate2">
    <Grid Height="150" Width="150">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Border Grid.RowSpan="2" Background="Black" Opacity="0.8" />
        <Image Width ="130" HorizontalAlignment="Center"
               Stretch="Uniform" Source="{Binding ImageThumbNail, Converter={StaticResource ThumbnailFileToImageSourceConverter}}"/>
        <TextBlock Grid.Row="1" MaxHeight="30" Text="{Binding Name}"
                   Foreground="White" TextTrimming="CharacterEllipsis"/>
    </Grid>
</DataTemplate>

Can anyone point me in the direction where I've gone wrong with this?

Sheri

* RESOLVED *

I was finally able to figure out what was causing this issue.

It was a side effect of the gridview virtualization, my data model and how I was supplying the image through the converter.

As a test I removed the converter, changed my data model to store a BitmapImage instance of the thumbnail (smaller than storing the whole image) and to bind directly to that property. This worked, the images displayed on the screen as I scrolled up and down through my gridview.

I then changed my data model to have the BitmapImage property getter return the BitmapImage build on the fly from the StorageItemThumbnail property - same issue as when using the converter.

By adding some debugging statements in the getter I saw that the height and width of the BitmapImage on the second request was 0. Aha! So why 0?

Looking at the StorageItemThumbnail property on the second request I saw that the Stream position was at the EOF (not 0 like in the first request) - so this explained the 0 width and height, which explains the empty image control on the screen.

I changed my code to use StorageItemThumbnail.CloneStream and now all images are displayed.

Here is the converter method now:

    public object Convert(object value, Type targetType, object parameter, string language)
    {

        BitmapImage image = null;

        if (value != null)
        {
            if (value.GetType() != typeof(StorageItemThumbnail))
            {
                throw new ArgumentException("Expected a StorageItemThumbnail as binding input.");
            }
            if (targetType != typeof(ImageSource))
            {
                throw new ArgumentException("Expected ImageSource as binding output.");
            }

            if ((StorageItemThumbnail)value == null)
            {
                System.Diagnostics.Debug.WriteLine("Thumbnail is null.");
                return image;
            }

            image = new BitmapImage();

            using (var thumbNailClonedStream = ((StorageItemThumbnail)value).CloneStream())
            {
                System.Diagnostics.Debug.WriteLine("Setting image source from cloned stream.");
                image.SetSource(thumbNailClonedStream);
            } 
        }

        return (image);

    }

Thanks to everyone who took the time to answer and help point me in the right direction.


Solution

  • You can try use compiled binding x:Bind instead of Binding
    In this case you application perfomance would be much more faster.

    Optimize your datatemplate with phases to update GridView items progressively:

        <Grid Height="150" Width="150">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Border Grid.RowSpan="2" Background="Black" Opacity="0.8" />
        <Image Width ="130" HorizontalAlignment="Center"
               Stretch="Uniform" Source="{Binding ImageThumbNail, Converter={StaticResource ThumbnailFileToImageSourceConverter}}" x:Phase="2"/>
        <TextBlock Grid.Row="1" MaxHeight="30" Text="{Binding Name}" x:Phase="1"
                   Foreground="White" TextTrimming="CharacterEllipsis"/>
       </Grid> 
    

    Read this topics:
    ListView and GridView UI optimization
    ListView and GridView data virtualization