locked
Stop GridView Recycling Image Controls RRS feed

  • Question

  • I have a gridview that has a WrapGrid as it's items source. I also use an ItemContainerStyleSelector.

    All the items are grid's which include an Image control.  When the source is set the image is downloaded and set in an Async method. When I scroll through the grid view very quickly the downloading can't keep up but as expected the UI does not hang.  The issue I am having is that when a new object is created, the Image control appears to have an image source of a previous Image Control.  This means when I stop scrolling the Async operations catch up and change the visible objects Image source two or more times.  It starts with a previous incorrect image, changes to another previous incorrect image etc.... until finally setting the source to the correct image.

    I suspect the gridview is recycling the items but I don't seem to be able to turn this off.  I either need to do this or cancel the operations of any items that have been recycled.

    My grid view looks like....
    <GridView x:Name="ItemsGrid" Grid.Row="1" ItemsSource="{Binding BrowseResults}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Margin="20,0,20,0"
                      IsItemClickEnabled="True" ItemClick="ItemsGrid_ItemClick" VirtualizingStackPanel.VirtualizationMode="Standard">
                <GridView.ItemContainerStyleSelector>
                    <controls:UPnPCDStyleSelector CDObject="{StaticResource somestyle}" Container="{StaticResource somestyle2}" Item="{StaticResource somestyle3}"/>
                </GridView.ItemContainerStyleSelector>
                <GridView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapGrid Orientation="Vertical" VirtualizingStackPanel.VirtualizationMode="Standard"/>
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>
            </GridView>

    All the styles contain an image
    <Image DataContext="{Binding CurrentBackdrop}" helpers:ImageExtensions.RemoteURISource="{Binding Uri}" Stretch="UniformToFill"/>

    When the "RemoteURISource" is changed the following runs.
    private static async void OnRemoteURISourceChanged(
                DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                string newRemoteURISource = d.GetValue(RemoteURISourceProperty) as string;
    
                if (!string.IsNullOrWhiteSpace(newRemoteURISource))
                {
                    var image = d as Image;
                    if (image != null)
                    {                    
                        await GetBitmapImageFromURI(newRemoteURISource, image);
                    }
                }
    }
    private static Task GetBitmapImageFromURI(string uri, Image image)
            {
                return Task.Run(async () =>
                    {
                        HttpClient client = new HttpClient();
                        using (HttpResponseMessage response = await client.GetAsync(uri))
                        {
                            byte[] imgByte = await response.Content.ReadAsByteArrayAsync();
                            using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream())
                            {
                                using (DataWriter writer = new DataWriter(stream.GetOutputStreamAt(0)))
                                {
                                    writer.WriteBytes(imgByte);
                                    await writer.StoreAsync();
                                    await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
                                      Windows.UI.Core.CoreDispatcherPriority.Low, async () =>
                                      {
                                          BitmapImage bi = new BitmapImage();
                                          image.Source = bi;
                                          await bi.SetSourceAsync(stream);
                                      });
                                }
                            }
                        }
                        return;
                    });
            }


    Tuesday, February 5, 2013 7:50 PM

Answers

  • I've finally come up with a solution to my problems. To recap....

    Method A - Binding to the Source Property and using a converter meant a Blank Bitmap image was returned as soon as the source was changed. This was great when the control was recycled as it didn't carry the old image across. This was not so good when the image changed of an already loaded control.  The bitmap was immediately blanked before the new image was loaded in an Async method. This meant changing an image has a nasty Flinkering effect.

    Method B -Creating a dependency property and updating the image.source property after the Bitmap had it's source set worked great to avoid the flickering effect but meant the Source was updated multiple times due to the Image Control being recycled faster than the Images could load.

    My solution.... Create a custom UserControl that is made up of an ImageControl.  It has a dependency property that takes a list of Image business objects.  When this changes (I.E the control is recycled) It updates the image using Method A.  When I want to change to the next image I call a method within the Custom User Control and this selects the next image in the list and updates using Method B.

    It's a little bit convoluted but after 2 weeks I finally have a fast UI with no nasty side effects ;-)

    • Marked as answer by StormENT Monday, February 18, 2013 1:42 PM
    Monday, February 18, 2013 1:42 PM

All replies

  • There is something unusual in the above GridView setup. It seems that the ItemContainerStyle 'memorizes' the access to the image source, which it is not meant to be used for. I would suggest to temporarily omit any styling and use a GridViewItem data template mechanism instead.
    -
    It would contain an <Image Source={Binding Path=Image} ...> whose Source is bound to a MyBusinessObject.Image property of type ImageSource. That property would be set in the same fashion [Http ...] as you presently do. However, the code would be constrained to each MyBusinessObject actually being displayed as a DataContext value for the GridViewItem being shown.
    -
    An alternative uses the same DataTemplate approach but uses a Converter mechanism like the one demonstrated by Arabisoft in XAML Image From Path. In that case, the bindig is towards a MyBusinessObject.Path which would in your case be a URI.
    -
    Such approach should work with VirtualizationMode="Standard"; then you can switch to "Recycled" [which is the preferred mechanism]
    -
    Finally, you can reintegrate the Style(Selector) to perform any styling for the DataTemplate(Selector). The simplest style would contain a 'ControlTemplate' with a ControlPresenter to display the DataTemplate content. From there onwards, you can elaborate further on that 'ControlTemplate' [using by the way {TemplateBinding}]. The standard GridViewItem.ItemContainerStyle is a place to start.

    Wednesday, February 6, 2013 7:09 AM
  • Hi ForInfo. I changed
    helpers:ImageExtensions.RemoteURISource="{Binding Uri}"

    to
    Source="{Binding Uri}"

    and the problem is no longer there.  Also changing the CoreDispatcher Priority in my code to normal fixes the issue.  Sadly the first case bypasses my custom code and thus performance is awful (Especially on my Surface tablet) and although the later is better it's still nowhere near that of CoreDispatcher Priority of Low. You mention "It seems that the ItemContainerStyle 'memorizes' the access to the image source". Is this by design or is there a way to force the creation of a new object?

    The Textboxes in the template for example don't seem to suffer the same problems but I'm not sure if this is simply due to the fact that changing Text is so much faster than loading an image.
    • Edited by StormENT Wednesday, February 6, 2013 10:16 AM
    Wednesday, February 6, 2013 9:41 AM
  • "It seems that the ItemContainerStyle 'memorizes' the access to the image source": I am under the impression -  I might be wrong - that your setup is using the ItemContainerStyle(Selector) in a manner that one would normally use a DataTemplate(Selector). That's why I suggested to use your code from within the realm of a DataTemplate rather than (from) a ItemContainerStyle.

    Wednesday, February 6, 2013 11:52 AM
  • Sorry, I misunderstood what you meant.  I changed to the following but still see the same results.  I wonder if it could have something to do with the Dependency Property?

    <GridView x:Name="ItemsGrid" Grid.Row="1" ItemsSource="{Binding BrowseResults}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Margin="20,0,20,0"
                      IsItemClickEnabled="True" ItemClick="ItemsGrid_ItemClick" VirtualizingStackPanel.VirtualizationMode="Standard">
                <GridView.ItemTemplate>
                    <DataTemplate>
                        <Grid x:Name="ContentGrid" Width="341" Height="192" Background="Black">
                            <Path Data="M16.540001,8.8660004L16.540001,21.927 27.425001,15.39711z M0,0L42.667,0 42.667,4.5183251 38.59277,4.5183251 38.59277,9.1850302 42.667,9.1850302 42.667,13.185029 38.59277,13.185029 38.59277,17.851774 42.667,17.851774 42.667,21.777514 38.59277,21.777514 38.59277,26.44426 42.667,26.44426 42.667,30.667 0,30.667 0,26.44426 4.5925722,26.44426 4.5925722,21.777514 0,21.777514 0,17.851774 4.5925722,17.851774 4.5925722,13.185029 0,13.185029 0,9.1850302 4.5925722,9.1850302 4.5925722,4.5183251 0,4.5183251z" 
                                      Stretch="Uniform" Fill="#FFFFFFFF" Width="104" Height="104" Margin="0,31.5,0,0" RenderTransformOrigin="0.5,0.5" VerticalAlignment="Top"/>
                            <Image x:Name="BackgroundImage" DataContext="{Binding CurrentBackdrop}" helpers:ImageExtensions.RemoteURISource="{Binding Uri}" Stretch="UniformToFill"/>
                            <Grid x:Name="ItemTextModule" Height="45" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" 
                                  Background="{StaticResource RTPlayTileTextModuleBackgroundBrush}" Opacity=".8">
                                <StackPanel Orientation="Vertical">
                                    <TextBlock x:Name="TitleTextBlock" Text="{Binding Title}" TextTrimming="None"  Style="{StaticResource RTPlayTileTextModuleHeader}"/>
                                    <TextBlock x:Name="InfoTextBlock" Text="{Binding ModelName}" TextTrimming="None" Style="{StaticResource RTPlayTileTextModuleInfo}"/>
                                </StackPanel>
                            </Grid>
                        </Grid>
                    </DataTemplate>
                </GridView.ItemTemplate>
                <GridView.ItemsPanel>
                    <ItemsPanelTemplate VirtualizingStackPanel.VirtualizationMode="Standard">
                        <WrapGrid Orientation="Vertical" VirtualizingStackPanel.VirtualizationMode="Standard"/>
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>
            </GridView>

    Wednesday, February 6, 2013 12:01 PM
  • it could have something to do with the Dependency Property?: ensure to have the following RegisterAttached property [e.g.] :

            public static readonly DependencyProperty HtmlProperty =
            DependencyProperty.RegisterAttached("Html", typeof(string), typeof(WebClientHelper), new PropertyMetadata(null, HtmlChanged));
            public static void SetHtml(DependencyObject obj, string value)
            {
                obj.SetValue(HtmlProperty, value);
            }
            public static string GetHtml(DependencyObject obj)
            {
                return (string)obj.GetValue(HtmlProperty);
            }
            private static void HtmlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
            }
    

    Wednesday, February 6, 2013 12:19 PM
  • I do have the following....

    public static readonly DependencyProperty RemoteURISourceProperty =
                DependencyProperty.RegisterAttached(
                    "RemoteURISource",
                    typeof(string),
                    typeof(ImageExtensions),
                    new PropertyMetadata(null, OnRemoteURISourceChanged));
    
            public static string GetRemoteURISource(DependencyObject d)
            {
                return (string)d.GetValue(RemoteURISourceProperty);
            }
    
            public static void SetRemoteURISource(DependencyObject d, string value)
            {
                d.SetValue(RemoteURISourceProperty, value);
            }

    It's a longshot but something to do with the fact the SetRemoteURISourceChanged is static?

    I'm pretty sure it's probably not the dependency.  I am also hooking up the Unloaded event of the Image (When it's removed from the Visual Tree) so I can cancel the image download if it's still going but this only fires when I navigate away from the page further pointing to the fact that new image controls are not created for each Item and that Recycling is still happening for some reason.

    Wednesday, February 6, 2013 12:46 PM
  • - "to do with the fact the SetRemoteURISourceChanged is static": no

    - all _seems_ ok. The only suggestion I could make is to check in OnRemoteURIChanged that you get a different Image instance each time it's invoked since there should be no recycling: just add a static HashSet<Image> field in the ImageExtensions an check that upon adding (Image)d there is a bool return = true;

    - check also that the DataContext value is what you expect, e.g. by comparing the URI value with the Title value

    - could also try with some local images in the Picture folder

    • Edited by ForInfo Wednesday, February 6, 2013 1:34 PM
    Wednesday, February 6, 2013 1:19 PM
  • Ok.... So the issue appears to be with the WrapGrid.  If I set the GridViews Item Panel to
    <GridView.ItemsPanel>
                    <ItemsPanelTemplate VirtualizingStackPanel.VirtualizationMode="Standard">
                        <VirtualizingStackPanel Orientation="Horizontal" VirtualizingStackPanel.VirtualizationMode="Standard"/>
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>

    then everything works as expected.  The images are created for each new item and the unloaded event fires while I scroll the items list.  Am I setting the Virtualization mode in the wrong place?
    Wednesday, February 6, 2013 1:56 PM
  • The logic is that VirtualizingStackpanel setting is basically ignored:

                <!-- GridView VirtualizingStackPanel.VirtualizationMode="Standard": does not recycle -->
                <!-- GridView VirtualizingStackPanel.VirtualizationMode="Recycling": recycles [*] -->
                <!-- GridView attribute not set : recycles [*]-->
                <!-- [*] and ignores 'VirtualizingStackPanel VirtualizingStackPanel.VirtualizationMode='Standard' -->
    

    Wednesday, February 6, 2013 2:30 PM
  • Sorry I still can't work out how to change the Virtualization to Standard.  Based on the following where would I set it?

    <GridView x:Name="ItemsGrid" Grid.Row="1" ItemsSource="{Binding BrowseResults}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Margin="20,0,20,0"
                      IsItemClickEnabled="True" ItemClick="ItemsGrid_ItemClick">
                <GridView.ItemContainerStyleSelector>
                    <controls:UPnPCDStyleSelector CDObject="{StaticResource somestyle1}" Container="{StaticResource somestyle2}" Item="{StaticResource somestyle3}"/>
                </GridView.ItemContainerStyleSelector>
                <GridView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapGrid Orientation="Vertical"/>
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>
            </GridView>
    I've tried simply
    <GridView x:Name="ItemsGrid" Grid.Row="1" ItemsSource="{Binding BrowseResults}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Margin="20,0,20,0"
                      IsItemClickEnabled="True" ItemClick="ItemsGrid_ItemClick" VirtualizingStackPanel.VirtualizationMode="Standard">
                <GridView.ItemContainerStyleSelector>
                    <controls:UPnPCDStyleSelector CDObject="{StaticResource somestyle1}" Container="{StaticResource somestyle2}" Item="{StaticResource somestyle3}"/>
                </GridView.ItemContainerStyleSelector>
            </GridView>

    but that doesn't seem to change it.
    Wednesday, February 6, 2013 2:38 PM
  • ... why not use 'Recycling'? ... and ClearContainerForItemOverride is the place to halt any operation in progress before the GridViewItem is being recycled.

    • Edited by ForInfo Wednesday, February 6, 2013 2:55 PM
    Wednesday, February 6, 2013 2:42 PM
  • Recycling seems to be what's causing all my issues.  If I use recycling then the wrong image is set in the wrong items whilst the async method catches up (It looks like a slot machine changing images all over the place until the right one is reached). Imaginescrolling from one end of a data set to another.  By the time you reach the end the item you see could have been recycled 5 times.  Every time the image is recycled a new image source is assigned to it and a new async method is started.  That means that the image you are looking at will be set to recycled item 1 image source, then recycled item 2 image source etc.... until you hit recycled item 5. 

    Also by using Standard I can hook into the unloaded event so when item 1 is removed from the visual tree, if the picture hasn't finished downloading yet I can kill it and free up some resources.  You can scroll a 1000 item dataset in a split second.  If you don't kill the downloading you'd have to wait for all 1000 images to complete (for no reason) before it gets to the one you are waiting for.

    I settled for this custom approach because my app (Which used to simply bind image control source to a uri) was unusable on my surface tablet.  Trying to touch scroll the GridView just wouldn't happen.  It would lock up the UI or be really jumpy.  Using this method it's completely smooth but with the obvious issue of incorrect images being updated.

    From the test with a VirtualizingStackPanel instead of WrapGrid I can see that the benefits of recycling are massively outweighed by the performance gain I get from this custom solution.

    Also I'm not sure how I'd get back to the Task in question from "ClearContainerForItemOverride".  I'm basically awaiting the line after the task is started in the Dependancy Property changed method.  It waits until unloaded and then kills the task if it's still running.
    • Edited by StormENT Wednesday, February 6, 2013 3:00 PM
    Wednesday, February 6, 2013 2:57 PM
  • Recycling case: possibly the 'absence of stopping' the load in progress / load to be  in " ClearContainerForItemOverride " is the key factor in the recycling case behavior. However, I cannot imagine any way to imperatively stop any 'load' in progress whereby the Image in the recycled GridViewItem would refrain acting as a slot machine [unless going back to a UI-penalizing synchronous approach].

    Note:

    - "bi.SetSource(stream);" wouldn't help ?

    - Also I'm not sure how I'd get back to the Task in question from "ClearContainerForItemOverride".  Correct: that's the penalty of not working thru a MyBusinessObject whereto you could to tell to cancel load in progress. Under the assumption of recycling, this would alleviate your problems: currently, the AttachedProperty is working on a Image whose data context changes but the Image itself is not changing. So, its normal to act as a slot machine that gets loaded with several images I succession.

    Standard case: <GridView VirtualizingStackPanel.VirtualizationMode="Standard"> is correct




    • Edited by ForInfo Wednesday, February 6, 2013 3:22 PM
    Wednesday, February 6, 2013 3:10 PM
  • I agree that in the Recycling case there is no way to stop the load as it has no idea that the image is not needed anymore.

    "Standard case: <GridView VirtualizingStackPanel.VirtualizationMode="Standard"> is correct" this is the part I can't for the life of me get working.
    I want a multiline item solution.

    This works and thus would appear to be Standard Virtualization but is only a single line solution

    <GridView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel Orientation="Horizontal" VirtualizingStackPanel.VirtualizationMode="Standard"/>
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>
    This does not work and thus would appear to be Recycled Virtualization as does having no Items panel and setting "<GridView VirtualizingStackPanel.VirtualizationMode="Standard">"
    <GridView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapGrid Orientation="Vertical" VirtualizingStackPanel.VirtualizationMode="Standard"/>
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>

    Which leads me to think the issue is with the WrapGrid (Gridview uses wrapgrid as default is no itemspanel is set) or how I am using/configuring it.

    Wednesday, February 6, 2013 3:21 PM
  • Can you work around the appearance of the old image by nulling out the Image.Source property once the Image object enters into your event handler?

    Like this:

    if(image != null)
    {
         image.Source = null;
         await ........
    }

    Wednesday, February 6, 2013 3:29 PM
  • Thanks Jason...need some help here :-)

    ---

    <GridView VirtualizingStackPanel.VirtualizationMode="Standard">
    together with
    <WrapGrid Orientation="Vertical" VirtualizingStackPanel.VirtualizationMode="Standard"/>
    ... indeed means 'recycling'. My above findings were for a VirtualizingStackPanel ItemsPanel. 

    Wednesday, February 6, 2013 3:33 PM
  • Unfortunately that would indeed blank the image but another async method would then come along and set the source to something wrong ;-(

    As ForInfo has stated the issue seems to be that I can't get the WrapGrid inside the GridView to use Standard virtualization.

    If I edit the GridViews item panel and change the default WrapGrid to a virtualizingstackpanel then Standard Virtualiztion seems to work ok but then I only have a single line of items and not multiline that fills the screen.

    The big issue is I'm completely bypassing the images Source property in XAML as I was having terrible performance issues with it (Even uses convertors etc..).  The only way I've tried that gives a smooth user experience on my Surface tab is to use a custom dependency property.  When this is changed it fires off an Async method that downloads the image, creates a new bitmap image and assigns it to the Image Control. 

    The key is when I marshall back to the UI thread to set the image control source I do so with a Low Priority. This means scrolling always gets priority over images and 99.9% of the time you don't ever see an item with a blank image.

    Wednesday, February 6, 2013 3:43 PM
  • ... a good summary
    Wednesday, February 6, 2013 3:52 PM
  • Unfortunately, WrapGrid does not inherit VirtualizingStackPanel, so the VirtualizingStackPanel.VirtualizationMode attached property will not have any effect on its virtualization behavior. Let me do a little research to see if there is a way to work around this.
    Wednesday, February 6, 2013 3:59 PM
  • Thanks Jason,

    Basically I need any kind of Items Panel that will wrap to multiple lines and support Standard Virtualization. Also happy to extend wrapgrid if that can be done ;-) 

    Wednesday, February 6, 2013 4:04 PM
  • In the meantime, all the above occurred _in fact_ in 'recycling mode'? So the ClearContainerForItemOverride option would be the way to go, even in the absence of a MyBusinessObject DataContext [agree with the incurred overhead]. I can see a solution if you'd have an attached property on a [e.g.] UserControl or TemplatedControl  or subclassed Grid instead of the Image. In such a manner, you'd have a 'Context' to at least set a CancellationToken from within the OnRemoteURIChanged ... and at most 1 image would then still be in the slot machine pipeline.

    Wednesday, February 6, 2013 4:05 PM
  • How does it perform if you don't await GetBitmapImageURI?
    Wednesday, February 6, 2013 7:40 PM
  • The same ;-( In fact in my current code I don't as I'm awaiting the next line instead which waits for the Image control to become unloaded at which point GetBitmapImageURI is killed if it's still running. (Currently this only happens when the page is left due to the recycling).
    I've also tried not awaiting GetBitmapImageURI and having nothing running after it but still no luck.

    Wednesday, February 6, 2013 10:14 PM
  • Hi ForInfo and Jason, so due to the issues the Virtualization I've moved to a setup using a Converter on the source.  Performance is actually very good. The only issue I now have is that GetBitmapImageFromURI runs to completion no matter what so fast scrolling means I have to wait for unloaded images to download before it gets too the currently visible ones.  Do you know how I can end the task from running any further as soon as the image control is recycled? This is what I currently have.
    public class URIToImageConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, string language)
            {
                if (value == null)
                {
                    return DependencyProperty.UnsetValue;
                }
                else if (string.IsNullOrWhiteSpace(value.ToString()))
                {
                    return DependencyProperty.UnsetValue;
                }
                BitmapImage bi = new BitmapImage();
                GetBitmapImageFromURI(value.ToString(), bi);
                return bi;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, string language)
            {
                throw new NotImplementedException();
            }
    
            private async Task GetBitmapImageFromURI(string uri, BitmapImage bi)
            {
                await Task.Run(async () =>
                {
                    HttpClient client = new HttpClient();
                    using (HttpResponseMessage response = await client.GetAsync(uri))
                    {
                        byte[] imgByte = await response.Content.ReadAsByteArrayAsync();
                        imgByte = await PhotoManager.ResizeImageFile(imgByte, new Resolution(341, 192));
                        using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream())
                        {
                            using (DataWriter writer = new DataWriter(stream.GetOutputStreamAt(0)))
                            {
                                writer.WriteBytes(imgByte);
                                await writer.StoreAsync();
                                await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
                                  Windows.UI.Core.CoreDispatcherPriority.Low, async () =>
                                  {
                                      await bi.SetSourceAsync(stream);
                                  });
                            }
                        }
                    }
                });
            }
        }

    Thursday, February 7, 2013 1:10 PM
  • Here are some aspects to consider:

    - ClearContainerForItemOverride can be a place to perform the cancel by setting the CancellationToken. This is somewhat suboptimal because those GridViewItems that need no recycling will download the Image up to completion. Hoewever that will do for those GridViewItems that are about to be recycled.
    - Of course, the problem is that the Converter cannot be 'reached' to force cancellation. This is were I would suggest to
    - encapsulate the Image in a simple contentcontrol [or the MyGridViewItem] whose dep prop would be 'Path/URI' bound and receive the result of conversion, i.e. the BitmapImage + the CancellationToken, and save the latter in a local field in addition to setting the image source.
    - have the contentcontrol manifest itself [on its layout updated event] towards a MyGridViewItem : GridViewItem, so that you can easily reach it from there.

    ---

    The alternative is to work via the DataContext. The trick essentially consists to bind Image not to 'Path' but rather to MyBusinessObject.Self { return this; }. So you can - in the converter - in addition to reach 'Path' - set the CancellationToken reference in a MyBusinessObject.Cancel property, which you can later easily access from the ClearContainerForItemOverride.


    • Edited by ForInfo Thursday, February 7, 2013 2:02 PM
    Thursday, February 7, 2013 2:01 PM
  • Thanks for all your help ForInfo, I've managed to pull something together that works for now.  Owing to the differences between the VirtualizingStackPanel with custom DP approach and WrapGrid with converter approach I'm still hopeful Jason (Or someone at MS) will be able to come up with a solution to force a new image control to be created when virtualizing.  I have tried to have a dig around the WrapGrid myself but it's sealed.  I'm not sure if that's where the Recycling happens or somewhere further up the inheritance chain.
    Thursday, February 7, 2013 5:27 PM
  • Hi Jason, Not sure if you've had any luck but I've been playing around with clear and prepare containerforitemoverride.  I've noticed that the WrapGrid's itemcontainergenerater (Which inherits from VirtualizingPanel) has a Recycle method which I presume is what's being called here. 

    From within ClearContainerForItemOverride I attempted to manually remove the item (As opposed to recycle it) with

    int containerIndex = parent.ItemContainerGenerator.IndexFromContainer(element);
                    parent.ItemContainerGenerator.Remove(parent.ItemContainerGenerator.GeneratorPositionFromIndex(containerIndex), 1);

    but it appears the Remove/Recycle happens before ClearContainerForIteOverride is called as calling ItemContainerGenerator calls ClearContainerForIteOverride again.  Do you know if there is a way to set the default behaviour on the VirtualizingPanel or is this buried somewhere in the WrapGrid class? I don't understand what happens in the Clear/Prepare container methods to really know what direction to take with this. I did successfully remove the element from the visual tree from ClearContainer which in turn killed any unnecessary running methods but the container was still recycled (Or already had been).
    Thursday, February 7, 2013 9:00 PM
  • I've finally come up with a solution to my problems. To recap....

    Method A - Binding to the Source Property and using a converter meant a Blank Bitmap image was returned as soon as the source was changed. This was great when the control was recycled as it didn't carry the old image across. This was not so good when the image changed of an already loaded control.  The bitmap was immediately blanked before the new image was loaded in an Async method. This meant changing an image has a nasty Flinkering effect.

    Method B -Creating a dependency property and updating the image.source property after the Bitmap had it's source set worked great to avoid the flickering effect but meant the Source was updated multiple times due to the Image Control being recycled faster than the Images could load.

    My solution.... Create a custom UserControl that is made up of an ImageControl.  It has a dependency property that takes a list of Image business objects.  When this changes (I.E the control is recycled) It updates the image using Method A.  When I want to change to the next image I call a method within the Custom User Control and this selects the next image in the list and updates using Method B.

    It's a little bit convoluted but after 2 weeks I finally have a fast UI with no nasty side effects ;-)

    • Marked as answer by StormENT Monday, February 18, 2013 1:42 PM
    Monday, February 18, 2013 1:42 PM
  • I've finally come up with a solution to my problems. To recap....

    Method A - Binding to the Source Property and using a converter meant a Blank Bitmap image was returned as soon as the source was changed. This was great when the control was recycled as it didn't carry the old image across. This was not so good when the image changed of an already loaded control.  The bitmap was immediately blanked before the new image was loaded in an Async method. This meant changing an image has a nasty Flinkering effect.

    Method B -Creating a dependency property and updating the image.source property after the Bitmap had it's source set worked great to avoid the flickering effect but meant the Source was updated multiple times due to the Image Control being recycled faster than the Images could load.

    My solution.... Create a custom UserControl that is made up of an ImageControl.  It has a dependency property that takes a list of Image business objects.  When this changes (I.E the control is recycled) It updates the image using Method A.  When I want to change to the next image I call a method within the Custom User Control and this selects the next image in the list and updates using Method B.

    It's a little bit convoluted but after 2 weeks I finally have a fast UI with no nasty side effects ;-)


    Nice to know, I'm facing a similar issue as getting the slot machine effect. Have managed to get rid of that, just need to get rid of any lingering images now while waiting for a new image to be applied. I'll use your suggestion as a base for further investigation as have already gone down the custom control route.

    British Airways Inspiration App & rara music

    Monday, February 18, 2013 1:52 PM