2

In my UWP application, I have a ListView with incremental loading. Now I need to also include an image in the ListViewItem. I tried directly giving it the URI.

where ThumbnailImage is just a string in my view model. The problem with this is that because of incremental loading, when I scroll down a little bit while the previous images are still loading, the app just hangs. Whereas if I let all the images appear and then scroll down, it works fine.

I've also tried the ImageEx control from the toolkit and it has the same problem.

I did some searching the only thing I found was doing IsAsync = True in XAML. But it seems that IsAsync is not available in UWP.

My ViewModel:

public class MyViewModel
{
    ...   
    public string ThumbnailImage { get; set; }
    ...
}

My XAML ListView that gets filled incrementally

<ListView  Grid.Row="0"            
        SelectionMode="Single"
        IsItemClickEnabled="True"
        ItemClick="SearchResultListView_ItemClick" >

    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="BorderBrush" Value="Gray"/>
            <Setter Property="BorderThickness" Value="0, 0, 0, 1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListViewItem">
                        <ListViewItemPresenter
                            ContentTransitions="{TemplateBinding ContentTransitions}"
                            SelectionCheckMarkVisualEnabled="True"
                            CheckBrush="{ThemeResource SystemControlForegroundBaseMediumHighBrush}"
                            CheckBoxBrush="{ThemeResource SystemControlForegroundBaseMediumHighBrush}"
                            DragBackground="{ThemeResource ListViewItemDragBackgroundThemeBrush}"
                            DragForeground="{ThemeResource ListViewItemDragForegroundThemeBrush}"
                            FocusBorderBrush="{ThemeResource SystemControlForegroundAltHighBrush}"
                            FocusSecondaryBorderBrush="{ThemeResource SystemControlForegroundBaseHighBrush}"
                            PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}"
                            PointerOverBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
                            SelectedBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
                            SelectedForeground="{ThemeResource SystemControlDisabledTransparentBrush}"
                            SelectedPointerOverBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
                            PressedBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
                            SelectedPressedBackground="{ThemeResource SystemControlDisabledTransparentBrush}"
                            DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
                            DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
                            ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}"
                            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                            ContentMargin="{TemplateBinding Padding}"
                            CheckMode="Inline"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ListView.ItemContainerStyle>

    <ListView.ItemTemplate>  
        <DataTemplate x:DataType="viewModel:MyViewModel">
            <UserControl >
                <Grid Style="{StaticResource SomeStyle}">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="VisualStateGroup">
                            <VisualState x:Name="VisualStatePhone">
                                <VisualState.StateTriggers>
                                    <AdaptiveTrigger MinWindowWidth="0"/>
                                </VisualState.StateTriggers>
                                <VisualState.Setters>
                                    <Setter Target="ThumbnailImage.Width" Value="50"/>
                                    <Setter Target="ThumbnailImage.Height" Value="50"/>                                         
                                </VisualState.Setters>
                            </VisualState>

                            <VisualState x:Name="VisualStateTablet">
                                <VisualState.StateTriggers> 
                                    <AdaptiveTrigger MinWindowWidth="600" />
                                </VisualState.StateTriggers>
                                <VisualState.Setters>
                                    <Setter Target="ThumbnailImage.Width" Value="70"/>
                                    <Setter Target="ThumbnailImage.Height" Value="70"/>
                                </VisualState.Setters>
                            </VisualState>

                            <VisualState x:Name="VisualStateDesktop">
                                <VisualState.StateTriggers>
                                    <AdaptiveTrigger MinWindowWidth="1200" />
                                </VisualState.StateTriggers>
                                <VisualState.Setters>
                                    <Setter Target="ThumbnailImage.Width" Value="90"/>
                                    <Setter Target="ThumbnailImage.Height" Value="90"/>
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>

                    <Grid.RowDefinitions>
                        <RowDefinition MaxHeight="30"></RowDefinition>
                        <RowDefinition MaxHeight="30"></RowDefinition>
                        <RowDefinition MaxHeight="30"></RowDefinition>
                        <RowDefinition MaxHeight="30"></RowDefinition>
                        <RowDefinition MaxHeight="30"></RowDefinition>
                        <RowDefinition MaxHeight="10"></RowDefinition>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>

                    <Image Source="{x:Bind ThumbnailImage}"                                              
                            Visibility="{x:Bind ThumbnailImage, Converter={StaticResource BoolToVisibilityImage}}"
                            Name="ThumbnailImage"/>

                    <TextBlock Text="{x:Bind Name}" .../>                             

                    <Grid  Grid.Row="1"   
                            Grid.Column="1"
                            Visibility="{x:Bind Source, Converter={StaticResource ConverterNameHere}}">

                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>

                        <TextBlock Text="{x:Bind lblSource, Mode=OneWay}" .../>

                        <TextBlock Text="{x:Bind Source}" .../>
                    </Grid>

                    <Grid Grid.Row="2"
                            Grid.Column="1"
                            Visibility="{x:Bind Author, Converter={StaticResource ConverterNameHere}}">

                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>

                        <TextBlock Text="{x:Bind lblAuthor, Mode=OneWay}" .../>
                        <TextBlock Text="{x:Bind Author}"  .../>
                    </Grid>

                    <TextBlock Text="{x:Bind EducationalLevel}" .../>

                    <StackPanel Orientation="Horizontal"
                                Grid.Row="4"
                                Grid.Column="1">

                        <ItemsControl ItemsSource="{x:Bind AccessRights}">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <StackPanel Orientation="Horizontal"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Image Source="{Binding}"/>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>

                        <ItemsControl ItemsSource="{x:Bind Languages}">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <StackPanel Orientation="Horizontal"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>

                        <ItemsControl ItemsSource="{x:Bind TypeImages}">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <StackPanel Orientation="Horizontal"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Image Source="{Binding}"/>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>                            

                    </StackPanel>

                </Grid>
            </UserControl>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Here's my incremental loading class:

public class ItemsToShow : ObservableCollection<MyViewModel>, ISupportIncrementalLoading
{
    private SearchResponse ResponseObject { get; set; } = new SearchResponse();  
    MyViewModel viewModel = null;


    public bool HasMoreItems
    {
        get
        {
            if ((string.IsNullOrEmpty(SearchResultDataStore.NextPageToken) && !SearchResultDataStore.IsFirstRequest) || SearchResultDataStore.StopIncrementalLoading)
                return false;

            if(SearchResultDataStore.IsFirstRequest)
            {
                using (var db = new DbContext())
                {
                    var json = db.UpdateResponse.First(r => r.LanguageId == DataStore.Language).JsonResponse;
                    Metadata = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateApiResponse>(json).response.metadata.reply;
                }

                var returnObject = SearchResultDataStore.SearchResponse;
                ResponseObject = returnObject.response;

                //This will show a grid with some message for 3 seconds on a xaml page named SearchResultPage.
                Toast.ShowToast(ResponseObject.message, SearchResultPage.Current.ToastGrid);
            }
            else
            {
                SearchApiResponse returnObject = null;
                try
                {
                    returnObject = new SearchApiCall().CallSearchApiAsync(param1, param2, param3).Result;
                }
                catch
                {  
                    return false;
                }
                ResponseObject = returnObject.response;
            }

            try
            {
                return ResponseObject.documents.Count > 0;                                        
            }
            catch { return false; }
        }
    }

    public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
    {
        CoreDispatcher coreDispatcher = Window.Current.Dispatcher;

        return Task.Run<LoadMoreItemsResult>(async () =>
        {           
            await coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                foreach (var item in ResponseObject.documents)
                {             
                    this.Add(PrepareViewModel(item));
                }
            });  

            await Task.Delay(350);                
            return new LoadMoreItemsResult() { Count = count };

        }).AsAsyncOperation<LoadMoreItemsResult>();
    }

    public MyViewModel PrepareViewModel(Document document)
    {
        viewModel = new MyViewModel();

        viewModel.property1 = document.value1;
        viewModel.property2 = document.value2;

        ...
        ...

        if(SearchResultDataStore.ShowImage)
        {            
            //thumbnailUrl is a string.
            viewModel.ThumbnailImage = document.thumbnailUrl;
        }
        else
        {
            viewModel.ThumbnailImage = "somegarbage.png";
        }

        ...
        ...

        return viewModel;
    }

}
ravi kumar
  • 1,548
  • 1
  • 13
  • 47
  • Source should use BitmapImage. – lindexi Sep 19 '17 at 12:50
  • I've tried that also, doesn't help, same results – ravi kumar Sep 19 '17 at 12:51
  • UWP Community Toolkit has `ImageEx` control. You should Look into that – AVK Sep 19 '17 at 15:11
  • Have you tried using `x:Phase` and seeing if that makes any difference? – shawnseanshaun Sep 19 '17 at 15:23
  • 1
    @AVK I've mentioned in the question that I've already tried that. – ravi kumar Sep 19 '17 at 17:07
  • https://stackoverflow.com/questions/31897154/xbind-image-with-null-string – lindexi Sep 20 '17 at 01:06
  • @lindexi I've already handled the null and empty string conditions in my view model. – ravi kumar Sep 20 '17 at 18:47
  • @TastesLikeTurkey That changes nothing. – ravi kumar Sep 20 '17 at 18:53
  • How your *Thumbnail* property looks like? You may take a look at [Stephen Cleary's post](https://msdn.microsoft.com/en-us/magazine/dn605875.aspx) regarding async properties, there is nice *NotifyTaskCompletion* class which you can probably use. Also you will need to rethink how to handle multiple downloads - design download manager (the simplest version may with some semaphores), so that you won't block yourself with parallel tasks. Using separate threads for downloads may be also a good idea. Nevertheless you have provided too little details in your question. – Romasz Sep 23 '17 at 18:31
  • It would be helpful if you could share a sample of this. The `Image` control shouldn't *hang* your app. – Justin XL Sep 25 '17 at 01:15
  • @JustinXL Well, if I just comment out the line ` from the XAML file, everything works just fine. I've added more code to my question. – ravi kumar Sep 25 '17 at 10:30
  • Just of curiosity - you have property *ThumbnailImage*, a control with the name *ThumbnailImage* and you are binding to visibility also *ThumbnailImage* by using converter BoolToVisibility. IMHO it's not good naming choice. – Romasz Sep 25 '17 at 19:47

2 Answers2

0

By default when you're using x:Bind for the source the binding is not updated automatically. To verify that's actually the root cause of your problem you could set a breakpoint in your value converter, and see how many time is called.

I'd suggest using x:Bind ThumbnailImage, Mode=OneWay

You might have to implement INotifyPropertyChanged in your code behind.

meanme
  • 144
  • 1
  • 4
0

Make few steps:

  1. Add x:Phase attribute to Image control. Set biggest than other value (for ex. all are "0", set "1" etc.)

  2. Add Mode=OneWay to Image because your should know when property will be changed.

  3. Add control extension to Image control with dependency property ImagePath. When property will be changed -> start to load image from net asynchronically (for ex. by HttpClient). When request will be finished -> get content and put it to BitmapSource and put it to the Source property of Image.

Be sure that original image's size not much more than Image control's size, because if it's true -> UI will be hang.