I'm here again trying to solve this issue with the ArrayList
that I've explained in this other question... the difference is that this time I'm not searching for any alternative solution such as the usage of other external file to avoid the real problem, I would really like to fix this using My.Settings and the ArrayList, I would like to understand what is happening here about My.Settings!.
The problem is that if we set a Setting like this:
Then any changes performed on the setting are preserved on the next app runs, this code demostrates the problem:
Public Class Test
Private Sub Test_Handler() Handles MyBase.Shown
' Create a temporal predefined ArrayList.
Dim tmpArrayList As New ArrayList(capacity:=10I)
With tmpArrayList
.Add({"Item0", 0.0F})
.Add({"Item1", 0.5F})
End With
' Check the setting status.
If My.Settings.MRU Is Nothing Then
Debug.WriteLine("MRU setting is null.")
Debug.WriteLine("Initializing the Setting...")
My.Settings.MRU = New ArrayList(capacity:=10I)
ElseIf My.Settings.MRU.Count = 0 Then
Debug.WriteLine("MRU is not null but the ArrayList is empty.")
Debug.WriteLine("Adding some items...")
My.Settings.MRU = tmpArrayList.Clone
ElseIf My.Settings.MRU.Count > 0 Then ' This part of the block will never thrown.
Debug.WriteLine("MRU setting is OK.")
Debug.WriteLine("Item Count: " & CStr(My.Settings.MRU.Count))
Threading.Thread.Sleep(Integer.MaxValue)
End If
Debug.WriteLine("Saving any changes")
My.Settings.Save()
Debug.WriteLine("Updating any changes")
My.Settings.Reload()
Debug.WriteLine(String.Empty)
Debug.WriteLine("****************************************")
Debug.WriteLine("Checking again the MRU setting status in...")
For Count As Integer = 1 To 3
Debug.WriteLine(CStr(Count) & New String("."c, Count))
Threading.Thread.Sleep(TimeSpan.FromSeconds(1))
Next
Debug.WriteLine("****************************************")
Debug.WriteLine(String.Empty)
Me.Test_Handler()
End Sub
End Class
Someone could teach me to understand why the ArrayList is not really saved, or show me how to solve this issue?.
UPDATE
Ok I've write this serializable class trying to solve this issue:
''' <summary>
''' A Class intended to use it as an Item for a MRU item collection that stores the item filepath, with additional info.
''' </summary>
<Serializable()>
Public Class MostRecentUsedItem
''' <summary>
''' Gets or sets the item filepath.
''' </summary>
''' <value>The file path.</value>
Public Property FilePath As String
''' <summary>
''' (Optionally) Gets or sets the item index.
''' </summary>
''' <value>The index.</value>
Public Property Index As Integer
''' <summary>
''' (Optionally) Gets or sets the item image.
''' </summary>
''' <value>The image.</value>
Public Property Img As Bitmap
''' <summary>
''' (Optionally) Gets or sets the item last-time open date.
''' </summary>
''' <value>The index.</value>
Public Property [Date] As Date
''' <summary>
''' (Optionally) Gets or sets the item tag.
''' </summary>
''' <value>The tag object.</value>
Public Property Tag As Object
End Class
Also I've write this helper function to help me in this issue:
''' <summary>
''' Determines whether an object can be XML serialized.
''' </summary>
''' <param name="Object">The object.</param>
''' <returns><c>true</c> if object is XML serializable; otherwise, <c>false</c>.</returns>
Private Function IsObjectSerializable(ByVal [Object] As Object) As Boolean
Using fs As New IO.FileStream(IO.Path.GetTempFileName, IO.FileMode.Create)
Dim Serializer As New Xml.Serialization.XmlSerializer([Object].GetType)
Try
Serializer.Serialize(fs, [Object])
Return True
Catch ex As InvalidOperationException
Return False
End Try
End Using
End Function
At the moment that I initialize the setting like this, it is serializable:
My.Settings.MRU = New ArrayList
At the moment that I add just a string, it still be serializable:
My.Settings.MRU.Add("test string")
But at the moment that I try to add my serializable class, or any other kind of datatype like a String()
, the ArrayList begins unserializable, Like this:
My.Settings.MRU.Add({"Collection", "Of", "Strings"})
Or like this else:
Dim MRUItem As New MostRecentUsedItem
MRUItem.FilePath = "C:\Test.ext"
My.Settings.MRU.Add(MRUItem)
...So the ArrayList contents are not preserved on the next run, can't be serialized.
I also tried to change the setting type from System.Collections.ArrayList
to System.Object
(desperately) so now I can do this, but the problem persist, I mean the collection is not saved on the next app run:
My.Settings.MRU = New List(Of MostRecentUsedItem)
Dim MRUItem As New MostRecentUsedItem
MRUItem.FilePath = "C:\Test.ext"
My.Settings.MRU.Add(MRUItem)
Since Application Settings serializes complex types as XML, you must make sure that the specific type can be serialized as XML before saving it.
You can test your data type with the following method:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim tmpArrayList As New ArrayList()
With tmpArrayList
.Add({"Item0", 0.0F})
.Add({"Item1", 0.5F})
End With
Dim XmlSerializer1 As New XmlSerializer(tmpArrayList.GetType)
XmlSerializer1.Serialize(Console.Out, tmpArrayList)
End Sub
This code fails to Serialize the ArrayList and returns the following message:
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Xml.dll Additional information: There was an error generating the XML document.
But if you try to store simple data type in the ArrayList, the Serialization will succeed
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim tmpArrayList As New ArrayList()
With tmpArrayList
.Add("Item0")
.Add("Item1")
End With
Dim XmlSerializer1 As New XmlSerializer(tmpArrayList.GetType)
XmlSerializer1.Serialize(Console.Out, tmpArrayList)
End Sub
The same happens when you store data in the Application Settings, but the difference is that it doesn't return errors.
Useful links:
EDIT
Implementation using DataTable
Create a new Windows Forms Project, add a new setting called NewMRU in the Application Settings with a data type of System.Data.DataTable and try the following code.
Imports System.IO
Imports System.Xml.Serialization
Imports System.Drawing.Imaging
Public Class Form1
Dim DataTable1 As New DataTable("MySettingsDataTable")
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
DataTable1.Columns.Add("FilePath", GetType(String))
DataTable1.Columns.Add("Index", GetType(Integer))
DataTable1.Columns.Add("Img", GetType(Byte()))
DataTable1.Columns.Add("Date", GetType(DateTime))
End Sub
Private Sub button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
AddPropertyRow("C:\Temp", 1, GetBytesFromBitmap(Me.Icon.ToBitmap), Now)
AddPropertyRow("C:\Windows", 2, GetBytesFromBitmap(Me.Icon.ToBitmap), Now)
My.Settings.NewMRU = DataTable1
My.Settings.Save()
'MsgBox(IsObjectSerializable(DataTable1))
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If Not My.Settings.NewMRU Is Nothing Then
Dim i As Integer
For i = 0 To My.Settings.NewMRU.Rows.Count - 1
Debug.WriteLine("Row - " & i + 1)
Debug.WriteLine("FilePath = " & My.Settings.NewMRU.Rows(i).Item("FilePath"))
Debug.WriteLine("Index = " & My.Settings.NewMRU.Rows(i).Item("Index"))
Debug.WriteLine("Img bytes count = " & TryCast(My.Settings.NewMRU.Rows(i).Item("Img"), Byte()).Length)
Debug.WriteLine("Date = " & My.Settings.NewMRU.Rows(i).Item("Date"))
Debug.WriteLine("")
Next
End If
'PictureBox1.Image = GetBitmapFromBytes(TryCast(My.Settings.NewMRU.Rows(0).Item("Img"), Byte()))
End Sub
Private Sub AddPropertyRow(ByVal FilePath As String, ByVal Index As Integer,
ByVal Img() As Byte, ByVal [Date] As Date)
DataTable1.Rows.Add(DataTable1.NewRow)
Dim RowIndex As Integer = DataTable1.Rows.Count - 1
DataTable1.Rows(RowIndex).Item("FilePath") = FilePath
DataTable1.Rows(RowIndex).Item("Index") = Index
DataTable1.Rows(RowIndex).Item("Img") = Img
DataTable1.Rows(RowIndex).Item("Date") = [Date]
End Sub
Private Function IsObjectSerializable(ByVal [Object] As Object) As Boolean
Using fs As New IO.FileStream(IO.Path.GetTempFileName, IO.FileMode.Create)
Dim Serializer As New Xml.Serialization.XmlSerializer([Object].GetType)
Try
Serializer.Serialize(fs, [Object])
Return True
Catch ex As InvalidOperationException
Return False
End Try
End Using
End Function
Private Function GetBytesFromBitmap(ByVal Bitmap1 As Bitmap) As Byte()
Dim BitmapBytes() As Byte
Using MemoryStream1 As New MemoryStream()
Bitmap1.Save(MemoryStream1, ImageFormat.Bmp)
BitmapBytes = MemoryStream1.GetBuffer()
End Using
Return BitmapBytes
End Function
Private Function GetBitmapFromBytes(ByVal Bytes As Byte()) As Bitmap
Dim Bitmap1 As Bitmap = Nothing
Using MemoryStream1 As New MemoryStream(Bytes, 0, Bytes.Length)
Bitmap1 = Image.FromStream(MemoryStream1)
End Using
Return Bitmap1
End Function
End Class
If you want to use the Tag property (which I have omitted from the code since it's not serializable), you should split its values (Item, Value) as columns in the DataTable.