CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 7 of 7
  1. #1
    Join Date
    Apr 2017
    Posts
    4

    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

  2. #2
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    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.

  3. #3
    Join Date
    Apr 2017
    Posts
    4

    Re: Out of memory when populating a datagridview with images

    Quote Originally Posted by Arjay View Post
    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 ?

  4. #4
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: Out of memory when populating a datagridview with images

    Quote Originally Posted by levinll View Post
    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.

  5. #5
    Join Date
    Apr 2017
    Posts
    4

    Re: Out of memory when populating a datagridview with images

    Quote Originally Posted by Arjay View Post
    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.

  6. #6
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    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>();
        }
    Attached Files Attached Files

  7. #7
    Join Date
    Apr 2017
    Posts
    4

    Re: Out of memory when populating a datagridview with images

    Quote Originally Posted by Arjay View Post
    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.

    ImageResultsWanted.jpg

    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 ?

    EditColumnChoices.JPG
    Thanks again for your help.

    Lloyd

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured