C# 为什么程序会因为向listview添加大量图像而崩溃?

C# 为什么程序会因为向listview添加大量图像而崩溃?,c#,wpf,listview,C#,Wpf,Listview,这是我的XAML: <ListView MouseDoubleClick="ImageList_MouseDoubleClick" Name="ImageList" Background="#353535" Grid.Row="2" Margin="0 5 0 0"> <ListView.ItemTemplate> <DataTemplate> <Grid Cursor="Hand" Width="200" Height="130

这是我的XAML:

<ListView MouseDoubleClick="ImageList_MouseDoubleClick" Name="ImageList" Background="#353535" Grid.Row="2" Margin="0 5 0 0">
<ListView.ItemTemplate>
    <DataTemplate>
        <Grid Cursor="Hand" Width="200" Height="130" VerticalAlignment="Center" HorizontalAlignment="Left">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition Height="30"/>
            </Grid.RowDefinitions>
            <Image Source="{Binding ImgPath}" Grid.Row="0"/>
            <Label FontSize="14" Foreground="White" Grid.Row="1" HorizontalAlignment="Center" Content="{Binding Name}"/>
        </Grid>
    </DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
    <ItemsPanelTemplate>
        <WrapPanel Orientation="Horizontal" Width="{Binding Path=ActualWidth, ElementName=ImageList}"/>
    </ItemsPanelTemplate>
</ListView.ItemsPanel>

通过这种方式,我将项目添加到ListView:

String[] extensions = { "*.bmp", "*.png", "*.jpg", "*.jpeg", "*.tiff", "*.ico", "*.gif" };
List<String> images = new List<String>();
foreach (String ext in extensions)
{
    try
    {
        images = images.Concat(Directory.GetFiles(path, ext)).ToList();
    }
    catch { }
}
ObservableCollection<ImageBlock> ImageCollection = new ObservableCollection<ImageBlock>();
foreach (String img in images)
    this.ImageList.Items.Add(new ImageBlock(img, System.IO.Path.GetFileName(img)));
String[]扩展={“*.bmp”、“*.png”、“*.jpg”、“*.jpeg”、“*.tiff”、“*.ico”、“*.gif”};
列表图像=新列表();
foreach(扩展名中的字符串扩展名)
{
尝试
{
images=images.Concat(Directory.GetFiles(path,ext)).ToList();
}
捕获{}
}
ObservableCollection ImageCollection=新的ObservableCollection();
foreach(图像中的字符串img)
添加(新的ImageBlock(img,System.IO.Path.GetFileName(img));

这段代码可以很好地处理少量图像,但是当我试图打开一个包含100多张图片的文件夹时,我的程序崩溃了。我做错了什么,或者我能做些什么来优化我的程序

您可以使用异步加载缩略图图像文件的视图模型,还可以通过设置
DecodePixelWidth
DecodePixelHeight
属性来限制其大小

public class ImageData
{
    public string Name { get; set; }
    public ImageSource ImageSource { get; set; }
}

public class ViewModel
{
    public ObservableCollection<ImageData> Images { get; }
        = new ObservableCollection<ImageData>();

    public async Task LoadFolder(string folderName, string extension = "*.jpg")
    {
        Images.Clear();

        foreach (var path in Directory.EnumerateFiles(folderName, extension))
        {
            Images.Add(new ImageData
            {
                Name = Path.GetFileName(path),
                ImageSource = await LoadImage(path)
            });
        }
    }

    public Task<BitmapImage> LoadImage(string path)
    {
        return Task.Run(() =>
        {
            var bitmap = new BitmapImage();

            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                bitmap.BeginInit();
                bitmap.DecodePixelHeight = 100;
                bitmap.CacheOption = BitmapCacheOption.OnLoad;
                bitmap.StreamSource = stream;
                bitmap.EndInit();
                bitmap.Freeze();
            }

            return bitmap;
        });
    }
}

过了很长时间,我找到了几个解决这个问题的办法。对我来说,有简单的方法,也有更难的方法。 我认为最简单的方法是: 在我的问题代码中,我们需要更改以下代码:

<ItemsPanelTemplate>
    <WrapPanel Orientation="Horizontal" Width="{Binding Path=ActualWidth, ElementName=ImageList}"/>
</ItemsPanelTemplate>

“WrapPanel”替换为“Zingstackpanel”上的“WrapPanel”。这样,程序将按照user@mkArtak的建议工作:

此外,在底部区域显示图像缩略图时,您应该使用某种虚拟化,因为您不希望一次加载所有图像。感觉像是一个非常常见的场景,在某个地方有一个可以重用的控件。这样做的目的是只加载从每一侧可见的图像+2。然后,在用户滚动时加载其他内容

据我所知,这是最重要的。当然,您可以调整虚拟化的方式。你可以在互联网上找到有关的信息

还有一个更难的方法:使用async\wait what suggest user@Clemens


可以使用异步加载缩略图图像文件的视图模型,还可以通过设置DecodePixelWidth或DecodePixelHeight属性来限制其大小

public class ImageData
{
    public string Name { get; set; }
    public ImageSource ImageSource { get; set; }
}

public class ViewModel
{
    public ObservableCollection<ImageData> Images { get; }
        = new ObservableCollection<ImageData>();

    public async Task LoadFolder(string folderName, string extension = "*.jpg")
    {
        Images.Clear();

        foreach (var path in Directory.EnumerateFiles(folderName, extension))
        {
            Images.Add(new ImageData
            {
                Name = Path.GetFileName(path),
                ImageSource = await LoadImage(path)
            });
        }
    }

    public Task<BitmapImage> LoadImage(string path)
    {
        return Task.Run(() =>
        {
            var bitmap = new BitmapImage();

            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                bitmap.BeginInit();
                bitmap.DecodePixelHeight = 100;
                bitmap.CacheOption = BitmapCacheOption.OnLoad;
                bitmap.StreamSource = stream;
                bitmap.EndInit();
                bitmap.Freeze();
            }

            return bitmap;
        });
    }
}
此外,我们可以使用这两种方式,我认为这将是最好的方式


非常感谢大家帮助找到这个问题的解决方案。

我建议首先了解问题所在。您是否有任何可以共享的异常消息?我想到的第一个想法是,你的记忆用完了。虽然只有100张图片,但这有点不切实际。尽管如此,为了消除怀疑,请确保您有一个try…catch处理程序来处理崩溃的代码并获得异常消息,这将让您了解发生了什么。如果问题确实是内存问题,并且您确实需要使用现有的内存,请确保您的应用程序是针对x64体系结构编译的。您会收到什么错误消息?平均图像大小是多少?我认为这是一个内存不足的异常。如果你有某种缩略图浏览器,加载图像,缩小它,将缩小的图像添加到列表中,然后释放原始图像…@mkArtak,@Michael我在描述我的问题时犯了一个错误。程序不会崩溃,它会冻结,然后出现一个程序不响应的窗口。这就是没有例外的问题。我也认为足够少的图像数量会使程序崩溃,但确实如此。我不明白为什么。如果您可以并且需要检查这个问题,我可以给您发送一个程序。@mkArtak,好的,我刚离开程序,我有10分钟没有回答,然后它被添加到listview中,但一切都冻结了。为什么这么长时间?@Sefe我调试了程序,它没有显示任何异常或类似的东西。是的,我可以中断程序,但这种情况将在下一次发射中重复。这些概念并不相互排斥。即使使用虚拟化,限制位图大小也很有意义。还可以异步加载图像。除此之外,async/await是一个非常强大的构造,在许多其他情况下都有帮助。不管怎样,你还是应该学习它。@Clemens这就是为什么我说使用这两种解决方案是最好的方法:)@Clemens我注意到,没有位图,冻结();程序无法运行。你能解释一下这个方法在代码中的作用吗?这是因为BitmapImage是在UI线程以外的线程中创建的,即Task.Run使用的线程。要从另一个线程访问BitmapImage,必须将其冻结。WPF为此提供了Freezable类。
<ItemsPanelTemplate>
    <WrapPanel Orientation="Horizontal" Width="{Binding Path=ActualWidth, ElementName=ImageList}"/>
</ItemsPanelTemplate>