none
要如何解决listbox开启虚拟化的情况下,加载多个图片的缩略图的进程内存不停增加的问题? RRS feed

  • 问题

  • Listbox启用虚拟化加载100000个textblock,没有问题,瞬间完成。进程内存占用100MB,移动滚动条进程内存也几乎不变。不知道能不能再减小?
    Listbox启动虚拟化加载100个textblock和100个图片

    结果:进程内存就已经比加载100000个textblock要多一些,而且还用了7秒时间才响应完成。为什么列表类控件加载多个图片的缩略图几乎都会有内存增长的问题

    Listbox启动虚拟化加载1000个textblock和1000个图片textblock

    加载图片的响应过程超过1分钟,还没有结束,进程内存还在不停的增加,于是我手动终止程序运行并截了图。

    要如何解决Listview加载图片时的内存不停增加等问题?

    下面是关键代码

    xaml代码部分

    <ListBox x:Name="listBox" HorizontalAlignment="Left" Height="547" Margin="34,32,0,0" VerticalAlignment="Top" Width="662"

    VirtualizingStackPanel.IsVirtualizing="True"   

    VirtualizingPanel.VirtualizationMode="Recycling"  ItemsSource="{Binding Listboxs}"   >

                <ListBox.ItemTemplate>

                    <DataTemplate>

                        <StackPanel>

                         <Image Source="{Binding Images}" Height="100" Width="100"></Image> 

                         <TextBlock Text="{Binding Name}" Height="20"></TextBlock>

                        </StackPanel>

                        </DataTemplate>

                   </ListBox.ItemTemplate>

                <ListBox.ItemsPanel>

                    <ItemsPanelTemplate>

                        <VirtualizingStackPanel Orientation="Vertical" />

                    </ItemsPanelTemplate>

                </ListBox.ItemsPanel>

            </ListBox>

     

    //实体类

    class Tree
        {
            public string Name
            { set; get; }
    public ImageSource Images
            { set; get; }
        }


       加载绑定数据
    for (long i = 0; i <= 1000; i++)
                {
                    Tree a = new Tree();
                   a.Images = getSmallImage(@"E:\ 01.jpg");
                    a.Name = "第" + i + "个数据";
                    Listboxs.Add(a); 
                }
      
    //对图片的处理。(从网上拷贝的代码,ChangeBitmapToImageSource的方法过程并没有研究过,所以不是很明白该方法主要做什么)
      private ImageSource getSmallImage(string fileName)  //将读取到的图片缩略化
            {
                var src = new System.Drawing.Bitmap(fileName);
                var des = new Bitmap(src, src.Width / 10, src.Height / 10); 
                src.Dispose();
                return ChangeBitmapToImageSource(des);
            } 
            [System.Runtime.InteropServices.DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);
    public ImageSource ChangeBitmapToImageSource(Bitmap bitmap)
    
            {
    IntPtr hBitmap = bitmap.GetHbitmap();
    ImageSource wpfBitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
    hBitmap,
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
                if (!DeleteObject(hBitmap))
                {
                    throw new System.ComponentModel.Win32Exception();
                }
                return wpfBitmap;
            }
    //加载绑定数据和图片
       加载绑定数据
    for (long i = 0; i <= 1000; i++)
                {
                    Tree a = new Tree();
                   a.Images = getSmallImage(@"E:\ 01.jpg");
                    a.Name = "第" + i + "个数据";
                    Listboxs.Add(a); 
                }
      
    //对图片的处理。(从网上拷贝的代码,方法的过程并没有去研究过,不明白)
      private ImageSource getSmallImage(string fileName)
            {
    var src = new System.Drawing.Bitmap(fileName);
                var des = new Bitmap(src, src.Width / 10, src.Height / 10); 
                src.Dispose();
                return ChangeBitmapToImageSource(des);
            } 
            [System.Runtime.InteropServices.DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);
    public ImageSource ChangeBitmapToImageSource(Bitmap bitmap)
            {
             IntPtr hBitmap = bitmap.GetHbitmap();
             ImageSource wpfBitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                    hBitmap,
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
                if (!DeleteObject(hBitmap))
                {
                    throw new System.ComponentModel.Win32Exception();
                }
                return wpfBitmap;
            }

    要如何解决listbox开启虚拟化的情况下,加载多个图片的缩略图的进程内存不停增加的问题?主要原因是什么?



    2016年11月3日 11:35

答案

  • 您好 轮回的锯齿,

    >>"ExtentHeight不会使用,能示范一下么?"

    不好意思,上次提供的信息不够详细而且有一些错误。能够指示当前滚钉条所在的垂直位置的属性为VerticalOffset。我们需要根据这个属性来做一些判断来决定需要加载多少个图片。以下为我写的详细的提示。供您参考。

    private void Scrollviewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        //通过其VerticalOffset来预估需要加载多个笔数据。
        //假设你的一条图片数据高度为50px
        //我们需要计算 e.VerticalOffset除以50得到的个数,并加上一定数量的缓存。
        int num = (int)e.VerticalOffset / 50 + 3;
        //接下来就要判断当前List中已经加载的图片的数量,如果已经加载的图片数量大于num
        //则不需要做任何操作,如果小于 num,则需要继续加载一定数量的图片。
    }

    Best Regards,
    Li Wang


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    2016年11月9日 13:13

全部回复

  • 您好 轮回的锯齿,

    通过使用Stopwatch来检测,发现时间和内存主要是消耗在图片的转换和存储上。

    Stopwatch watch = new Stopwatch();
    watch.Start();
    //加载绑定数据
    for (long i = 0; i <= 1000; i++)
    {
        //add tree item
    }
    watch.Stop();
    Console.WriteLine("共耗时: {0}", watch.Elapsed);

    只要有大量数据的话,肯定要消耗大量的时间。建议你在初始的时候只加载文字,图片等到滚动条滚动的时候再加载。

    Best Regards,
    Li Wang


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    2016年11月4日 9:25
  • 要如何解决Listview加载图片时的内存不停增加等问题

    内存和性能同时增加,一方面是图片本身在耗费内存,另一方面还在于getSmallImage在处理图片

    内存增加我也没有方法,性能变慢可以考虑预处理,把图片预先处理好了,比如在系统启动时,或是页面发生特定事件时就处理图片,放到内存中,供ListBox加载

    最后,可以考虑用ants performance profiler分析代码,可以精确检测到哪一行代码的问题。


    专注于.NET ERP/CRM开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    2016年11月5日 9:09
  • 您好 轮回的锯齿,

    通过使用Stopwatch来检测,发现时间和内存主要是消耗在图片的转换和存储上。

    Stopwatch watch = new Stopwatch();
    watch.Start();
    //加载绑定数据
    for (long i = 0; i <= 1000; i++)
    {
        //add tree item
    }
    watch.Stop();
    Console.WriteLine("共耗时: {0}", watch.Elapsed);

    只要有大量数据的话,肯定要消耗大量的时间。建议你在初始的时候只加载文字,图片等到滚动条滚动的时候再加载。

    Best Regards,
    Li Wang


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    建议你在初始的时候只加载文字,图片等到滚动条滚动的时候再加载。

    这个要怎么做到?实体类要怎么设计?


    2016年11月7日 0:15
  • 要如何解决Listview加载图片时的内存不停增加等问题

    内存和性能同时增加,一方面是图片本身在耗费内存,另一方面还在于getSmallImage在处理图片

    内存增加我也没有方法,性能变慢可以考虑预处理,把图片预先处理好了,比如在系统启动时,或是页面发生特定事件时就处理图片,放到内存中,供ListBox加载

    最后,可以考虑用ants performance profiler分析代码,可以精确检测到哪一行代码的问题。


    专注于.NET ERP/CRM开发框架,C/S架构,SQL Server + ORM(LLBL Gen Pro) + Infragistics WinForms

    那如何进行二次加载?先加载textblock,图片先不加载,只加载滚动条所在的图片,滚动条移走后加载的图片对象销毁
    2016年11月7日 0:18
  • 您好 轮回的锯齿,

    >>"这个要怎么做到?实体类要怎么设计?"

    实体类不需要做改变,我们需要先获取ListView中下拉框中的 ScrollViewer控件,然后注册其ScrollChanged事件。

    //获取视觉树上的控件
    private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(obj, i);
            if (child != null && child is childItem)
                return (childItem)child;
            else
            {
                childItem childOfChild = FindVisualChild<childItem>(child);
                if (childOfChild != null)
                    return childOfChild;
            }
        }
        return null;
    }
    ScrollViewer scrollviewer = FindVisualChild<ScrollViewer>(listView);
    
    scrollviewer.ScrollChanged += Scrollviewer_ScrollChanged;

    在ScrollChanged方法中根据ExtentHeight来预估需要加载多少笔数据。比如一笔数据的Height大概是50px, 那么需要加载的笔数是e.ExtentHeight/50.

    private void Scrollviewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        //通过其ExtentHeight来预估需要加载多个笔数据。
        //e.ExtentHeight
    }
    Best Regards,
    Li Wang

    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    2016年11月7日 7:21
  • 您好 轮回的锯齿,

    >>"这个要怎么做到?实体类要怎么设计?"

    实体类不需要做改变,我们需要先获取ListView中下拉框中的 ScrollViewer控件,然后注册其ScrollChanged事件。

    //获取视觉树上的控件
    private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(obj, i);
            if (child != null && child is childItem)
                return (childItem)child;
            else
            {
                childItem childOfChild = FindVisualChild<childItem>(child);
                if (childOfChild != null)
                    return childOfChild;
            }
        }
        return null;
    }
    ScrollViewer scrollviewer = FindVisualChild<ScrollViewer>(listView);
    scrollviewer.ScrollChanged += Scrollviewer_ScrollChanged;

    在ScrollChanged方法中根据ExtentHeight来预估需要加载多少笔数据。比如一笔数据的Height大概是50px, 那么需要加载的笔数是e.ExtentHeight/50.

    private void Scrollviewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        //通过其ExtentHeight来预估需要加载多个笔数据。
        //e.ExtentHeight
    }
    Best Regards,
    Li Wang

    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.


    private void Scrollviewer_ScrollChanged(object sender, ScrollChangedEventArgs e){  

        ExtentHeight不会使用,能示范一下么?

    }



    2016年11月7日 12:30
  • 您好 轮回的锯齿,

    >>"ExtentHeight不会使用,能示范一下么?"

    不好意思,上次提供的信息不够详细而且有一些错误。能够指示当前滚钉条所在的垂直位置的属性为VerticalOffset。我们需要根据这个属性来做一些判断来决定需要加载多少个图片。以下为我写的详细的提示。供您参考。

    private void Scrollviewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        //通过其VerticalOffset来预估需要加载多个笔数据。
        //假设你的一条图片数据高度为50px
        //我们需要计算 e.VerticalOffset除以50得到的个数,并加上一定数量的缓存。
        int num = (int)e.VerticalOffset / 50 + 3;
        //接下来就要判断当前List中已经加载的图片的数量,如果已经加载的图片数量大于num
        //则不需要做任何操作,如果小于 num,则需要继续加载一定数量的图片。
    }

    Best Regards,
    Li Wang


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    2016年11月9日 13:13