【问题标题】:In a WPF ListBox with more than 1000 Image Items the Zoom Images become slow在具有超过 1000 个图像项的 WPF 列表框中,缩放图像变慢
【发布时间】:2010-09-16 00:33:03
【问题描述】:

我在开发照片查看器应用程序时遇到了一个问题。 我使用 ListBox 来显示图像,它包含在 ObservableCollection 中。 我将 ListBox 的 ItemsSource 绑定到 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>

我还将 ListBox 中的 Image'height 与滑块绑定。(滑块的值也绑定到 zoombarmanager.ZoomBarWidth.Width)。 但是我发现如果集合变得更大,例如:包含超过 1000 张图像,如果我使用滑块更改 iamges 的大小,它会变得有点慢。 我的问题是。 1. 为什么会变慢?成为它尝试缩放每个图像,或者它只是因为 notify("Width") 被调用超过 1000 次。 2. 有什么方法可以解决这种问题,让它更快。

PhotoListBoxStyle 是这样的:

    <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~~>

但是如果我使用上面的样式,我必须在 ListBox 之外使用 ScrollViewer,否则我不知道如何获得平滑滚动的滚动条,并且包装面板似乎没有默认滚动条。有人帮忙吗?据说带有滚动查看器的列表框性能很差。

【问题讨论】:

  • 绑定每个图像的高度/宽度是非常低效的,只需在 ItemsPanel 上使用 LayoutTransform(对此效果添加了答案)。

标签: wpf image listbox performance


【解决方案1】:

问题是您的新布局面板是 WrapPanel,它不支持虚拟化!可以创建自己的虚拟化 WrapPanel... 阅读更多 here

还可以阅读有关其他问题的更多信息,例如实现 IScrollInfo here

我还强烈建议您不要创建一个新的控件模板来替换布局面板...而是执行以下操作:

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

这样做的好处是您不需要将列表框包装在滚动查看器中!

[更新] 另请阅读 Josh Smith 的 this 文章!要使 WrapPanel 换行...您还必须记住禁用水平滚动...

<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />

【讨论】:

  • 亲爱的 rudigrobler,你试过这个应用程序并让它打开一个包含 10000 张图像的文件夹吗?我试过了,调整主窗口大小时性能很糟糕。我认为这对于数百张图像可能是一个很好的解决方案,但对于大量图像,列表框无法处理。
  • 我将 Josh Smith 示例中的 robotImages.Add 行更改为 robotImages.Add( BitmapFrame.Create( uri ).Thumbnail );并使目录包含一万张图片。加载图像后,调整大小很费时间。
  • Josh 的示例不使用 VirtualizingWrapPanel...只有使用 VirtualizingWrapPanel,性能才会变得更好!!!
  • @rudigrobler WPF 不包括开箱即用的 VirtualizingWrapPanel
【解决方案2】:

部分问题是它正在加载每个中的完整图像。您必须使用IValueConverter 以缩略图大小打开每个图像,方法是在BitmapImage 上设置DecodePixelWidthDecodePixelHeight 属性。这是我在一个项目中使用的示例...

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();
    }

}

【讨论】:

    【解决方案3】:

    我建议您不要绑定每个单独图像的 Width/Height 属性,而是在 ListBox 的 ItemsPanel 上绑定 LayoutTransform。比如:

    <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,我觉得你的话有道理!我会试试的!
    • 我发现它仍然很慢。
    • 结合六个字母变量和rudigrobler的答案,问题就解决了!谢谢!我之前犯了一个错误,你的解决方案是对的!谢谢!
    【解决方案4】:

    尝试使用 VirtualizingStackPanel.IsVirtualizing="True" 附加属性来虚拟化您的堆栈面板。这应该会提高性能。

    在滚动查看器中使用包含许多项目的列表框是 wpf 中另一个已知的性能问题。如果可以,请尝试摆脱滚动查看器。

    如果您的项目模板有点复杂,您应该考虑使用回收虚拟化模式。这告诉您的列表框重用现有对象,而不是一直创建新对象。

    【讨论】:

      【解决方案5】:

      您的 PhotoListBoxStyle 样式是什么样的?如果它正在更改 ListBox 的 ItemsPanelTemplate,那么您的 ListBox 很可能没有使用 VirtualizingStackPanel 作为其底层列表面板。非虚拟化 ListBox 包含许多项目时要慢得多。

      【讨论】:

      • 我现在添加它,您知道如何改进它吗?谢谢!
      【解决方案6】:
      1. 我不熟悉这个组件,但总的来说,列表框一次可以显示的项目数量会受到限制。

      2. 解决此类问题的一种方法是将控件中加载的图像数量保持在控件可以在可接受的性能水平上显示的数量范围内。执行此操作的两种技术是分页或动态加载。

      在分页中,您可以添加控件以在离散的图片块之间切换,例如,一次 100 个,带有前进和后退箭头,类似于导航数据库记录。

      使用动态加载,您可以在后台实现分页,当用户滚动到最后时,应用程序会自动加载下一批图片,甚至可能删除一批旧图片以保持响应能力合理的。发生这种情况时可能会稍作停顿,并且可能需要进行一些工作以将控件保持在正确的滚动点,但这可能是一个可以接受的折衷方案。

      【讨论】:

      • 谢谢,我认为这种方法可行,但也许需要做很多工作。谢谢!
      猜你喜欢
      • 1970-01-01
      • 2010-10-12
      • 1970-01-01
      • 2015-12-30
      • 2021-05-01
      • 1970-01-01
      • 2012-09-20
      相关资源
      最近更新 更多