ListView items from an ObservableCollection that need to prepare themselves for realization

427 views Asked by At

I have a ListView which is supposed to display a rather large number of items comprised of a "Name", a "Thumbnail" and a "AnimationPosition" property. A background task in each item's type is in charge of switching the thumbnails in order to animate them.

Now it goes without saying that this is a rather heavy operation and should be limited to as few items as possible e.g. to visible/realizing items of a Virtualized ListView. Now I already have set the DataContext of my ListView to the ObeservableCollection instance and have bound it to the properties of its type. Here's a peek into my XAML code for that.

<TabControl Grid.Row="0" Grid.Column="2">

  <TabControl.Resources>

    <Style x:Key="MediaItemStyle" TargetType="{x:Type ListViewItem}">

      <Setter Property="Margin" Value="5,5,5,5"/>
      <Setter Property="Padding" Value="0,0,0,0"/>
      <Setter Property="HorizontalAlignment" Value="Left"/>
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate  TargetType="{x:Type ListViewItem}">
            <Grid HorizontalAlignment="Left" VerticalAlignment="Top" Height="Auto" >
              <Border x:Name="border" BorderBrush="{x:Null}" BorderThickness="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CornerRadius="2.5"/>
              <StackPanel HorizontalAlignment="Stretch"  VerticalAlignment="Stretch">
                <ContentPresenter/>
              </StackPanel>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>

    </Style>

    <Style TargetType="custom:MediaContainerListView">

      <Setter Property="ItemsSource" Value="{Binding}"/>
      <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
      <Setter Property="ItemContainerStyle" Value="{StaticResource MediaItemStyle}"/>
      <Setter Property="ItemsPanel">
        <Setter.Value>
          <ItemsPanelTemplate>
            <WrapPanel/>
          </ItemsPanelTemplate>
        </Setter.Value>
      </Setter>
      <Setter Property="ItemTemplate">
        <Setter.Value>
          <DataTemplate>
            <DockPanel Width="256">

              <Image DockPanel.Dock="Top" Height="144" StretchDirection="Both" 
                     Stretch="Fill" Source="{Binding Thumbnail.Source,Mode=OneWay}"/>

              <ProgressBar DockPanel.Dock="Top" Height="2"
                           Minimum="0" Maximum="{Binding Thumbnail.AnimationPosition.Length}"
                           Value="{Binding Thumbnail.AnimationPosition.Position}"
                           Visibility="{Binding Thumbnail.AnimationPosition.Visibility}"/>

              <TextBlock DockPanel.Dock="Bottom" Height="40" 
                         TextWrapping="Wrap" TextTrimming="CharacterEllipsis" 
                         TextAlignment="Center" Text="{Binding Name}"/>

            </DockPanel>
          </DataTemplate>
        </Setter.Value>
      </Setter>
    </Style>

  </TabControl.Resources>

  <TabItem Header="">

    <custom:MediaContainerListView x:Name="MediaContainerView"></custom:MediaContainerListView>

  </TabItem>

</TabControl>

Basically, I have two methods that start/stop the animation for each individual item.

      public async void StartAnimation()
      {
         if( Count > 1 )
         {
            Task thumbnailAnimationTask = AnimationTask( AnimationCancellationToken.Token );
            await thumbnailAnimationTask;
         }
      }

      public void StopAnimation()
      {
         AnimationCancellationToken.Cancel();
      }

I have two issues here.

  1. The ListView seems to realize all the items rather than only those visible or within the realization range. I suspect my XAML somehow kills the Virtualization and have tried many solutions with no success. Mind you I need my ListView to be scalable to the MainWindow's dimensions and not be of fixed hight and width.
  2. I need to call StartAnimation when an item is about to be realized and StopAnimation when it has left the view.

Even though my ListView is not correctly Virtualized, if my understanding of how ObservableCollections work is correct, it's only the UI representation of the items that are managed by the Virtualization and not the items themselves i.e. calling the StartAnimation/StopAnimation from the Constructor/Destructor of the items don't help much as they are called for every single item at the time of creation anyway.

Is there a neat way to somehow inform each item that they are about to be realized or leave the ListView view?

Update: The issue of virtualizing not working correctly was related to the WrapPanel and once I switched to VirtualizingStackPanel it started to work correctly. Unfortunately it's not exactly the same as a WrapPanel and since .NET framework does not offer a VirtualizingWrapPanel, I chose to use the one from here. It's not perfect but it does the job.

2

There are 2 answers

0
Omid Ariyan On BEST ANSWER

I finally solved the problem myself. I knew it wouldn't have to be so complicated and it really isn't. Since I already had created my own ListView inherited class called MediaContainerListView, I could override a few of its virtual methods. Two of them proved to be just what I wanted.

protected override void PrepareContainerForItemOverride( DependencyObject element, object item )

which is called just before the item is about to appear, and

protected override void ClearContainerForItemOverride( DependencyObject element, Object item )

which is called just before the item is about to disappear. So I call the StartAnimation in the first one and the StopAnimation in the second one and it works flawlessly!

2
paparazzo On

You said it was 1/2

Look for a call to GetHashcode
I think it calls it GetHashcode just to find it
By accident I discovered it is called when the item is virtualized

Have an animation that terminates (does not loop)