///------------------------------------------------------------------------------
///
/// Copyright (c) Microsoft Corporation. All Rights Reserved.
///
/// This source code is intended only as a supplement to Microsoft
/// Development Tools and/or on-line documentation. See these other
/// materials for detailed information regarding Microsoft code samples.
///
///
//------------------------------------------------------------------------------
namespace SampleDesignerHost
{
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.CodeDom.Compiler;
using System.CodeDom;
using Microsoft.CSharp;
using Microsoft.VisualBasic;
using SampleDesignerApplication;
using System.Resources;
/// This is a designer loader that is based on XML. We use reflection
/// to write out values into an XML document. The techniques used in this
/// designer loader to discover, via reflection, the properties and
/// objects that need to be saved or loaded can be applied to any
/// persistence format.
///
/// The XML format we use here is not terribly user-friendly, but
/// is fairly straightforward. It handles the vast majority of
/// persistence requirements including collections, instance descriptors,
/// and binary data.
///
/// In addition to maintaining the buffer in the form of an XmlDocument,
/// we also maintain it in a CodeCompileUnit. We use this DOM to generate
/// C# and VB code, as well as to compile the buffer into an executable.
public delegate void ModifyDelegate();
public class SampleDesignerLoader : DesignerLoader
{
private bool _dirty;
public bool dirty
{
get
{
return _dirty;
}
set
{
_dirty = value;
if (value && modify != null) modify();
}
}
public ModifyDelegate modify;
private bool unsaved;
private string fileName;
private string executable;
internal IDesignerLoaderHost host;
private XmlDocument xmlDocument;
private CodeCompileUnit codeCompileUnit;
private Process run;
private static readonly Attribute[] propertyAttributes = new Attribute[]
{
DesignOnlyAttribute.No
};
/// Empty constructor simply creates a new form.
public SampleDesignerLoader()
{
}
/// 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 SampleDesignerLoader(string fileName)
{
if (fileName == null)
{
throw new ArgumentNullException("fileName");
}
this.fileName = fileName;
}
// Called by the host when we load a document.
public override void BeginLoad(IDesignerLoaderHost host)
{
if (host == null)
{
throw new ArgumentNullException("SampleDesignerLoader.BeginLoad: Invalid designerLoaderHost.");
}
this.host = host;
// The loader will put error messages in here.
//
ArrayList errors = new ArrayList();
bool successful = true;
string baseClassName;
// The loader is responsible for providing certain services to the host.
//
host.AddService(typeof(IDesignerSerializationManager), new SampleDesignerSerializationManager(this));
host.AddService(typeof(IEventBindingService), new SampleEventBindingService(host));
host.AddService(typeof(ITypeResolutionService), new SampleTypeResolutionService());
host.AddService(typeof(CodeDomProvider), new CSharpCodeProvider());
host.AddService(typeof(IResourceService), new SampleResourceService(host));
// 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 = host.CreateComponent(typeof(System.Windows.Forms.Form)).Site.Name;
}
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;
}
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);
}
}
/// 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.
public override void Flush()
{
// Nothing to flush if nothing has changed.
if (!dirty)
{
return;
}
// We use an XmlDocument to build up the XML for
// the designer. Here is a sample XML chunk:
//
XmlDocument document = new XmlDocument();
// This element will serve as the undisputed DocumentElement (root)
// of our document. This allows us to have objects of equal level below,
// which we need, since component tray items are not children of the form.
//
document.AppendChild(document.CreateElement("DOCUMENT_ELEMENT"));
// We start with the root component and then continue
// to all the rest of the components. The nametable
// object we create keeps track of which objects we have
// seen. As we write out an object's contents, we save
// it in the nametable, so we don't write out an object
// twice.
//
IComponent root = host.RootComponent;
Hashtable nametable = new Hashtable(host.Container.Components.Count);
resources_file_name = Path.GetDirectoryName(full_file_name) + '\\' + Path.GetFileNameWithoutExtension(file_name) + '.' + root.Site.Name + ".resources";
resources_not_empty = false;
current_component = root;
document.DocumentElement.AppendChild(WriteObject(document, nametable, root));
foreach(IComponent comp in host.Container.Components)
{
if (comp != root && !nametable.ContainsKey(comp))
{
current_component = comp;
document.DocumentElement.AppendChild(WriteObject(document, nametable, comp));
}
}
FinishResources();
// Along with the XML, we also represent the designer in a CodeCompileUnit,
// which we can use to generate C# and VB, and which we can compile from.
//
CodeCompileUnit code = new CodeCompileUnit();
// Our dummy namespace is the name of our main form + "Namespace". Creative, eh?
CodeNamespace ns = new CodeNamespace(root.Site.Name + "Namespace");
ns.Imports.Add(new CodeNamespaceImport("System"));
// We need to look at our type resolution service to find out what references
// to import.
//
SampleTypeResolutionService strs = host.GetService(typeof(ITypeResolutionService)) as SampleTypeResolutionService;
foreach (Assembly assm in strs.RefencedAssemblies)
{
ns.Imports.Add(new CodeNamespaceImport(assm.GetName().Name));
}
//ssyy
host.RemoveService(typeof(IDesignerSerializationManager));
host.AddService(typeof(IDesignerSerializationManager), new SampleDesignerSerializationManager(this));
// Autogenerates member declaration and InitializeComponent()
// in a new CodeTypeDeclaration
//
RootDesignerSerializerAttribute a = TypeDescriptor.GetAttributes(root)[typeof(RootDesignerSerializerAttribute)] as RootDesignerSerializerAttribute;
Type t = host.GetType(a.SerializerTypeName);
CodeDomSerializer cds = Activator.CreateInstance(t) as CodeDomSerializer;
IDesignerSerializationManager manager = host.GetService(typeof(IDesignerSerializationManager)) as IDesignerSerializationManager;
CodeTypeDeclaration td = cds.Serialize(manager, root) as CodeTypeDeclaration;
// We need a constructor that will call the InitializeComponent()
// that we just generated.
//
CodeConstructor con = new CodeConstructor();
con.Attributes = MemberAttributes.Public;
con.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), "InitializeComponent")));
td.Members.Add(con);
// Finally our Main method, where the magic begins.
CodeEntryPointMethod main = new CodeEntryPointMethod();
main.Name = "Main";
main.Attributes = MemberAttributes.Public | MemberAttributes.Static;
main.CustomAttributes.Add(new CodeAttributeDeclaration("System.STAThreadAttribute"));
main.Statements.Add(new CodeMethodInvokeExpression(new CodeMethodReferenceExpression(
new CodeTypeReferenceExpression(
typeof(System.Windows.Forms.Application)), "Run"),
new CodeExpression[] {
new CodeObjectCreateExpression(new CodeTypeReference(root.Site.Name))
}));
td.Members.Add(main);
ns.Types.Add(td);
code.Namespaces.Add(ns);
// Now we reset our dirty bit and set the member
// variables.
dirty = false;
xmlDocument = document;
codeCompileUnit = code;
// Now we update the code windows to show what new stuff we've learned.
UpdateCodeWindows();
resources_not_empty = false;
}
public string file_name;
public string full_file_name;
public string form_name;
public string resources_file_name;
public bool resources_not_empty = false;
private ResourceWriter resource_writer = null;
private IComponent current_component = null;
private PropertyDescriptor current_property = null;
/// This method writes out the contents of our designer in C#, VB, and XML.
/// For the first two, it generates code from our codeCompileUnit. For the XML,
/// it writes out the contents of xmlDocument.
private void UpdateCodeWindows()
{
// The main form's TabControl was added to the host's lists of services
// just so we could get at it here. Fortunately for us, each tab page
// has but one Control--a textbox.
//
CodeStrings codes = host.GetService(typeof(CodeStrings)) as CodeStrings;
//TabControl tc = host.GetService(typeof(TabControl)) as TabControl;
//TextBox csWindow = tc.TabPages[1].Controls[0] as TextBox;
//TextBox vbWindow = tc.TabPages[2].Controls[0] as TextBox;
//TextBox xmlWindow = tc.TabPages[3].Controls[0] as TextBox;
// The string writer we'll generate code to.
StringWriter sw;
// The options for our code generation.
CodeGeneratorOptions o = new CodeGeneratorOptions();
o.BlankLinesBetweenMembers = false;
o.BracingStyle = "C";
o.ElseOnClosing = false;
o.IndentString = " ";
// CSharp Code Generation
//sw = new StringWriter();
//CSharpCodeProvider cs = new CSharpCodeProvider();
//cs.CreateGenerator().GenerateCodeFromCompileUnit(codeCompileUnit, sw, o);
//csWindow.Text = sw.ToString();
//sw.Close();
// PABC Code Generation
sw = new StringWriter();
if (resources_not_empty)
{
sw.WriteLine("{$resource " + Path.GetFileName(resources_file_name) + '}');
sw.Write(" ");
}
//VBCodeProvider vb = new VBCodeProvider();
//vb.CreateGenerator().GenerateCodeFromCompileUnit(codeCompileUnit, sw, o);
ICodeGenerator abc = new PABCCodeGenerator();
//sw.Write(string.Format(string_consts.begin_unit, file_name, form_name));
abc.GenerateCodeFromCompileUnit(codeCompileUnit, sw, o);
//sw.Write(string_consts.end_unit);
codes.PascalABCCode = sw.ToString();
sw.Close();
// XML Output
sw = new StringWriter();
XmlTextWriter xtw = new XmlTextWriter(sw);
xtw.Formatting = Formatting.Indented;
xmlDocument.WriteTo(xtw);
// Get rid of our artificial super-root before we display the XML.
//
string cleanup = sw.ToString().Replace("", "");
cleanup = cleanup.Replace("", "");
//xmlWindow.Text = cleanup;
codes.XMLCode = cleanup;
sw.Close();
}
/// 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;
}
/// Reads an Event node and binds the event.
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);
}
}
/// 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();
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;
}
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("