Displaying an Image from a byte[] in an ItemControl leads to huge memory leak
See original GitHub issue- .NET Core Version: 6.0 or any other .NET core version I could try
- Windows version: 10
- Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes
Problem description:
I need to display an image which is in a form of byte[]
(array of byte) in a WPF ItemsControl
, but everything I could try leads to memory leak. I use what I think to be the canonical MVVM approach to do so. To enable the Image
to display the byte[]
, I convert it in the ViewModel into an ImageSource
using a MemoryStream
(example below). I’ve tried some more exotic ways to get this byte[]
converted to an ImageSource
, using Bitmap
and GDI+ in between. But this always end up with the same conclusion: the memory fills in and never gets freed.
Minimal repro:
MainWindow.xaml
<Window x:Class="Toy.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Toy"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<Button Content="Collect Garbage" Click="gcButton_Click"/>
<Button x:Name="wxButton" Click="wxButton_Click"/>
<ItemsControl x:Name="wxToy" ItemsSource="{Binding}" HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Bisque" Width="100" Height="100" BorderThickness="2">
<Image Source="{Binding Data}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Toy
{
internal class ToyItem : INotifyPropertyChanged
{
#region Boilerplate INotifyPropertyChanged
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler? PropertyChanged;
#endregion
/// <summary>
/// Build an image from Picture bytes
/// </summary>
/// <param name="imageData">Picture as array of bytes</param>
/// <returns>Pictures as BitmapImage</returns>
public static ImageSource? BitmapFromRaw(byte[]? imageData)
{
if (imageData == null) return null;
var image = new BitmapImage();
var mem = new MemoryStream(imageData);
image.BeginInit();
//image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
//image.CacheOption = BitmapCacheOption.None;
//image.UriSource = null;
image.StreamSource = mem;
image.EndInit();
//mem.Close();
//mem.Dispose();
image.Freeze();
return image;
}
public ImageSource? Data
{
get { return _Data; }
set
{
if (value != _Data)
{
_Data = value;
OnPropertyChanged();
}
}
}
private ImageSource? _Data;
public ToyItem ()
{
Data = BitmapFromRaw(Properties.Resources.pexels_jonathan_faria_8581946);
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<ToyItem> ToyList = new ObservableCollection<ToyItem>();
int Counter = 0;
public MainWindow()
{
InitializeComponent();
wxToy.DataContext = ToyList;
wxButton.Content = Counter;
}
private void gcButton_Click(object sender, RoutedEventArgs e)
{
GC.Collect();
}
private void wxButton_Click(object sender, RoutedEventArgs e)
{
wxButton.Content = ++Counter;
ToyList.Clear();
for (int i = 0; i < 5; i++)
{
ToyList.Add(new ToyItem());
}
}
}
}
You could see some attempts to rweak the code with the comments //
References
You can download a ready-to-use solution of this here
I’ve also ask the question here This is to apply in the Desktop application I’m developping here The problem seems similar but is different to #1082 because it needs byte[] and ItemsControl.
Additional notes
This example uses a resource (Properties.Resources.pexels_jonathan_faria_8581946) to get an image in byte[]. This is only for simplifying the example, don’t bother to explain how I could workaround the problem by using a jpg image and URI instead. In the real application, I have no choice but getting the images from byte[] arrays…
If you run this code, you could hit a button to generate and show 5 images at a time in a MVVM scheme in an ItemsControl, just the way we are suppose to do it in WPF/C# I guess… Clicking the button several times will bring this deadly simple code to progressively eat Gigabytes of memory.
I really do hope this is not a bug and that I’m doing something wrong here. Please point out on what I’m doing bad here…
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:5 (3 by maintainers)
Top GitHub Comments
Thanks @lindexi and @gurpreet-wpf ! I really value your responsive reply with a pointer to a potential solution.
However, the #2397 trick is not really something addressing a pure MVVM approach… In my example, I only control the View Model contrary to the other issue where it instantiates the controls directly in the code. I doubt we could call that a duplicate then.
Anyway, I’ll try this trick on my side and let you know if this solves the issue (and how I made it into a MVVM). If this works, that’s a workaround I can use in my application (and maybe that could unblock other people who face the issue) so it would at least solve the use-case.
We keep in touch !
@Starwer I hope we can mark this as duplicate of #2397 and close it?