none
gridview中绑定大量数据,程序直接崩溃掉,救命! RRS feed

  • 问题

  • 我打算做一个类似于win8的照片app一样的东西,但是遇到了很多问题。

    1. 我用gridview绑定了一个数据源,数据源是图片库里的大量的照片,然后运行的时候,程序会崩溃掉。

    2. 我是用了SemanticZoom加两个gridview来做小图浏览和普通大小的图片浏览,然后想再做一种全屏浏览图片的模式(与win8的照片app功能类似)。现在遇到的问题是: 我鼠标放在普通大小的图片上,按ctrl+鼠标滚动,怎样获得当前鼠标所在的gridviewitem的信息呢? 或者有其他的办法可以将鼠标所在位置处的图片放大到全屏?

    取图片的代码:

     itemData data = new itemData();

    StorageFile itemfile = (StorageFile)itemtemp;
     IRandomAccessStream fileStream = await itemfile.GetThumbnailAsync(ThumbnailMode.PicturesView, 480, ThumbnailOptions.ResizeThumbnail);
    data.stream = fileStream;
     ImageProperties imagep = await itemfile.Properties.GetImagePropertiesAsync();
     data.file = itemfile;
    data.width = (int)imagep.Width;
    data.height = (int)imagep.Height;
    data.stThumbStream = await itemfile.GetThumbnailAsync(ThumbnailMode.PicturesView, 80, ThumbnailOptions.UseCurrentScale);
    data.path = itemfile.Path;
     flist.Add(data);

    imageconverter的代码:

     

     public override object Convert(object value, Type targetType, object parameter, string culture)
            {
                if (value != null)
                {
                    selectphotos1._1.itemData data = (selectphotos1._1.itemData)value;
                    IRandomAccessStream thumbnailStream = data.stream as IRandomAccessStream;
                    thumbnailStream.Seek(0);
                    BitmapImage bi = new BitmapImage();
                    bi.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                 
                    if (thumbnailStream != null)
                    {
                            bi.SetSource(thumbnailStream);
                    }
                    return bi;

                }
                return DependencyProperty.UnsetValue;
            }

    如果图片很多,很大,  bi.SetSource(thumbnailStream)处会出错,程序会崩溃掉,请问是什么原因,要怎么解决呢?

    2012年6月20日 12:18

答案

全部回复

  • 你参考下下面这个帖子中我的代码:http://social.msdn.microsoft.com/Forums/zh-CN/metroappzhcn/thread/77155b1e-c271-4fef-a477-822ea48fda1b/

    通过 VisualTreeHelper.FindElementsInHostCoordinates 方法可以找到你鼠标点击位置的下方元素。

    至于图片过多过大,我的建议是不要一次把所有图片都加载,你需要进行一些虚拟化操作,和异步操作,让用户滚动去决定是否要加载更多的图片。过大,我想即使满屏的图片也不会有很大,只有一种可能就是本身图片分辨率很高,所以这种情况下,你需要考虑这个图片是否合适,是否需要进行大小缩小。


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    • 已标记为答案 vickie shi 2012年6月26日 6:45
    • 取消答案标记 vickie shi 2012年7月22日 15:45
    • 已标记为答案 vickie shi 2012年8月3日 8:03
    2012年6月21日 8:53
    版主
  • 多谢,第一个问题顺利解决,关于第二个问题

    请问一下,虚拟化操作是怎样的?我读图片的时候都是异步的读取的。

    2012年6月25日 12:22
  • 异步读取并不是虚拟化,我们所讲的虚拟化从字面上讲是指为一些目前还不需要的资源和数据提供一个假的轻量的替代。 当要使用时再进行载入。

    所以虚拟化在这里可以分为两种表现形式,数据虚拟化和UI虚拟化。

    关于数据虚拟化,最典型的例子就是针对拥有大量数据的数据源,我们会让UI发生滚动时候去异步地请求下一批数据,比如我们上的微博客户端,每次他会请求一部分数据,当我们拉动到最下端,他又会请求下一批。 这样就能保证在数据加载上有较高的性能,同时对数据存储空间也有高的利用率。

    UI虚拟化,通常比较直观的就是UI集合的分组了,我们通常会见到表格的分组形式,当有大量数据的时候,通过分组一方面提供出料好的用户体验,另一方面提高当前页面的绘制性能。  同样UI虚拟化,在WPF/Silverlght/Metro中,都有了直接的体现,我们的ItemsControl如果使用了如 VirtualizingStackPanel 为ItemsPanel,他本身以及提供了虚拟化的动作,他会在我们ItemsControl界面还不需要显示一些Items的时候仅计算这些Item的可能占位大小,并不去为他们生成ItemContainer。 而仅到滚动发生并且这些Items需要进入显示区域时候,才为其生成ItemContainer进行包装,同时他们也会帮助处理掉已经滚动出显示区域的那些ItemContainer。


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年6月26日 5:24
    版主
  • 谢谢你,第二个问题已经解决。我没有用virtualizingStackPanel,用的是VariableSizedWrapGrid,似乎也可以达到Ui虚拟化的效果?

    先将空List绑定给gridview.ItemsSource,然后再往List里填充数据。运行时,系统会先把当前已经读取出来的图片显示出来。滚动条往后拖,才会继续读取后面的图片。但是用这种方法,不知道会不会自动处理掉已经滚动出显示区域的那些ItemContainer。

    2012年6月26日 6:45
  • 会的,具体的模式由 VirtualizingStackPanel.VirtualizationMode 这个附加属性决定的。

    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年6月26日 7:19
    版主
  • 不好意思,我用VariableSizedWrapGrid代替virtualizingStackPanel,当需加载的图片变多,如需加载1.5G的照片,在滚动条往后拖时,仍然会发生内存不够的问题,并且会crash掉。

     我的代码是这样的:

       <common:LayoutAwarePage.Resources>

            <Style x:Key="TooShortItem" TargetType="GridViewItem">
                <Setter Property="VariableSizedWrapGrid.ColumnSpan" Value="2"/>
                <Setter Property="VariableSizedWrapGrid.RowSpan" Value="4"/>
                <Setter Property="Width" Value="240" />
                <Setter Property="Height" Value="480" />
            </Style>
           
            <Style x:Key="ShortItem" TargetType="GridViewItem">
                <Setter Property="VariableSizedWrapGrid.ColumnSpan" Value="3"/>
                <Setter Property="VariableSizedWrapGrid.RowSpan" Value="5"/>
                <Setter Property="Width" Value="360" />
                <Setter Property="Height" Value="600" />
            </Style>
               
            <Style x:Key="NormalItem" TargetType="GridViewItem">
                <Setter Property="VariableSizedWrapGrid.ColumnSpan" Value="6"/>
                <Setter Property="VariableSizedWrapGrid.RowSpan" Value="4"/>
                <Setter Property="Width" Value="720" />
                <Setter Property="Height" Value="480" />
            </Style>
           
            <Style x:Key="LongItem" TargetType="GridViewItem">
                <Setter Property="VariableSizedWrapGrid.ColumnSpan" Value="14"/>
                <Setter Property="VariableSizedWrapGrid.RowSpan" Value="3"/>
                <Setter Property="Width" Value="1680" />
                <Setter Property="Height" Value="360" />
            </Style>
               
            <local:FileFolderInformationStyleSelector x:Key="FileFolderInformationStyleSelector"
                TooShortStyle="{StaticResource TooShortItem}"
          ShortStyle="{StaticResource ShortItem}"
          NormalStyle="{StaticResource NormalItem}"
                LongStyle="{StaticResource LongItem}"/>

       </common:LayoutAwarePage.Resources>

      <GridView
                         AutomationProperties.AutomationId="ItemGridView"
                         AutomationProperties.Name="Items"

                         Margin="0,-4,0,0"
                         Padding="30,30,30,0"

                         ItemTemplateSelector="{StaticResource FileFolderInformationTemplateSelector}"
                         SelectionMode="Multiple" SelectionChanged="select"
                         ItemContainerStyleSelector="{Binding Mode=OneWay, Source={StaticResource FileFolderInformationStyleSelector}}" VerticalAlignment="Center" >

                        <GridView.ItemsPanel>
                            <ItemsPanelTemplate>                    
                                    <VariableSizedWrapGrid MaximumRowsOrColumns="5" ItemHeight="120" ItemWidth="120" HorizontalAlignment="Left" VerticalChildrenAlignment="Center">
                                    </VariableSizedWrapGrid>
                            </ItemsPanelTemplate>
                        </GridView.ItemsPanel>
                    </GridView>

    我应该怎样用virtualizingStackPanel来做呢?

                                           
    2012年6月26日 8:42
  • 因为你的需求决定了你只能使用 VariableSizedWrapGrid,来显示如StartPage那样的不同大小的分块。

    所以我的建议是更多的去使用数据虚拟化,根据判断滚动条位置 来决定是否要加载更多的图片进来。当然这个时候,你需要做的是要将前面已经滚动出显示区域的图片可以从绑定的数据集合中Remove,让其被回收,下次滚动发生时再次载入。

    看一下这个例子:StorageDataSource and GetVirtualizedFilesVector sample


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年6月26日 9:32
    版主
  • 请问一下:gridview里面没有加入scollviewer控件,仍然是可以显示滚动条的,那我要怎样来实时获取到滚动条的位置呢?
    2012年7月13日 6:08
  • gridview的默认模板中是包含一个 scollviewer的,你可以用可视树来找到这个内部的scollviewer. 

    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年7月13日 6:33
    版主
  • StorageDataSource and GetVirtualizedFilesVector sample 例子里的取图方法,在我的工程上有一个比较矛盾的问题。数据是虚拟化的,需要的时候才load, 而我的  ItemContainerStyle是需要一开始就选择好的,在运行StyleSelector的时候,绑定的数据还是null,没有办法根据绑定的数据判断使用哪一种ItemContainerStyle. 请问这个问题有没有什么好的方法可以解决?

    2012年7月18日 3:32
  • 选择ItemContainerStyle需要基于你绑定的数据,如果没有绑定数据,这样的选择就没有意义,变得你之后还需要再次选择。所以我的想法是,为何需要一开始就选择好,或者你可以考虑将所需要选择的Style 直接由绑定数据提供,变为绑定数据对象的一个属性。

    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年7月18日 4:01
    版主
  • itemcontainerstyle是可以动态调整的吗?比如说为了做成数据虚拟化,我绑定数据的代码是这样的:

     var fileInformationFactory = new FileInformationFactory(fileQuery, ThumbnailMode.SingleItem, 1000, ThumbnailOptions.UseCurrentScale, true);
                gridview.Source = fileInformationFactory.GetVirtualizedFilesVector();

    这样绑定的数据类型是fileinformation,然后我需要根据每张图片的宽高来配置合适它的containerstyle。最后显示的效果是http://social.msdn.microsoft.com/Forums/zh-CN/metroappzhcn/thread/aaeaee49-0110-489f-adb5-27fc3ec5174f上面紧凑排列的效果,那我要怎么做?在什么方法里面来配置itemcontainerstyle呢?itemcontainerstyle又要如何配置呢?

    2012年7月18日 4:28
  • 你并不需要先写好这些ContainerStyle放到资源中,你可以在 StyleSelector 的代码中去创建Style 或者反序列化一个Style XAML代码来为你的Item指定Style。注意我那个帖子中的 MyStyleSelector 这个类型,是一个StyleSelector的实现,我们可以在其中做文章。

    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年7月18日 9:29
    版主
  • 这种方法貌似还是有点问题,我是直接用StorageDataSource and GetVirtualizedFilesVector sample 来做测试的,修改的代码如下:
    xaml:
      <GridView
                x:Name="itemGridView"
                AutomationProperties.AutomationId="ItemGridView"
                AutomationProperties.Name="Items"
                Grid.Row="1"
                Margin="0,-140,0,0"
                Padding="116,0,40,46"
                ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
                ItemTemplateSelector="{StaticResource FileFolderInformationTemplateSelector}"
                 ItemContainerStyleSelector="{Binding Mode=OneWay, Source={StaticResource FileFolderInformationStyleSelector}}"
                SelectionMode="None">
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VariableSizedWrapGrid  MaximumRowsOrColumns="1" ItemHeight="480" ItemWidth="120"/>
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>
            </GridView>
               
    cs:
       public class FileFolderInformationStyleSelector : StyleSelector
        {
            private string strStyleFormat = "<Style  xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" TargetType=\"GridViewItem\">" +
               "<Setter Property=\"VariableSizedWrapGrid.ColumnSpan\" Value=\"{1}\"/>" +
               "<Setter Property=\"VariableSizedWrapGrid.RowSpan\" Value=\"1\"/>" +
               "<Setter Property=\"Width\" Value=\"{0}\" />" +
               "<Setter Property=\"Height\" Value=\"{2}\" />" +
           "</Style>";

     

            protected override Style SelectStyleCore(object item, DependencyObject container)
            {

                if (item != null)
                {
                    FileInformation file = item as FileInformation;
                    if (file != null)
                    {
                        uint height = 480;
                        if (file.ImageProperties.Width / file.ImageProperties.Height > 3)
                        {
                            height = 300;
                        }
                        uint width = height * file.ImageProperties.Width / file.ImageProperties.Height;

                        string strStyle = String.Format(strStyleFormat, width, (width + 119) / 120, height);
                        return XamlReader.Load(strStyle) as Style;
                    }
                    else
                    {
                        FolderInformation folder = item as FolderInformation;
                        uint width = 360;
                        string strStyle = String.Format(strStyleFormat, width, 3, 480);
                        return XamlReader.Load(strStyle) as Style;
                    }

                }
                return null;

            }
        }

    如果不动态修改containerstyle,在资源中把item的宽高写定100*480, 读取400张照片,内存占用量在100M左右,如果用上面的代码来动态修改,那么内存占用在1G以上。这样性能就很差了。不知道我的代码的问题再哪里呢?
    这个性能的问题好纠结~~

    2012年7月20日 2:19
  • 等了好几天都没用回复,问题急需解决,所以再顶一下我的帖子,希望可以尽快得到答复,谢谢!
    2012年7月24日 9:11
  • 代码没有问题, 问题在于你的数量过大。我不建议你要读这么多 400张,因为VariableSizedWrapGrid 在布局大量元素时会进行非常复杂的计算,他要考虑每个元素的周围布局空间是否符合一个元素。

    所以这里我想到你还是:

    1. 尽可能对你的400张和大量的数据进行数据层面的分组,不要一次载入。

    2. 不要使用过多的大小值。

    考虑下 LoadMoreItemsAsync? http://social.msdn.microsoft.com/Forums/en-US/winappswithcsharp/thread/8f7a1a5b-6a8d-4971-8f38-2075295298b5


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    • 已标记为答案 vickie shi 2012年8月3日 8:05
    2012年7月24日 10:46
    版主
  • 弱弱的问一下,怎样手动去释放这些已经加载的内存呢? StorageDataSource and GetVirtualizedFilesVector sample 这个例子里面,如果不停的交替进行显示图片,返回,显示图片的操作,内存占用量同样会不断上升。我检查了一下代码,似乎没有显式的做过内存释放的工作。每次加载图片都没有被释放掉。现在我想返回的时候,把原先占用的内存释放出来,请问这个要怎样做?
    2012年7月25日 7:35
  • 内存的管理在这里通常由GC去做,我们需要做的就是及时将你不需要的对象断开其引用,因为只有没有被任何对象所引用,GC才会去回收这些对象。所以你这里需要做的,就是在你不使用的图片,比如BitmapImage对象,则将其从Image控件上断开,设置Image.Source为null, 然后如果这个BitmapImage还有被绑定的对象,也要将其断开,然后设置此BitmapImage为null, 一般这样这图片所占空间会被GC释放. 当然你也可以手动调用 GC.Collect 这个和.Net下类似。

    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年7月25日 8:34
    版主
  • image是直接绑定到数据源的一个属性上的,属性更改,image会跟着改变,但是不会调用unload事件,那么怎样来释放image上原先占用的内存呢? 也就是说image的数据源改变的时候,image会发出什么通知吗?
    2012年7月31日 13:04
  • 比如说,你有些Item已经不需要显示了,那么你可以直接将他们从数据源集合中删除,同时将删除的BitmapImage设为null。 而对于Image控件,一旦数据源项目被删,她所在的ItemContainer也就会被移出,数据源设为null,则绑定就会失效,即Image可能被回收。

    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us

    2012年8月1日 8:56
    版主