SerializationException when trying to inherit a control from a Serializable Class

artix

Member
Joined
Oct 5, 2007
Messages
16
Location
Sweden
Programming Experience
1-3
Hello,

I got a working serialize/deserialize class thats saving and loading some data to a file on the HD.. but when I add an inheritance to a control in the class I get the included error.



In my case Im inhereting the Forms.Panel control. Furthermore I guess I don't want to serialize the whole Panel control but atleast the Width/Height prop.



Any ideas?



/Anders.



System.Runtime.Serialization.SerializationException was unhandled
Message="Type 'System.Windows.Forms.Panel' in Assembly 'System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable."
Source="mscorlib"
StackTrace:
at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
at System.Runtime.Serialization.Formatters.Soap.WriteObjectInfo.InitMemberInfo()
at System.Runtime.Serialization.Formatters.Soap.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SoapAttributeInfo attributeInfo, ObjectWriter objectWriter)
at System.Runtime.Serialization.Formatters.Soap.WriteObjectInfo.Serialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SoapAttributeInfo attributeInfo, ObjectWriter objectWriter)
at System.Runtime.Serialization.Formatters.Soap.ObjectWriter.Serialize(Object graph, Header[] inHeaders, SoapWriter serWriter)
at System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers)
at System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Serialize(Stream serializationStream, Object graph)
at XMLserializer.Form1.SaveSerializedFile() in D:\Visual Studio\XMLserializer\XMLserializer\Form1.vb:line 21
at XMLserializer.Form1.Button1_Click(Object sender, EventArgs e) in D:\Visual Studio\XMLserializer\XMLserializer\Form1.vb:line 39
at System.Windows.Forms.Control.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ButtonBase.WndProc(Message& m)
at System.Windows.Forms.Button.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(ApplicationContext context)
at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.OnRun()
at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.DoApplicationModel()
at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Run(String[] commandLine)
at XMLserializer.My.MyApplication.Main(String[] Args) in 17d14f5c-a337-4978-8281-53493378c1071.vb:line 81
at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
 
Last edited by a moderator:
Panel class isn't marked as Serializable so you can't serialize an instance of Panel.
You can for example let your class implement IXmlSerializable and do custom serialization to save the info you want.
 
Thanks,
But from what I understand (and I did a quick test) this only let me decide basically how the syntax of the serialization itself looks..

What im trying to say is, how will this make my program accept the inherited control? Looks like its still trying to serialize the control or something because im getting the same error even with that interface implemented.

I guess if I could just let him "ignore" the inherit line as when it serialize it might work or something :p But I can't figure out how to declare an inheritance with a 'noserialization'..
 
I have no idea what you are doing, but xml ignore is done by applying the XmlIgnore attribute to the class member you want ignored.
 
Hehe, thank youfor your patiance and I think im starting to misslead you. So im starting from scratch with a clean project..
I was trying to implement this in an already pretty massive application so Im starting a new small one where its gonna be easier to try some stuff.

Right now im pretty much back to square one. What I want to serialize to a file in either XML or Binary.. whichever is easiest :p is pretty much the content of this Class below.

As you can see its using the Panel control as a base to get some mouse click event and width/height and some other stuff in the "real application".. I want to have the width and height with me into the serialization file and the click event handlers with me after I get them all deserialized and into my program again.
Hmm, so.. Hope im making myself clear enough.

I mean, this has to be able to be done someway? Do I have to somehow seperate the panel along with the panel events from the serializable class or something? I can't seem to figure out how to make the panel inheritance serializable.


VB.NET:
Serializable()> Public Class JustAClass
    Inherits Panel
    Private iWidth As Integer
    Private iHeight As Integer

    Shadows Sub MouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDoubleClick
        'Do something
    End Sub

    Public Property myWidth() As String
        Get
            Return iWidth
        End Get
        Set(ByVal value As String)
            iWidth = value
            MyBase.Width = value
        End Set
    End Property

    Public Property myHeight() As String
        Get
            Return iHeight
        End Get
        Set(ByVal value As String)
            iHeight = value
            MyBase.Height = value
        End Set
    End Property

End Class
 
You can't do that (serialize JustAClass) without custom serialization, because the serializer does all public properties including the inherited.

To serialize only the two class properties and not the inherited is pretty simple with IXmlSerializable.
 
Write "Implements Xml.Serialization.IXmlSerializable" at top of class (line below "inherits") and press enter, all interface members are generated.

GetSchema method:
VB.NET:
Return Nothing
WriteXml method:
VB.NET:
writer.WriteElementString("myWidth", myWidth)
writer.WriteElementString("myHeight", myHeight)
ReadXml method:
VB.NET:
reader.Read()
myWidth = reader.ReadElementString("myWidth")
myHeight = reader.ReadElementString("myHeight")
reader.ReadEndElement()
 
Last edited:
That is the exact code that I tried before and that gave me the exact same error.

This is what the class looks like right now.

VB.NET:
<Serializable()> Public Class JustAClass
    Inherits Panel
    Implements Xml.Serialization.IXmlSerializable

    Private iWidth As Integer
    Private iHeight As Integer

    Shadows Sub MouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDoubleClick
        'Do something
    End Sub

    Public Property myWidth() As String
        Get
            Return iWidth
        End Get
        Set(ByVal value As String)
            iWidth = value
            MyBase.Width = value
        End Set
    End Property

    Public Property myHeight() As String
        Get
            Return iHeight
        End Get
        Set(ByVal value As String)
            iHeight = value
            MyBase.Height = value
        End Set
    End Property

    Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
        Return Nothing
    End Function

    Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml
        reader.Read()
        myWidth = reader.ReadElementString("myWidth")
        myHeight = reader.ReadElementString("myHeight")
    End Sub

    Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
        writer.WriteElementString("myWidth", myWidth)
        writer.WriteElementString("myHeight", myHeight)
    End Sub
End Class

And this is what the actual serialization sub looks like. Could the problem lie there? in this example Im getting a width and height from two textboxes.

VB.NET:
Sub SaveSerializedFile()
        Dim MyJustAClass As New JustAClass
        Dim s As New FileStream("test.txt", FileMode.Create)
        Dim f As New SoapFormatter

        MyJustAClass.myWidth = TextBox1.Text
        MyJustAClass.myHeight = TextBox2.Text
        f.Serialize(s, MyJustAClass)
        s.Close()
    End Sub

Im conna attach the whole project here too if you wanna try it out.. might be easier to see.
 

Attachments

  • XMLserializer.zip
    15.1 KB · Views: 21
Last edited by a moderator:
SoapFormatter? I didn't notice you used that in first post. I don't think that is what you want, SoapFormatter is rarely used explicitly by programmers, usually it is only used low-level by Webservices and Remoting. IXmlSerializable is of course only used by the XmlSerializer (Xml.Serialization namespace) and produces Xml.
VB.NET:
Dim ser As New Xml.Serialization.XmlSerializer(GetType(JustAClass))
Custom serialization that will work for BinaryFormatter and SoapFormatter is to implement ISerializable (Runtime.Serialization namespace). You will get a GetObjectData method where this code can be used:
VB.NET:
info.AddValue("myWidth", myWidth)
info.AddValue("myHeight", myHeight)
This also requires a special constructor with same parameters used for deserialization:
VB.NET:
Public Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext)
    myWidth = info.GetString("myWidth")
    myHeight = info.GetString("myHeight")
End Sub

'also added a parameterless constructor for normal use
Public Sub New()
End Sub
 
Thank you so much! That sure made the trick. I spent all my effort in thinking it was the class that was the problem.

Now im gonna implement this on my real project and figure out how to get an array (arraylist or collection or something) of my class to serialize..

Im hoping that wont be as much of a challenge :p
 
(de)serializing arraylist

Hello again :)

I've managed to read through a couple of examples and it seems im getting the serialization working now as an custom class arraylist and it saves them into an XML file that looks like this. (two objects saved)

VB.NET:
<?xml version="1.0"?>
<BalconyCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <BalconyArrayList>
    <Balcony>
      <BalconyGuid>2a8e5aa7-1885-466c-ae68-8ac36061d456</BalconyGuid>
      <Identifier>1</Identifier>
      <BalconyWidth>3333</BalconyWidth>
      <BalconyHeight>2222</BalconyHeight>
      <myStartX>112</myStartX>
      <myStartY>38</myStartY>
      <ConcreteThickness>70</ConcreteThickness>
      <PLName>asd</PLName>
      <ObjNumber>sad</ObjNumber>
      <ObjName>namn2</ObjName>
    </Balcony>
    <Balcony>
      <BalconyGuid>b7083932-161a-4d83-bd6b-d97105760373</BalconyGuid>
      <Identifier>0</Identifier>
      <BalconyWidth>3333</BalconyWidth>
      <BalconyHeight>2222</BalconyHeight>
      <myStartX>88</myStartX>
      <myStartY>42</myStartY>
      <ConcreteThickness>70</ConcreteThickness>
      <PLName>asd</PLName>
      <ObjNumber>sad</ObjNumber>
      <ObjName>namn1</ObjName>
    </Balcony>
  </BalconyArrayList>
</BalconyCollection>

But now when I try to deserialize it again it only captures the first "balcony".. This is what the serialization and deserialization part looks like

VB.NET:
Sub SaveSerializedFile()

        Dim myStream As Stream
        Dim saveFileDialog1 As New SaveFileDialog()

        saveFileDialog1.Filter = "XML filer (*.xml)|*.xml|Alla filer (*.*)|*.*"
        saveFileDialog1.FilterIndex = 1
        saveFileDialog1.InitialDirectory = Application.StartupPath
        saveFileDialog1.RestoreDirectory = True

        If saveFileDialog1.ShowDialog() = DialogResult.OK Then
            myStream = saveFileDialog1.OpenFile()
            If (myStream IsNot Nothing) Then

                Dim BalconyList As New BalconyArrayList()

                Dim i As Integer
                For i = 0 To Form1.SplitContainer1.Panel1.Controls.Count - 1
                    If TypeOf Form1.SplitContainer1.Panel1.Controls(i) Is Balcony Then
                          'If there are other controls on the panel this could result in 
                          'empty 'array items'.. will have to work around this later.                        
                         BalconyList(i) = (CType(Form1.SplitContainer1.Panel1.Controls(i), Balcony))
                    End If
                Next

                Dim f As New System.Xml.Serialization.XmlSerializer(GetType(BalconyArrayList))

                f.Serialize(myStream, BalconyList)
                myStream.Close()
            End If
        End If

    End Sub

Sub RestoreSerializedFile()
        Dim myStream As Stream = Nothing
        Dim openFileDialog1 As New OpenFileDialog()

        openFileDialog1.InitialDirectory = "c:\"
        openFileDialog1.Filter = "XML filer (*.xml)|*.xml|Alla filer (*.*)|*.*"
        openFileDialog1.FilterIndex = 1
        openFileDialog1.InitialDirectory = Application.StartupPath
        openFileDialog1.RestoreDirectory = True

        If openFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
            Try
                myStream = openFileDialog1.OpenFile()
                If (myStream IsNot Nothing) Then
                    
                    Dim f As New System.Xml.Serialization.XmlSerializer(GetType(BalconyArrayList))
                    Dim BalconyList As New BalconyArrayList
                    BalconyList = CType(f.Deserialize(myStream), BalconyArrayList)
                    myStream.Close()

                    Dim x As Balcony
                    For Each x In BalconyList.BalconyCollection

                        'Here im doing some stuff.
                    Next

                End If
            Catch Ex As Exception
                MessageBox.Show("Kan inte läsa filen. Error: " & Ex.Message)
            Finally
                If (myStream IsNot Nothing) Then
                    myStream.Close()
                End If
            End Try
        End If
    End Sub

Also.. the BalconyArrayList

VB.NET:
Imports System
Imports System.Collections
Imports System.Xml.Serialization

<XmlRoot("BalconyCollection")> _
Public Class BalconyArrayList
    Private _BalconyList As New ArrayList()

    Public Sub New()
    End Sub

    <XmlArray("BalconyArrayList")> _
    <XmlArrayItem("Balcony", GetType(Balcony))> _
    Public Property BalconyCollection() As ArrayList
        Get
            Return _BalconyList
        End Get
        Set(ByVal value As ArrayList)
            _BalconyList = value
        End Set
    End Property

    Default Public Property Item(ByVal index As Integer) As Balcony
        Get
            Return DirectCast(_BalconyList(index), Balcony)
        End Get
        Set(ByVal value As Balcony)
            If index > _BalconyList.Count - 1 Then
                _BalconyList.Add(value)
            Else
                _BalconyList(index) = value
            End If
        End Set
    End Property

End Class

Any ideas why I only would get the first item in the serialized 'arraylist'..?
 
I guess you're using the custom IXmlSerializable implementation suggested in post 10, there was a slight error in ReadXml method. It used Read method to navigate into the wrapping node, then two ReadElementString calls to read content, but were missing a ReadEndElement to navigate out of the wrapper node again, which caused array serializer to not detect there were more items. I fixed the code of post 10 and tested your scenario, now it worked.

I would still not use your BalconyArrayList, instead I would use a List(of Balcony) which is a strongly typed list coming from the new generics in .Net 2.0. About "empty array items", the fix is also simple:
VB.NET:
Dim balconys As New List(Of Balcony)
For Each c As Control In Form1.SplitContainer1.Panel1.Controls
    If TypeOf c Is Balcony Then
        balconys.Add(c)
    End If
Next
Dim f As New System.Xml.Serialization.XmlSerializer(balconys.GetType)
f.Serialize(myStream, balconys)
 
Back
Top