namespace Loader
{
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Collections.Generic;
///
/// Inherits from BasicDesignerLoader. It can persist the HostSurface
/// to an Xml file and can also parse the Xml file to re-create the
/// RootComponent and all the components that it hosts.
///
public class BasicHostLoader : BasicDesignerLoader
{
private IComponent root;
private bool dirty = true;
private bool unsaved;
private string fileName;
private bool bRun = false;
private IDesignerLoaderHost host;
private XmlDocument xmlDocument;
private static readonly Attribute[] propertyAttributes = new Attribute[] {
DesignOnlyAttribute.No
};
private Type rootComponentType;
public List mComponents = new List();
#region Constructors
/// Empty constructor simply creates a new form.
public BasicHostLoader(Type rootComponentType)
{
this.rootComponentType = rootComponentType;
this.Modified = true;
}
///
/// This constructor takes a file name. This file
/// should exist on disk and consist of XML that
/// can be read by a data set.
///
public BasicHostLoader(string fileName, bool bRun)
{
if (fileName == null)
{
throw new ArgumentNullException("fileName");
}
this.bRun = bRun;
this.fileName = fileName;
}
#endregion
#region Overriden methods of BasicDesignerLoader
// Called by the host when we load a document.
protected override void PerformLoad(IDesignerSerializationManager designerSerializationManager)
{
this.host = this.LoaderHost;
if (host == null)
{
throw new ArgumentNullException("BasicHostLoader.BeginLoad: Invalid designerLoaderHost.");
}
// The loader will put error messages in here.
ArrayList errors = new ArrayList();
bool successful = true;
string baseClassName = "";
if (!bRun)
{
// If no filename was passed in, just create a form and be done with it. If a file name
// was passed, read it.
if (fileName == null)
{
if (rootComponentType == typeof(Form))
{
host.CreateComponent(typeof(Form));
baseClassName = "Form1";
}
else if (rootComponentType == typeof(UserControl))
{
host.CreateComponent(typeof(UserControl));
baseClassName = "UserControl1";
}
else if (rootComponentType == typeof(Component))
{
host.CreateComponent(typeof(Component));
baseClassName = "Component1";
}
else
{
throw new Exception("Undefined Host Type: " + rootComponentType.ToString());
}
}
else
{
baseClassName = ReadFile(fileName, errors, out xmlDocument);
}
// Now that we are done with the load work, we need to begin to listen to events.
// Listening to event notifications is how a designer "Loader" can also be used
// to save data. If we wanted to integrate this loader with source code control,
// we would listen to the "ing" events as well as the "ed" events.
IComponentChangeService cs = host.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
if (cs != null)
{
cs.ComponentChanged += new ComponentChangedEventHandler(OnComponentChanged);
cs.ComponentAdded += new ComponentEventHandler(OnComponentAddedRemoved);
cs.ComponentRemoved += new ComponentEventHandler(OnComponentAddedRemoved);
}
// Let the host know we are done loading.
host.EndLoad(baseClassName, successful, errors);
// We've just loaded a document, so you can bet we need to flush changes.
dirty = true;
unsaved = false;
}
else//run for load !!!
{
// If no filename was passed in, just create a form and be done with it. If a file name
// was passed, read it.
if (fileName != null)
{
baseClassName = LoadFile(fileName, errors, out xmlDocument);
}
// Let the host know we are done loading.
host.EndLoad(baseClassName, successful, errors);
}
}
///
/// This method is called by the designer host whenever it wants the
/// designer loader to flush any pending changes. Flushing changes
/// does not mean the same thing as saving to disk. For example,
/// In Visual Studio, flushing changes causes new code to be generated
/// and inserted into the text editing window. The user can edit
/// the new code in the editing window, but nothing has been saved
/// to disk. This sample adheres to this separation between flushing
/// and saving, since a flush occurs whenever the code windows are
/// displayed or there is a build. Neither of those items demands a save.
///
protected override void PerformFlush(IDesignerSerializationManager designerSerializationManager)
{
// Nothing to flush if nothing has changed.
if (!dirty)
{
return;
}
//PerformFlushWorker();
}
public override void Dispose()
{
// Always remove attached event handlers in Dispose.
IComponentChangeService cs = host.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
if (cs != null)
{
cs.ComponentChanged -= new ComponentChangedEventHandler(OnComponentChanged);
cs.ComponentAdded -= new ComponentEventHandler(OnComponentAddedRemoved);
cs.ComponentRemoved -= new ComponentEventHandler(OnComponentAddedRemoved);
}
}
#endregion
#region Helper methods
///
/// Simple helper method that returns true if the given type converter supports
/// two-way conversion of the given type.
///
private bool GetConversionSupported(TypeConverter converter, Type conversionType)
{
return (converter.CanConvertFrom(conversionType) && converter.CanConvertTo(conversionType));
}
///
/// As soon as things change, we're dirty, so Flush()ing will give us a new
/// xmlDocument and codeCompileUnit.
///
private void OnComponentChanged(object sender, ComponentChangedEventArgs ce)
{
dirty = true;
unsaved = true;
}
private void OnComponentAddedRemoved(object sender, ComponentEventArgs ce)
{
dirty = true;
unsaved = true;
}
///
/// This method prompts the user to see if it is OK to dispose this document.
/// The prompt only happens if the user has made changes.
///
internal bool PromptDispose()
{
if (dirty || unsaved)
{
switch (MessageBox.Show("Save changes to existing designer?", "Unsaved Changes", MessageBoxButtons.YesNoCancel))
{
case DialogResult.Yes:
Save(false);
break;
case DialogResult.Cancel:
return false;
}
}
return true;
}
#endregion
#region Serialize - Flush
///
/// This will recussively go through all the objects in the tree and
/// serialize them to Xml
///
public void PerformFlushWorker()
{
XmlDocument document = new XmlDocument();
document.AppendChild(document.CreateElement("DOCUMENT_ELEMENT"));
IDesignerHost idh = (IDesignerHost)this.host.GetService(typeof(IDesignerHost));
root = idh.RootComponent;
Hashtable nametable = new Hashtable(idh.Container.Components.Count);
IDesignerSerializationManager manager = host.GetService(typeof(IDesignerSerializationManager)) as IDesignerSerializationManager;
document.DocumentElement.AppendChild(WriteObject(document, nametable, root));
foreach (IComponent comp in idh.Container.Components)
{
if (comp != root && !nametable.ContainsKey(comp))
{
document.DocumentElement.AppendChild(WriteObject(document, nametable, comp));
}
}
xmlDocument = document;
}
private XmlNode WriteObject(XmlDocument document, IDictionary nametable, object value)
{
IDesignerHost idh = (IDesignerHost)this.host.GetService(typeof(IDesignerHost));
Debug.Assert(value != null, "Should not invoke WriteObject with a null value");
XmlNode node = document.CreateElement("Object");
XmlAttribute typeAttr = document.CreateAttribute("type");
typeAttr.Value = value.GetType().AssemblyQualifiedName;
node.Attributes.Append(typeAttr);
IComponent component = value as IComponent;
if (component != null && component.Site != null && component.Site.Name != null)
{
XmlAttribute nameAttr = document.CreateAttribute("name");
nameAttr.Value = component.Site.Name;
node.Attributes.Append(nameAttr);
Debug.Assert(nametable[component] == null, "WriteObject should not be called more than once for the same object. Use WriteReference instead");
nametable[value] = component.Site.Name;
}
bool isControl = (value is Control);
if (isControl)
{
XmlAttribute childAttr = document.CreateAttribute("children");
childAttr.Value = "Controls";
node.Attributes.Append(childAttr);
}
if (component != null)
{
if (isControl)
{
foreach (Control child in ((Control)value).Controls)
{
if (child.Site != null && child.Site.Container == idh.Container)
{
node.AppendChild(WriteObject(document, nametable, child));
}
}
}// if isControl
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value, propertyAttributes);
if (isControl)
{
PropertyDescriptor controlProp = properties["Controls"];
if (controlProp != null)
{
PropertyDescriptor[] propArray = new PropertyDescriptor[properties.Count - 1];
int idx = 0;
foreach (PropertyDescriptor p in properties)
{
if (p != controlProp)
{
propArray[idx++] = p;
}
}
properties = new PropertyDescriptorCollection(propArray);
}
}
WriteProperties(document, properties, value, node, "Property");
EventDescriptorCollection events = TypeDescriptor.GetEvents(value, propertyAttributes);
IEventBindingService bindings = host.GetService(typeof(IEventBindingService)) as IEventBindingService;
if (bindings != null)
{
properties = bindings.GetEventProperties(events);
WriteProperties(document, properties, value, node, "Event");
}
}
else
{
WriteValue(document, value, node);
}
return node;
}
private void WriteProperties(XmlDocument document, PropertyDescriptorCollection properties, object value, XmlNode parent, string elementName)
{
foreach (PropertyDescriptor prop in properties)
{
if (prop.Name == "AutoScaleBaseSize")
{
string _DEBUG_ = prop.Name;
}
if (prop.ShouldSerializeValue(value))
{
string compName = parent.Name;
XmlNode node = document.CreateElement(elementName);
XmlAttribute attr = document.CreateAttribute("name");
attr.Value = prop.Name;
node.Attributes.Append(attr);
DesignerSerializationVisibilityAttribute visibility = (DesignerSerializationVisibilityAttribute)prop.Attributes[typeof(DesignerSerializationVisibilityAttribute)];
switch (visibility.Visibility)
{
case DesignerSerializationVisibility.Visible:
if (!prop.IsReadOnly && WriteValue(document, prop.GetValue(value), node))
{
parent.AppendChild(node);
}
break;
case DesignerSerializationVisibility.Content:
object propValue = prop.GetValue(value);
if (typeof(IList).IsAssignableFrom(prop.PropertyType))
{
WriteCollection(document, (IList)propValue, node);
}
else
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(propValue, propertyAttributes);
WriteProperties(document, props, propValue, node, elementName);
}
if (node.ChildNodes.Count > 0)
{
parent.AppendChild(node);
}
break;
default:
break;
}
}
}
}
private XmlNode WriteReference(XmlDocument document, IComponent value)
{
IDesignerHost idh = (IDesignerHost)this.host.GetService(typeof(IDesignerHost));
Debug.Assert(value != null && value.Site != null && value.Site.Container == idh.Container, "Invalid component passed to WriteReference");
XmlNode node = document.CreateElement("Reference");
XmlAttribute attr = document.CreateAttribute("name");
attr.Value = value.Site.Name;
node.Attributes.Append(attr);
return node;
}
private bool WriteValue(XmlDocument document, object value, XmlNode parent)
{
IDesignerHost idh = (IDesignerHost)this.host.GetService(typeof(IDesignerHost));
// For empty values, we just return. This creates an empty node.
if (value == null)
{
return true;
}
TypeConverter converter = TypeDescriptor.GetConverter(value);
if (GetConversionSupported(converter, typeof(string)))
{
parent.InnerText = (string)converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(string));
}
else if (GetConversionSupported(converter, typeof(byte[])))
{
byte[] data = (byte[])converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(byte[]));
parent.AppendChild(WriteBinary(document, data));
}
else if (GetConversionSupported(converter, typeof(InstanceDescriptor)))
{
InstanceDescriptor id = (InstanceDescriptor)converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(InstanceDescriptor));
parent.AppendChild(WriteInstanceDescriptor(document, id, value));
}
else if (value is IComponent && ((IComponent)value).Site != null && ((IComponent)value).Site.Container == idh.Container)
{
parent.AppendChild(WriteReference(document, (IComponent)value));
}
else if (value.GetType().IsSerializable)
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, value);
XmlNode binaryNode = WriteBinary(document, stream.ToArray());
parent.AppendChild(binaryNode);
}
else
{
return false;
}
return true;
}
private void WriteCollection(XmlDocument document, IList list, XmlNode parent)
{
foreach (object obj in list)
{
XmlNode node = document.CreateElement("Item");
XmlAttribute typeAttr = document.CreateAttribute("type");
typeAttr.Value = obj.GetType().AssemblyQualifiedName;
node.Attributes.Append(typeAttr);
WriteValue(document, obj, node);
parent.AppendChild(node);
}
}
private XmlNode WriteBinary(XmlDocument document, byte[] value)
{
XmlNode node = document.CreateElement("Binary");
node.InnerText = Convert.ToBase64String(value);
return node;
}
private XmlNode WriteInstanceDescriptor(XmlDocument document, InstanceDescriptor desc, object value)
{
XmlNode node = document.CreateElement("InstanceDescriptor");
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, desc.MemberInfo);
XmlAttribute memberAttr = document.CreateAttribute("member");
memberAttr.Value = Convert.ToBase64String(stream.ToArray());
node.Attributes.Append(memberAttr);
foreach (object arg in desc.Arguments)
{
XmlNode argNode = document.CreateElement("Argument");
if (WriteValue(document, arg, argNode))
{
node.AppendChild(argNode);
}
}
if (!desc.IsComplete)
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(value, propertyAttributes);
WriteProperties(document, props, value, node, "Property");
}
return node;
}
#endregion
#region DeSerialize - Load
///
/// This method is used to parse the given file. Before calling this
/// method the host member variable must be setup. This method will
/// create a data set, read the data set from the XML stored in the
/// file, and then walk through the data set and create components
/// stored within it. The data set is stored in the persistedData
/// member variable upon return.
///
/// This method never throws exceptions. It will set the successful
/// ref parameter to false when there are catastrophic errors it can't
/// resolve (like being unable to parse the XML). All error exceptions
/// are added to the errors array list, including minor errors.
///
private string ReadFile(string fileName, ArrayList errors, out XmlDocument document)
{
string baseClass = null;
// Anything unexpected is a fatal error.
//
try
{
// The main form and items in the component tray will be at the
// same level, so we have to create a higher super-root in order
// to construct our XmlDocument.
StreamReader sr = new StreamReader(fileName);
string cleandown = sr.ReadToEnd();
sr.Close();
cleandown = "" + cleandown + "";
XmlDocument doc = new XmlDocument();
doc.LoadXml(cleandown);
// Now, walk through the document's elements.
//
foreach (XmlNode node in doc.DocumentElement.ChildNodes)
{
if (baseClass == null)
{
baseClass = node.Attributes["name"].Value;
}
if (node.Name.Equals("Object"))
{
ReadObject(node, errors);
}
else
{
errors.Add(string.Format("Node type {0} is not allowed here.", node.Name));
}
}
document = doc;
}
catch (Exception ex)
{
document = null;
errors.Add(ex);
}
return baseClass;
}
//run for load
private string LoadFile(string fileName, ArrayList errors, out XmlDocument document)
{
string baseClass = null;
// Anything unexpected is a fatal error.
//
try
{
// The main form and items in the component tray will be at the
// same level, so we have to create a higher super-root in order
// to construct our XmlDocument.
StreamReader sr = new StreamReader(fileName);
string cleandown = sr.ReadToEnd();
sr.Close();
cleandown = "" + cleandown + "";
XmlDocument doc = new XmlDocument();
doc.LoadXml(cleandown);
// Now, walk through the document's elements.
//
foreach (XmlNode node in doc.DocumentElement.ChildNodes)
{
if (baseClass == null)
{
baseClass = node.Attributes["name"].Value;
}
if (node.Name.Equals("Object"))
{
LoadObject(node, errors);
}
else
{
errors.Add(string.Format("Node type {0} is not allowed here.", node.Name));
}
}
document = doc;
}
catch (Exception ex)
{
document = null;
errors.Add(ex);
}
return baseClass;
}
private void ReadEvent(XmlNode childNode, object instance, ArrayList errors)
{
IEventBindingService bindings = host.GetService(typeof(IEventBindingService)) as IEventBindingService;
if (bindings == null)
{
errors.Add("Unable to contact event binding service so we can't bind any events");
return;
}
XmlAttribute nameAttr = childNode.Attributes["name"];
if (nameAttr == null)
{
errors.Add("No event name");
return;
}
XmlAttribute methodAttr = childNode.Attributes["method"];
if (methodAttr == null || methodAttr.Value == null || methodAttr.Value.Length == 0)
{
errors.Add(string.Format("Event {0} has no method bound to it"));
return;
}
EventDescriptor evt = TypeDescriptor.GetEvents(instance)[nameAttr.Value];
if (evt == null)
{
errors.Add(string.Format("Event {0} does not exist on {1}", nameAttr.Value, instance.GetType().FullName));
return;
}
PropertyDescriptor prop = bindings.GetEventProperty(evt);
Debug.Assert(prop != null, "Bad event binding service");
try
{
prop.SetValue(instance, methodAttr.Value);
}
catch (Exception ex)
{
errors.Add(ex.Message);
}
}
private object ReadInstanceDescriptor(XmlNode node, ArrayList errors)
{
// First, need to deserialize the member
//
XmlAttribute memberAttr = node.Attributes["member"];
if (memberAttr == null)
{
errors.Add("No member attribute on instance descriptor");
return null;
}
byte[] data = Convert.FromBase64String(memberAttr.Value);
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream(data);
MemberInfo mi = (MemberInfo)formatter.Deserialize(stream);
object[] args = null;
// Check to see if this member needs arguments. If so, gather
// them from the XML.
if (mi is MethodBase)
{
ParameterInfo[] paramInfos = ((MethodBase)mi).GetParameters();
args = new object[paramInfos.Length];
int idx = 0;
foreach (XmlNode child in node.ChildNodes)
{
if (child.Name.Equals("Argument"))
{
object value;
if (!ReadValue(child, TypeDescriptor.GetConverter(paramInfos[idx].ParameterType), errors, out value))
{
return null;
}
args[idx++] = value;
}
}
if (idx != paramInfos.Length)
{
errors.Add(string.Format("Member {0} requires {1} arguments, not {2}.", mi.Name, args.Length, idx));
return null;
}
}
InstanceDescriptor id = new InstanceDescriptor(mi, args);
object instance = id.Invoke();
// Ok, we have our object. Now, check to see if there are any properties, and if there are,
// set them.
//
foreach (XmlNode prop in node.ChildNodes)
{
if (prop.Name.Equals("Property"))
{
ReadProperty(prop, instance, errors);
}
}
return instance;
}
/// Reads the "Object" tags. This returns an instance of the
/// newly created object. Returns null if there was an error.
private object ReadObject(XmlNode node, ArrayList errors)
{
XmlAttribute typeAttr = node.Attributes["type"];
if (typeAttr == null)
{
errors.Add("