Search code examples
.netvb.netdatagridviewdatagridviewimagecolumn

Generic error in GDI+ saving Datagridview data with images to XML


First off - thanks for all the information and snippets around here. Appreciate it. My problem;

I'm trying to save some datagridview data, including images, to an XML file. Then trying to read it back in the grid again. I'm using a dataset & table (unbounded) for easy XML writing as as far as I'm aware - binding doesnt work with an imagecolumn.

I can save the data, then read it in again. However - when I try to save the data again - it fails on the following line in "Sub Savetofile":

Img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg) 

with the following error:

An unhandled exception of type System.Runtime.InteropServices.ExternalException' occurred in System.Drawing.dll Additional information: A generic error occurred in GDI+.

Any ideas what I'm missing?

 Private Sub SaveToFile(sender As Object, e As EventArgs) Handles SaveToolStripMenuItem.Click
    Dim rows As Integer = DataGridView1.Rows.Count - 1
    Dim cols As Integer = DataGridView1.Columns.Count - 1
    Dim MyByte As Byte() = Nothing
    Dim Img As Image = Nothing
    Dim ms = New MemoryStream()
    DataSet1.Observations.Rows.Clear()

    For i = 0 To rows
        For j = 0 To cols
            If j = 0 Then
                Img = DataGridView1.Rows(i).Cells(j).Value
                Img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg)
                MyByte = ms.ToArray()
                DataSet1.Observations.Rows.Add.Item(1) = Compress(MyByte)
                ms.close()
            ElseIf j >= 1 Then
                If DataGridView1.Rows(i).Cells(j).Value IsNot Nothing Then
                    DataSet1.Observations.Rows(i).Item(j + 1) = DataGridView1.Rows(i).Cells(j).Value.ToString
                End If
            End If
        Next
    Next
    File.Delete("C:\test2.quad")
    DataSet1.WriteXml("C:\test2.quad")
End Sub
Private Sub OpenFile(sender As Object, e As EventArgs) Handles OpenToolStripMenuItem.Click

    Dim ms = New MemoryStream()
    Dim MyByte As Byte()

    DataSet1.Clear()
    DataGridView1.Rows.Clear()
    DataSet1.ReadXml("C:\test2.quad")
    Dim rows As Integer = DataSet1.Observations.Rows.Count - 2
    Dim cols As Integer = DataSet1.Observations.Columns.Count - 1

    For i = 0 To rows
        For j = 1 To cols
            If j = 1 Then
                MyByte = Decompress(DataSet1.Observations.Rows(i).Item(1))
                Dim stream As New MemoryStream(MyByte)
                DataGridView1.Rows.Add(Image.FromStream(stream))
                stream.Close()
            ElseIf j >= 2 And DataSet1.Observations.Rows(i).Item(j) IsNot Nothing Then
                DataGridView1.Rows(i).Cells(j - 1).Value = DataSet1.Observations.Rows(i).Item(j).ToString
            End If
        Next
    Next


End Sub

Solution

  • That error is a catch-all which includes anything from file access denied to running out of resources. In this case it is because you are reusing the same MemoryStream over and over.

    But you do not have to manually convert the image and "compress" them. If the DataTable has a Byte() column, the DataGridView will know how to convert that for use as an Image. The WriteXML method will also automatically encode the bytes as a Base64 string (and read it back).

    Sample:

    Dim dtX = New DataTable("dtX")
    
    Dim imgs As Image() = {My.Resources.ballblack, My.Resources.ballblue,
                     My.Resources.ballgreen, My.Resources.ballorange,
                     My.Resources.ballpurple, My.Resources.ballred,
                     My.Resources.ballyellow}
    
    ' columns to use
    dtX.Columns.Add(New DataColumn("Name", GetType(String)))
    dtX.Columns.Add(New DataColumn("Descr", GetType(String)))
    dtX.Columns.Add(New DataColumn("Img", GetType(Byte())))
    
    Dim dr As DataRow
    Dim g As Int32
    For n As Int32 = 0 To 9
        dr = dtX.NewRow
    
        dr(0) = RD.GetNames(2)
        dr(1) = RD.GetLorem(40)
        g = RNG.Next(0, 7)          ' pick random index
        Using ms As New MemoryStream
            imgs(g).Save(ms, ImageFormat.Png)
            dr(2) = ms.ToArray()
        End Using
    
        dtX.Rows.Add(dr)
    Next
    
    ' "Before"
    dgv1.DataSource = dtX
    ' save to XML
    dtX.WriteXml("C:\Temp\DTX.xml", XmlWriteMode.WriteSchema)
    
    • RD is just a random data generator. Ignore it.
    • XmlWriteMode.WriteSchema is very important. When you read back the data, you want the destination DataTable to know to convert that Base64 string back to a Byte array rather than displaying iVBORw0KGgoAAAANSUhEUgAAABA...
    • Also note the Using block. This will dispose of the MemoryStream at the end and allow it to free any resources it allocates. This is missing in your code and as a result the MemoryStream is accumulating data in addition to leaking:
      • If I use the same one on very small images, the size of the stream reports 334, 684, 1014, 1361.... It is accumulating image data and all but the first image will be corrupt resulting in the GDI error.

    Code to test for the round trip:

    Dim dtXYZ = New DataTable()
    
    ' or use a dataset
    dtXYZ.ReadXml("C:\Temp\DTX.xml")
    dgv1.DataSource = dtXYZ
    

    enter image description here

    The same data for both before and after.