views:

7103

answers:

6

I met a problem when deveoping a photo viewer application. I use ListBox to Show Images, which is contained in a ObservableCollection. I bind the ListBox's ItemsSource to the ObservableCollection.

  <DataTemplate DataType="{x:Type modeldata:ImageInfo}">
        <Image 
            Margin="6"
            Source="{Binding Thumbnail}"
            Width="{Binding ZoomBarWidth.Width, Source={StaticResource zoombarmanager}}"
            Height="{Binding ZoomBarWidth.Width, Source={StaticResource zoombarmanager}}"/>
  </DataTemplate>

<Grid DataContext="{StaticResource imageinfolder}">
    <ScrollViewer
        VerticalScrollBarVisibility="Auto" 
        HorizontalScrollBarVisibility="Disabled">
        <ListBox Name="PhotosListBox"
            IsSynchronizedWithCurrentItem="True"
            Style="{StaticResource PhotoListBoxStyle}" 
            Margin="5"
            SelectionMode="Extended" 
            ItemsSource="{Binding}" 
           />
    </ScrollViewer>

I also bind the Image'height in ListBox with a slider.(the slider's Value also bind to zoombarmanager.ZoomBarWidth.Width). But I found if the collection become larger, such as: contains more then 1000 images, If I use the slider to change the size of iamges, it become a bit slow. My Question is. 1. Why it become Slow? become it tries to zoom every images,or it just because notify("Width") is invoked more than 1000 times. 2. Is there any method to solve this kind of problem and make it faster.

The PhotoListBoxStyle is like this:

    <Style~~ TargetType="{x:Type ListBox}" x:Key="PhotoListBoxStyle">
        <Setter Property="Foreground" Value="White" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListBox}" >
                    <WrapPanel 
                        Margin="5" 
                        IsItemsHost="True" 
                        Orientation="Horizontal" 
                        VerticalAlignment="Top"                             
                        HorizontalAlignment="Stretch" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style~~>

But If I use the Style above, I have to use ScrollViewer outside ListBox, otherwise I have no idea how to get a smooth scrolling scrollerbar and the wrappanel seems have no default scrollerbar. Anyone help? It is said listbox with scrollviewer has poor performance.

+2  A: 
  1. I am not familiar with this component, but in general there is going to be limitations on the number of items a listbox can display at one time.

  2. A method to solve this kind of problem is to keep the number of images loaded in the control within the number the control can display at acceptable performance levels. Two techniques to do this are paging or dynamic loading.

In paging, you add controls to switch between discrete blocks of pictures, for example, 100 at a time, with forward and back arrows, similar to navigating database records.

With dynamic loading, you implement paging behind the scenes in such a way that when the user scrolls to the end, the application automatically loads in the next batch of pictures, and potentially even removes a batch of old ones to keep the responsiveness reasonable. There may be a small pause as this occurs and there may be some work involved to keep the control at the proper scroll point, but this may be an acceptable trade-off.

J c
Thank you I think this method work but Maybe Lots of work need be done.Thank you!
A: 

What does your PhotoListBoxStyle style look like? If it's changing the ListBox's ItemsPanelTemplate then there's a good chance your ListBox isn't using a VirtualizingStackPanel as its underlying list panel. Non-virtualized ListBoxes are a lot slower with many items.

Matt Hamilton
I add it now, do you have some idea how to improve it ? Thank you!
A: 

try to virtualize your stackpael with the VirtualizingStackPanel.IsVirtualizing="True" attached property. this should increase performance.

using a listbox with many items in a scrollviewer is another known performance issue within wpf. if you can, try to get rid of the scrollviewer.

if your itemtemplates are kinda complex you should consider using the Recycling VirtualizationMode. this tells your listbox to reuse existing objects and not create new ones all the time.

Joachim Kerschbaumer
A: 

Part of the problem is that it is loading the full image in each. You have to use an IValueConverter to open each image in a thumbnail size by setting either the DecodePixelWidth or DecodePixelHeight properties on the BitmapImage. Here's an example I use in one of my projects...

class PathToThumbnailConverter : IValueConverter {
    public int DecodeWidth {
        get;
        set;
    }

    public PathToThumbnailConverter() {
        DecodeWidth = 200;
    }

    public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
        var path = value as string;

        if ( !string.IsNullOrEmpty( path ) ) {

            FileInfo info = new FileInfo( path );

            if ( info.Exists && info.Length > 0 ) {
                BitmapImage bi = new BitmapImage();

                bi.BeginInit();
                bi.DecodePixelWidth = DecodeWidth;
                bi.CacheOption = BitmapCacheOption.OnLoad;
                bi.UriSource = new Uri( info.FullName );
                bi.EndInit();

                return bi;
            }
        }

        return null;
    }

    public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
        throw new NotImplementedException();
    }

}
Joel B Fant
A: 

I would recommend you not bind the Width/Height property of each individual image, but rather you bind a LayoutTransform on the ListBox's ItemsPanel. Something like:

<ListBox.ItemsPanel>
   <ItemsPanelTemplate>
      <StackPanel>
        <StackPanel.LayoutTransform>
           <ScaleTransform
               ScaleX="{Binding Path=Value, ElementName=ZoomSlider}"
               ScaleY="{Binding Path=Value, ElementName=ZoomSlider}" />
        </StackPanel.LayoutTransform>
      </StackPanel>
   </ItemsPanelTemplate>
</ListBox.ItemsPanel>
sixlettervariables
Thank you sixlettervariables, I think your words make sense! I will try it!
I found the it is still very slow.
Combine sixlettervariables and rudigrobler's answer, the problem is solved! Thanks! I made a mistake before, your solution is right!Thank you!
+4  A: 

The problem is that your new Layout Panel is the WrapPanel and it doesn't support Virtualization! It is possible to create your own Virtualized WrapPanel... Read more here

Also read more about other issues like the implementation IScrollInfo here

I also highly recommend that your do not create a new control template just to replace the layout panel... Rather do the following:

<ListBox.ItemsPanel>
   <ItemsPanelTemplate>
      <WrapPanel Orientation="Horizontal"/>
   </ItemsPanelTemplate>
</ListBox.ItemsPanel>

The advantage of doing this is that you do not need to wrap your listbox in a scrollviewer!

[UPDATE] Also read this article by Josh Smith! To make the WrapPanel wrap... you also have to remember to disable horizontal scrolling...

<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
rudigrobler
Dear rudigrobler, Have you tried this application and made it open a folder contain 10000 Images?I tried, and the performace is terrible when resizing the main window. I think may be it is a good solution for hundreds of images, but for larger amount images, listbox can not handle it.
I change the robotImages.Add line in Josh Smith's sample into robotImages.Add( BitmapFrame.Create( uri ).Thumbnail ); and made the directory contain ten thousands images. After images are loaded, the resizing is time comusing.
The sample is described in http://www.codeproject.com/KB/WPF/CustomListBoxLayoutInWPF.aspx
Josh's sample do not use the VirtualizingWrapPanel... The performance WILL ONLY GET BETTER IF YOU USE THE VirtualizingWrapPanel!!!
rudigrobler