托管Windows Forms Designer-在运行时序列化设计器并生成C#代码 [英] Hosting Windows Forms Designer - Serialize designer at runtime and generate C# code

查看:556
本文介绍了托管Windows Forms Designer-在运行时序列化设计器并生成C#代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在创建设计器图面,并将控件加载到运行时。
将控件反序列化/加载到运行时时出现问题。



我尝试过的所有方法似乎都存在某种类型的问题。



例如,遇到了这样的问题:


  • 控件仍受设计时的约束

  • 并非所有属性都与所有属性反序列化,即嵌套的属性。

  • 似乎遵循了控件关联,即面板中的Button不再位于面板中,即使属性在加载后仍然是父级。
  • >


我在git上创建了一个示例项目:



示例-在运行时从DesignSurface生成C#代码



在此示例中,我将说明如何在运行时托管Windows窗体设计器并设计包含一些控件和组件的窗体,并在运行时生成C#代码并运行生成的代码。


请注意:这不是生产代码,只是示例le作为
的概念证明。


创建DesignSurface并托管设计师

您可以像这样创建设计图面:

  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);
}

使用TypeCodeDomSerializer和CSharpCodeProvider生成C#代码

这是我从设计图面生成代码的方式:

  string GenerateCSFromDesigner( DesignSurface designSurface)
{
CodeType声明类型;
var host =(IDesignerHost)designSurface.GetService(typeof(IDesignerHost));
var root = host.RootComponent;
var manager = new DesignerSerializationManager(host);
使用(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选项=新的CodeGeneratorOptions();
option.BracingStyle = C;
option.BlankLinesBetweenMembers = false;
使用(var writer = new StringWriter(builder,CultureInfo.InvariantCulture))
{
using(var codeDomProvider = new CSharpCodeProvider())
{
codeDomProvider.GenerateCodeFromType (类型,作者,选项);
}
返回builder.ToString();
}
}

例如:

  var code = GenerateCSFromDesigner(designSurface); 

运行代码CSharpCodeProvider



然后运行它:

  void运行(字符串代码,字符串formName)
{
var csc = new CSharpCodeProvider();
var参数=新的CompilerParameters(new [] {
mscorlib.dll,
System.Windows.Forms.dll,
System.dll,
System.Drawing.dll,
System.Core.dll,
Microsoft.CSharp.dll});
参数.GenerateExecutable = true;
代码= $ @
{code}
公共类程序
{{
[System.STAThread]
static void Main()
{{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
System.Windows.Forms.Application.Run( new {formName}());
}}
}};
var结果= csc.CompileAssemblyFromSource(参数,代码);
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(错误);
}
}

例如:

  Run(GenerateCSFromDesigner(designSurface), Form1); 


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?

解决方案

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:

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");

这篇关于托管Windows Forms Designer-在运行时序列化设计器并生成C#代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆