5

I am creating a designer surface and loading the controls to a runtime. I am having issues when deserializing/loading the controls to the runtime.

All methods I have tried seem to have some type of issue.

Issued faced for example:

  • Controls are still bound of the design-time
  • Not all properties deserialize with all the properties, namely nested properties.
  • Control associations does seem to be followed, i.e. Button in a Panel, will not be in the panel anymore, even though the property is still the parent after loading.

I have created a sample Project on git here: Surface Designer Test

There are the main code snippets:

Serialization from the design-time

private void LoadRuntime(int type)
{
    var controls = surface.ComponentContainer.Components;
    SerializationStore data = (SerializationStore)surface.
        _designerSerializationService.Serialize(controls);
    MemoryStream ms = new MemoryStream();
    data.Save(ms);
    SaveData.Data = ms.ToArray();
    SaveData.LoadType = type;
    new RuntimeForm().Show();
}

public object Serialize(System.Collections.ICollection objects)
{
    ComponentSerializationService componentSerializationService = 
        _serviceProvider.GetService(typeof(ComponentSerializationService)) as 
        ComponentSerializationService;
    SerializationStore returnObject = null;
    using (SerializationStore serializationStore = 
        componentSerializationService.CreateStore())
    {
        foreach (object obj in objects)
        {
            if (obj is Control control)
            {
                componentSerializationService.SerializeAbsolute(serializationStore, obj);
            }
            returnObject = serializationStore;
        }
    }
    return returnObject;
}

Deserialization in runtime

Here is attempt with reflection:

MemoryStream ms = new MemoryStream(SaveData.Data);
Designer d = new Designer();
var controls = d._designerSerializationService.Deserialize(ms);

ms.Close();
if (SaveData.LoadType == 1)
{
    foreach (Control cont in controls)
    {
        var ts = Assembly.Load(cont.GetType().Assembly.FullName);
        var o = ts.GetType(cont.GetType().FullName);
        Control controlform = (Control)Activator.CreateInstance(o);
        PropertyInfo[] controlProperties = cont.GetType().GetProperties();
        foreach (PropertyInfo propInfo in controlProperties)
        {
            if (propInfo.CanWrite)
            {
                if (propInfo.Name != "Site" && propInfo.Name != WindowTarget")
                {
                    try
                    {
                        var obj = propInfo.GetValue(cont, null);
                        propInfo.SetValue(controlform, obj, null);
                    }
                    catch { }
                }
                else { }
            }
        }
        Controls.Add(controlform);
    }
}

Here is attempt with loading controls directly (still bound to the design-time):

MemoryStream ms = new MemoryStream(SaveData.Data);
Designer d = new Designer();
var controls = d._designerSerializationService.Deserialize(ms);
foreach (Control cont in controls)
    Controls.Add(cont);

I feel like I am missing a concept from the System.ComponentModel.Design framework.

I also do not believe there is a need to write a custom serializer for each control, as surely the already have this has Visual Studio is able to serialize all their properties as they are changed in the PropertyGrid and load them back when you run the program.

I'd love to serialize the designer into a .cs file, but how? How do you serialize controls/form and changed properties to a file like the VS designer, I tried and looked only to find xml and binary serializer. My ideal solution would be build a designer.cs with the CodeDom.

What is the correct way do accomplish this serialization between design-time and run-time?

6
  • You should serialize the whole form. Then load the form, compile and run it. Commented Dec 30, 2019 at 16:19
  • @RezaAghaei It produces the exact sample issues :( Commented Dec 30, 2019 at 16:48
  • By serializing, I mean serializing to a text file, exactly like VS does for you, and when you open the file, you should see something like designer.cs files. Then you need to load and compile and run it. Commented Dec 30, 2019 at 16:54
  • @RezaAghaei I would love to do it that way, but how? How do you serialize controls/form and changed properties to a file like the designer, I tried and looked only to find xml and binary serializer. My ideal solution would be build a designer.cs with the CodeDom like you suggest. Could you provide a solution? Commented Dec 30, 2019 at 17:03
  • Have you tried CodeDomComponentSerializationService? Commented Dec 30, 2019 at 17:29

1 Answer 1

10

Assuming you have a DesignSurface to show a Form as root component of the designer and having some components created at run-time by using CreateComponent method of IDesignerHost, here is how I approach the problem:

You can also extend the example a bit and use ISelectionService to get notified about selected components and change properties at run-time using a PropertyGrid:

enter image description here

Example - Generate C# code from DesignSurface at runtime

Here in this example, I'll show how you can host a windows forms designer at run-time and design a form containing some controls and components and generate C# code at run-time and run the generated code.

Please note: It's not a production code and it's just an example as a proof of concept.

Create the DesignSurface and host the designer

You can create the design surface like this:

DesignSurface designSurface;
private void Form1_Load(object sender, EventArgs e)
{
    designSurface = new DesignSurface(typeof(Form));
    var host = (IDesignerHost)designSurface.GetService(typeof(IDesignerHost));
    var root = (Form)host.RootComponent;
    TypeDescriptor.GetProperties(root)["Name"].SetValue(root, "Form1");
    root.Text = "Form1";

    var button1 = (Button)host.CreateComponent(typeof(Button), "button1");
    button1.Text = "button1";
    button1.Location = new Point(8, 8);
    root.Controls.Add(button1);

    var timer1 = (Timer)host.CreateComponent(typeof(Timer), "timer1");
    timer1.Interval = 2000;
    var view = (Control)designSurface.View;
    view.Dock = DockStyle.Fill;
    view.BackColor = Color.White;
    this.Controls.Add(view);
}

Generate C# code using TypeCodeDomSerializer and CSharpCodeProvider

This is how I generate code from design surface:

string GenerateCSFromDesigner(DesignSurface designSurface)
{
    CodeTypeDeclaration type;
    var host = (IDesignerHost)designSurface.GetService(typeof(IDesignerHost));
    var root = host.RootComponent;
    var manager = new DesignerSerializationManager(host);
    using (manager.CreateSession())
    {
        var serializer = (TypeCodeDomSerializer)manager.GetSerializer(root.GetType(),
            typeof(TypeCodeDomSerializer));
        type = serializer.Serialize(manager, root, host.Container.Components);
        type.IsPartial = true;
        type.Members.OfType<CodeConstructor>()
            .FirstOrDefault().Attributes = MemberAttributes.Public;
    }
    var builder = new StringBuilder();
    CodeGeneratorOptions option = new CodeGeneratorOptions();
    option.BracingStyle = "C";
    option.BlankLinesBetweenMembers = false;
    using (var writer = new StringWriter(builder, CultureInfo.InvariantCulture))
    {
        using (var codeDomProvider = new CSharpCodeProvider())
        {
            codeDomProvider.GenerateCodeFromType(type, writer, option);
        }
        return builder.ToString();
    }
}

For example:

var code = GenerateCSFromDesigner(designSurface);

Run the code sing CSharpCodeProvider

Then to run it:

void Run(string code, string formName)
{
    var csc = new CSharpCodeProvider();
    var parameters = new CompilerParameters(new[] {
    "mscorlib.dll",
    "System.Windows.Forms.dll",
    "System.dll",
    "System.Drawing.dll",
    "System.Core.dll",
    "Microsoft.CSharp.dll"});
    parameters.GenerateExecutable = true;
    code = $@"
        {code}
        public class Program
        {{
            [System.STAThread]
            static void Main()
            {{
                System.Windows.Forms.Application.EnableVisualStyles();
                System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
                System.Windows.Forms.Application.Run(new {formName}());
            }}
        }}";
    var results = csc.CompileAssemblyFromSource(parameters, code);
    if (!results.Errors.HasErrors)
    {
        System.Diagnostics.Process.Start(results.CompiledAssembly.CodeBase);
    }
    else
    {
        var errors = string.Join(Environment.NewLine,
            results.Errors.Cast<CompilerError>().Select(x => x.ErrorText));
        MessageBox.Show(errors);
    }
}

For example:

Run(GenerateCSFromDesigner(designSurface), "Form1");
Sign up to request clarification or add additional context in comments.

20 Comments

Thank you for a very complete and helpful answer! This seems to solve all my problems at run time, although recreates another issue, reloading the generated code for the designer. I have tried all morning to get the generated code from the GenerateCodeFromType back to the TypeCodeDomSerializer Deserializer method, closed I get is the CodeUnit Class. For completeness would be so kind to expand your answer to include this?
Let's say you have generated a c# code using this designer, then do you want to be abke to load the designer using that generated code?
Exactly, like VS Designer would do. As I would not expect to to compile the generated code like it does in the runtime. I would presume it uses the TypeCodeDomSerializer Deserialize Method but to it from generate code but to CodeTypeDeclaration, is where I am getting stuck. Unless there is another way?
@TommyBoii I gave it a try (and failed). It looks like it makes this question too broad and unfocused. I suggest you post another question, including your attempt to load the designer using a piece of serialized code. If you think it may help. refer to this question as well.
@Jack thanks for the feedback. All the Windows Forms designer features are implemented in .NET class libraries (in .NET framework and VS libs) and I guess you cannot use them. The way that windows Forms Designer works is: It create real instances of the components at design time, but put them behind an overlay, so that the user cannot really interact with them; and all the selection and changing their properties will be done indirectly. The designer serializes the design into a code, and later it's able to deserialize the code and create the components at design-time.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.