When using the Serializer class, remember these tips:
- You MUST have a parameterless constructor defined in order for Serializer.CreateObjectInstanceAndDeserialize to create a new instance of your class to deserialize.
- It is a good idea to initialize the fields in the class with some default or dummy values, but it is not necessary if you always save each field to the XML regardless of whether it is set to its default (which is required to make GetObjectXml and SetObjectXml work properly).
- The Serializer CanSerializeObject method determines whether the specified object can be serialized with SerializeObject. This method checks whether the object implements ICanSerializeXml (in which case it returns the value of the object's CanSerializeXml property), or whether the object implements ISerializeSupport, or is a simply type, or if the type's IsSerializable property returns true.
- Saving the object using elements (see the first subclass example below) requires more coding, but results in more readable XML when the number of properties being saved is high (as in this example of the LineBorder class below).
- Saving the object using attributes (the second subclass example below) requires less coding, and results in more readable XML when the number of properties being saved is low (as in this example of the GradientLineBorder class below), so the latter subclass example is better.
- If you implement ISerializeSupport using elements, then you should call the base class implementation first (if there is one), then save and load your properties. You should also return after reading the last end element that you serialize, so that subclasses can also use elements to serialize their properties.
- If you implement ISerializeSupport using attributes, then you should read your attributes first, then call the base class implementation (if there is one).
- You can use both attributes and elements to serialize your object; there are some Spread objects (mostly the models) which do this. In that case you should do the attributes first, then call the base class, then save and load your elements, returning from Deserialize after you reach the last end element.
The examples below show saving simple things like integers and colors, but saving other types is no different. There are methods in Serializer for saving and loading Color, DateTime, DateTimeFormatInfo, Enum types, Font, Image, Int32 array, NumberFormatInfo, PointF array, String array, and Object. It is best (though not required) to use the most specific method available -- for example, it would work to use SerializeObject to save a simple type or a Color value, but it is less efficient and may not result in the same XML. In general, there is a reason each of the methods was created for its specific type. DateTimeFormatInfo and NumberFormatInfo must be saved using their respective methods because SerializeObject would default to using binary serialization, which does not work across versions of the framework (so a DateTimeFormatInfo saved using .NET 1.0 does not load using .NET 1.1 or 2.0).
For simple types (Int32, String, Boolean, etc.) it is best to simply use ToString() to convert them to a string for saving (passing CultureInfo.InvariantCulture if possible) and then use XmlConvert to convert the strings back into values.
SaveObject is intended for saving objects which implement ISerializeSupport, or for certain specific types (intrinsic data types and DataSet), or for generic objects which are serializable but do not implement ISerializeSupport (such objects get saved using EncodeObject, which uses a BinaryFormatter to save the object to a MemoryStream and then uses Convert to encode the bytes into a base-64 string).
Debug versus Release Build
To get files saved with a debug build to load into a release build that is strong named, you must eliminate or update the assembly attribute in the node where the object is saved. SerializeObject will write an assembly attribute if the assembly in which the object's type is defined is different from the calling assembly and different from the executing assembly (FarPoint.Win.dll). The calling assembly is usually FarPoint.Win.Spread.dll but is possibly the user's assembly if they are defining their own objects which implement ISerializeSupport and saving child objects using SerializeObject. The tricky case is, unfortunately, likely the most common one: a user creates a custom object to plug into the Spread's object model (for example, a custom data model) and they want to save and load the Spread using the save and load methods in FpSpread and using different builds of their assembly.
In that case, the assembly attribute is required to load the correct assembly containing the object's type definition, but the correct attribute for the debug and release builds is different. In that case, the user would need to edit the value in the XML to change the assembly reference (using something like String.Replace). The less common cases involve users who are using ISerializeSupport and Serializer to save and load their own files containing objects that they define, perhaps in the same assembly or in a different assembly that they are writing in parallel. If the objects are defined in the same assembly, then the assembly tab will not be written out, and if it is a different assembly, the developer can call the overloads for SerializeObject and CreateObjectInstanceAndDeserialize which take the callingAssembly argument and pass the assembly containing the object (even if that is not really the assembly calling into Serializer). That will prevent the assembly attribute from being written out, but it is important that this be done on both ends (the call to save AND load) so that the type reference can be resolved and an instance of the object created.
Here are some examples of how you might implement ISerializeSupport.
First, here is the implementation of ISerializeSupport in FarPoint.Win.LineBorder:
Example of Base Class
C# | ![]() |
---|---|
// A constructor with no arguments is required for ISerializeSupport. // It does not need to be public though. internal LineBorder() { color = SystemColors.WindowFrame; thickness = 1; left = top = right = bottom = true; inset = new Inset(1); } /// <summary> /// Saves the object to XML. /// </summary> /// <param name="w">XmlTextWriter object to which to save the object</param> /// <returns>true if successful; false otherwise</returns> public virtual bool Serialize(XmlTextWriter w) { w.WriteStartElement("Inset"); w.WriteElementString("Bottom",inset.Bottom.ToString()); w.WriteElementString("Left", inset.Left.ToString()); w.WriteElementString("Right", inset.Right.ToString()); w.WriteElementString("Top", inset.Top.ToString()); w.WriteEndElement(); Serializer.SerializeColor(color, "Color", w); w.WriteStartElement("Sides"); w.WriteElementString("Bottom", bottom.ToString()); w.WriteElementString("Left", left.ToString()); w.WriteElementString("Right", right.ToString()); w.WriteElementString("Top", top.ToString()); w.WriteEndElement(); w.WriteElementString("Thickness", thickness.ToString(CultureInfo.InvariantCulture)); return true; } /// <summary> /// Loads the object from XML. /// </summary> /// <param name="r">XmlNodeReader from which to load the object</param> /// <returns>true if successful; false otherwise</returns> public virtual bool Deserialize(XmlNodeReader r) { bool inInset = false; bool inBottom = false; bool inLeft = false; bool inRight = false; bool inTop = false; bool inColor = false; bool inSides = false; bool inThickness = false; int bottom = inset.Bottom; int left = inset.Left; int right = inset.Right; if( r.IsEmptyElement ) return true; while( r.Read() ) { switch( r.NodeType ) { case XmlNodeType.Element: if( r.Name.Equals("Inset") ) inInset = true; else if( r.Name.Equals("Color") ) inColor = true; else if( r.Name.Equals("Sides") ) inSides = true; else if( inInset || inSides ) { if( r.Name.Equals("Bottom") ) inBottom = true; else if( r.Name.Equals("Left") ) inLeft = true; else if( r.Name.Equals("Right") ) inRight = true; else if( r.Name.Equals("Top") ) inTop = true; } else if( r.Name.Equals("Thickness") ) inThickness = true; break; case XmlNodeType.Text: if( inInset ) { if( inBottom ) bottom = XmlConvert.ToInt32(r.Value); else if( inLeft ) left = XmlConvert.ToInt32(r.Value); else if( inRight ) right = XmlConvert.ToInt32(r.Value); else if( inTop ) inset = new Inset(left, XmlConvert.ToInt32(r.Value), right, bottom); } else if( inSides ) { if( inBottom ) this.bottom = XmlConvert.ToBoolean(r.Value.ToLower(CultureInfo.InvariantCulture)); else if( inLeft ) this.left = XmlConvert.ToBoolean(r.Value.ToLower(CultureInfo.InvariantCulture)); else if( inRight ) this.right = XmlConvert.ToBoolean(r.Value.ToLower(CultureInfo.InvariantCulture)); else if( inTop ) this.top = XmlConvert.ToBoolean(r.Value.ToLower(CultureInfo.InvariantCulture)); } else if( inColor ) color = Serializer.DeserializeColorValue(r.Value); else if( inThickness ) thickness = XmlConvert.ToInt32(r.Value); break; case XmlNodeType.EndElement: if( inInset && r.Name.Equals("Inset") ) inInset = false; else if( inColor && r.Name.Equals("Color") ) inColor = false; else if( inSides && r.Name.Equals("Sides") ) inSides = false; else if( inInset || inSides ) { if( inBottom && r.Name.Equals("Bottom") ) inBottom = false; else if( inLeft && r.Name.Equals("Left") ) inLeft = false; else if( inRight && r.Name.Equals("Right") ) inRight = false; else if( inTop && r.Name.Equals("Top") ) inTop = false; } else if( inThickness && r.Name.Equals("Thickness") ) // return here so that subclasses can deserialize return true; break; } } return true; } |
Now here is an example of what a derived class might do in overrides for those methods:
Example of Derived Class
C# | ![]() |
---|---|
// A constructor with no arguments is required for ISerializeSupport. // It does not need to be public though. protected GradientLineBorder() : LineBorder(SystemColors.WindowFrame, 1, true, true, true, true) { startColor = Color.Blue; endColor = Color.Green; } /// <summary> /// Saves the object to XML. /// </summary> /// <param name="w">XmlTextWriter object to which to save the object</param> /// <returns>true if successful; false otherwise</returns> public override bool Serialize(XmlTextWriter w) { if( !base.Serialize(w) ) return false; Serializer.SerializeColor(startColor, "StartColor", w); Serailizer.SerializeColor(endColor, "EndColor", w); return true; } /// <summary> /// Loads the object from XML. /// </summary> /// <param name="r">XmlNodeReader from which to load the object</param> /// <returns>true if successful; false otherwise</returns> public override bool Deserialize(XmlNodeReader r) { bool inStartColor = false; bool inEndColor = false; if( r.IsEmptyElement ) return true; if( !base.Deserialize(r) ) return false; while( r.Read() ) { switch( r.NodeType ) { case XmlNodeType.Element: if( r.Name.Equals("StartColor") ) inStartColor = true; else if( r.Name.Equals("EndColor") ) inEndColor = true; case XmlNodeType.Text: if( inStartColor ) startColor = Serializer.DeserializeColorValue(r.Value); else if( inEndColor ) endColor = Serializer.DeserializeColorValue(r.Value); break; case XmlNodeType.EndElement: if( inStartColor && r.Name.Equals("StartColor") ) inStartColor = false; else if( inEndColor && r.Name.Equals("EndColor") ) // return here so that subclasses can deserialize return true; break; } } return true; } |
Another (somewhat easier) way to implement it would be this way:
Example of Another Way
C# | ![]() |
---|---|
/// <summary> /// Saves the object to XML. /// </summary> /// <param name="w">XmlTextWriter object to which to save the object</param> /// <returns>true if successful; false otherwise</returns> public override bool Serialize(XmlTextWriter w) { w.WriteAttributeString("startColor", Serializer.SerializeColorValue(startColor)); w.WriteAttributeString("endColor", Serializer.SerializeColorValue(endColor)); return base.Serialize(w); } /// <summary> /// Loads the object from XML. /// </summary> /// <param name="r">XmlNodeReader from which to load the object</param> /// <returns>true if successful; false otherwise</returns> public override bool Deserialize(XmlNodeReader r) { if( r.MoveToAttribute("startColor") ) startColor = Serializer.DeserializeColorValue(r.Value); if( r.MoveToAttribute("endColor") ) endColor = Serializer.DeserializeColorValue(r.Value); return base.Deserialize(r); } |