1

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
Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
Jelle O
  • 15
  • 4
  • How many rows are you working with? – Ňɏssa Pøngjǣrdenlarp Jun 28 '16 at 12:39
  • Is there an InternalException and if so what does it say? – Alex B. Jun 28 '16 at 13:08
  • 2
    @AlexB. that exception never has anything more to say. it is a catch all for anything from a File access issue to resources exceeded. Actually `A generic error occurred in GDI+` *is* the additional info – Ňɏssa Pøngjǣrdenlarp Jun 28 '16 at 13:13
  • What the heck is that `j` loop meant to do? it looks at col0 then adds a new *row* to hold the byte data on col 1??? If the datatable stores an image, you dont have to pre-process - it will convert them to Base64 and save them by itself. – Ňɏssa Pøngjǣrdenlarp Jun 28 '16 at 13:21
  • 1
    @Plutonix Thank you for the information. I was not aware of that. Seems like hell for debugging.... – Alex B. Jun 28 '16 at 13:23
  • @plutonix. Apologies if its unclear. Rowcount is variable. However, for some reason the dataviewgrid counts the last empty row as a row, hence -1. Counting from 0 is rowcount -1. Makes -2. The datatable has one extra ID column at the start, which is why I offset the columns by 1 between the table and the datagridview. "If the datatable stores an image, you dont have to pre-process - it will convert them to Base64 and save them by itself." The datatable doesnt hold images as far as I'm aware. Because of that, no binding and the transfer to compressed Byte() – Jelle O Jun 28 '16 at 13:58
  • If column 0 is an image column you ought not have to do any of that - `WriteXml` will write out an image as B64 – Ňɏssa Pøngjǣrdenlarp Jun 28 '16 at 14:07
  • I might be missing something obvious here... the Dataset.Datatable datatypes do not have images listed. The DataType property supports the following base .NET Framework data types: Boolean, Byte, Char, DateTime, Decimal, Double, Guid, Int16, Int32, Int64, SByte, Single, String, TimeSpan, UInt16, UInt32, UInt64, Byte[] This is the reason I'm converting to byte() and back. the gridview does have an imagecolumn - but doesnt have an XMLwriter. – Jelle O Jun 28 '16 at 14:14
  • Plutonix - first off - I massively appreciate your help. I appear to be missing your point however. Just tried binding a new datatable again - with the imagecolumn as byte() - then added an image to the DGV. No such luck. Error on it as the datatable doesnt allow it. I believe there is either something I'm not releasing properly or somewhere the image format is changed when reinserting - although it shouldnt I believe. I dont see a good reasong why it would act differently on a image inserted the first time vs the second time – Jelle O Jun 28 '16 at 15:05

1 Answers1

0

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.

Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178