Jelle O Jelle O - 3 months ago 18
Vb.net Question

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

Answer

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.

Comments