Out of memory when populating a datagridview with images
I've used a datagridview many times in the past without any issues, but this is the first time I am trying to display an image in one and I'm coming across an issue that I could use some help with.
The datagridview only has 2 columns associated with it. One for the actual image, and one for the path that indicates where the images exists.
Here is the code that I am using to load images into a datagridview:
Code:
// private void ViewImages_Load(object sender, EventArgs e)
{
try
{
// if item id is not empty, add it to the form title
if (string.IsNullOrEmpty(sItemID) == false)
this.Text = this.Text + sItemID;
int iCell = 0;
// get all the files that exist for the item so we can display them on the screen
string[] files = System.IO.Directory.GetFiles(SelectedImagePath, "[Image]." + sItemID + "_*.jpg");
foreach (string s in files)
{
dataGridView1.Rows.Add(Bitmap.FromFile(s), s);
dataGridView1.Rows[iCell++].Height = 500; // make cell height bigger
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
The first time I load this form I don't encounter an error, but the second time I try to load this form , I get an out of memory error.
I am only trying to populate this datagridview with < 10 images.
Can anyone assist me with figuring how what needs to be done to resolve this issue ?
T U !
Last edited by 2kaud; April 3rd, 2017 at 01:02 PM.
Reason: Fixed code tags
Re: Out of memory when populating a datagridview with images
If the intention is the grid displays images and file names of the selected item, it looks like you aren't clearing the previous items in the grid before adding the new ones.
Re: Out of memory when populating a datagridview with images
Originally Posted by Arjay
If the intention is the grid displays images and file names of the selected item, it looks like you aren't clearing the previous items in the grid before adding the new ones.
Please ignore my ignorance but doesn't closing a form release any memory that was allocated while it was open ?
Re: Out of memory when populating a datagridview with images
Originally Posted by levinll
Please ignore my ignorance but doesn't closing a form release any memory that was allocated while it was open ?
Kind of. The GC will eventually free up memory, but it may not free up the memory in a timely manner. When dealing with system level objects, like bitmaps and files (and even database objects), it is better to deterministically free up the objects when you are finished with them and not rely on the GC to clean up.
Take for example the following database pseudo code...
Code:
var cn = new SqlConnection(...);
var cmd = new SqlCommand("...", cn);
var result = cmd.Execute(...);
Considering that most db providers have a 100 connection limit default, calling the above code in a tight loop will exhaust the available connections. Here's why:
It is true that the GC will recognize that the above code has gone out of scope and when the GC disposes the code, any underlying connections will be closed. The problem is that the GC doesn't run that often, so even if the code has been run and you are finished with the db resources, they are still in use while sitting around waiting for the GC to clean them up.
To take care of the problem, you can leverage the IDisposable interface and the use of the 'using' block:
Code:
using(var cn = new SqlConnection(...))
{
using(var cmd = new SqlCommand("...", cn))
{
var result = cmd.Execute(...);
} // IDisposable called on the SqlCommand object
} // IDisposable called on the SqlConnection object
In the above code, when the code reaches the end of the using block, the objects that are allocated within the using statement are freed.
I'm using a database as an example, but the same issue is true for any .net class that holds onto a handle for an underlying system object like a bitmap or a file.
So in your case, you have a form that creates a grid that contains rows of bitmaps. When you close the form, the form goes out of scope, but the bitmaps aren't cleaned up until the GC runs. You open the form and new bitmaps are created. Close and reopen the form and it doesn't take long to run out of memory.
One way to handle this problem is to alter the code to keep a list of the bitmaps around, so that you can dispose them when the form closes. You could also add an IDisposable interface to the form so you can invoke the form inside a using block. That way, when the form is closed all of its resources are immediately freed.
Re: Out of memory when populating a datagridview with images
Originally Posted by Arjay
Kind of. The GC will eventually free up memory, but it may not free up the memory in a timely manner. When dealing with system level objects, like bitmaps and files (and even database objects), it is better to deterministically free up the objects when you are finished with them and not rely on the GC to clean up.
Take for example the following database pseudo code...
Code:
var cn = new SqlConnection(...);
var cmd = new SqlCommand("...", cn);
var result = cmd.Execute(...);
Considering that most db providers have a 100 connection limit default, calling the above code in a tight loop will exhaust the available connections. Here's why:
It is true that the GC will recognize that the above code has gone out of scope and when the GC disposes the code, any underlying connections will be closed. The problem is that the GC doesn't run that often, so even if the code has been run and you are finished with the db resources, they are still in use while sitting around waiting for the GC to clean them up.
To take care of the problem, you can leverage the IDisposable interface and the use of the 'using' block:
Code:
using(var cn = new SqlConnection(...))
{
using(var cmd = new SqlCommand("...", cn))
{
var result = cmd.Execute(...);
} // IDisposable called on the SqlCommand object
} // IDisposable called on the SqlConnection object
In the above code, when the code reaches the end of the using block, the objects that are allocated within the using statement are freed.
I'm using a database as an example, but the same issue is true for any .net class that holds onto a handle for an underlying system object like a bitmap or a file.
So in your case, you have a form that creates a grid that contains rows of bitmaps. When you close the form, the form goes out of scope, but the bitmaps aren't cleaned up until the GC runs. You open the form and new bitmaps are created. Close and reopen the form and it doesn't take long to run out of memory.
One way to handle this problem is to alter the code to keep a list of the bitmaps around, so that you can dispose them when the form closes. You could also add an IDisposable interface to the form so you can invoke the form inside a using block. That way, when the form is closed all of its resources are immediately freed.
Arjay,
Thank you for explaining to me what is happening when I call this form more than once in a short period of time.
Would it be possible for you to show me how to save the bitmaps so I can dispose of them when this form is closing ? Do I need to add another column in my datagridview to hold them ?
Is this line
Code:
dataGridView1.Rows.Add(Bitmap.FromFile(s),s)
creating a bitmap each time it's called ?
Thanks again for all your assistance.
I'm learning a little bit more about C# every day.
Re: Out of memory when populating a datagridview with images
Here you go. The following is an example of a form application calling another form that displays a data grid view of images from the my documents folder.
It uses a dictionary object to track the images and cleans up previous images before loading new images as well as when the form is closed.
I've created a static FormGrid.Display() method which the main form calls to display the grid form. This method uses a using block so that the Dispose method is called on the form when it is closed.
I would probably use the binding source property to bind to the grid, but I set this up using the dataGridView.Add method as you had in your example.
See the attached zip file for the complete example.
Code:
public partial class FormGrid : Form
{
public FormGrid()
{
InitializeComponent();
LoadImages();
}
/// <summary>
/// Static display method - displays the form as a modal dialog and calls IDispose on the form
/// when the form is closed
/// </summary>
/// <returns></returns>
public static DialogResult Display()
{
using (var form = new FormGrid())
{
return form.ShowDialog();
}
}
/// <summary>
/// Clean up any resources being used. NOTE: moved from the FormGrid.Designer.cs file
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
RemoveImages();
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
/// <summary>
/// Cleans up the images by calling dispose on each image
/// </summary>
private void RemoveImages()
{
foreach (var image in _images.Values)
{
image.Dispose();
}
_images.Clear();
}
/// <summary>
/// Load up some images from the MyDocuments folder. Uses a dictionary to store a key (file path) and value (Image object)
/// so that the image can be disposed of.
/// </summary>
private void LoadImages()
{
RemoveImages();
_dataGridView.Rows.Clear();
foreach (var file in Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "*.jpg"))
{
_images.Add(file, Image.FromFile(file));
}
foreach (var image in _images)
{
_dataGridView.Rows.Add(image.Value, image.Key);
}
}
private void _btnLoad_Click(object sender, EventArgs e)
{
LoadImages();
}
// Dictionary to hold the images (could use a list instead)
private readonly Dictionary<string, Image> _images = new Dictionary<string, Image>();
}
Re: Out of memory when populating a datagridview with images
Originally Posted by Arjay
Here you go. The following is an example of a form application calling another form that displays a data grid view of images from the my documents folder.
It uses a dictionary object to track the images and cleans up previous images before loading new images as well as when the form is closed.
I've created a static FormGrid.Display() method which the main form calls to display the grid form. This method uses a using block so that the Dispose method is called on the form when it is closed.
I would probably use the binding source property to bind to the grid, but I set this up using the dataGridView.Add method as you had in your example.
See the attached zip file for the complete example.
Code:
public partial class FormGrid : Form
{
public FormGrid()
{
InitializeComponent();
LoadImages();
}
/// <summary>
/// Static display method - displays the form as a modal dialog and calls IDispose on the form
/// when the form is closed
/// </summary>
/// <returns></returns>
public static DialogResult Display()
{
using (var form = new FormGrid())
{
return form.ShowDialog();
}
}
/// <summary>
/// Clean up any resources being used. NOTE: moved from the FormGrid.Designer.cs file
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
RemoveImages();
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
/// <summary>
/// Cleans up the images by calling dispose on each image
/// </summary>
private void RemoveImages()
{
foreach (var image in _images.Values)
{
image.Dispose();
}
_images.Clear();
}
/// <summary>
/// Load up some images from the MyDocuments folder. Uses a dictionary to store a key (file path) and value (Image object)
/// so that the image can be disposed of.
/// </summary>
private void LoadImages()
{
RemoveImages();
_dataGridView.Rows.Clear();
foreach (var file in Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "*.jpg"))
{
_images.Add(file, Image.FromFile(file));
}
foreach (var image in _images)
{
_dataGridView.Rows.Add(image.Value, image.Key);
}
}
private void _btnLoad_Click(object sender, EventArgs e)
{
LoadImages();
}
// Dictionary to hold the images (could use a list instead)
private readonly Dictionary<string, Image> _images = new Dictionary<string, Image>();
}
Arjay,
Thank you for taking the time to provide me with the logic I need in order to release the memory that's been allocated when I am loading images into a datagrid. I still get an out of memory message if I try to display too many images, but I don't know if there is a way to get around this.
I do, however, have another question regarding how to display images in a datagrid. I would like to display the images in the size similar to what appears when you use the standard file open dialog box and choose to display images utilizing a extra large icon image.
Currently I am setting the row height as I am adding the rows to my datagrid, but the results are less than ideal.
I see there are a multitude of options regarding the properties associated with the image column (AutoSizeMode, ImageLayout, etc). Can I use one of these options to get the results that I desire ?
* The Best Reasons to Target Windows 8
Learn some of the best reasons why you should seriously consider bringing your Android mobile development expertise to bear on the Windows 8 platform.