托管 Windows Forms Designer - 在运行时序列化设计器并生成 C# 代码

Hosting Windows Forms Designer - Serialize designer at runtime and generate C# code

我正在创建设计器表面并将控件加载到 运行 时间。 当 deserializing/loading 控制到 运行 时间时,我遇到了问题。

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

发行面例如:

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

主要代码片段有:

从设计时开始序列化

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

运行时间反序列化

这里是反思的尝试:

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

这里是直接加载控件的尝试(仍然绑定到设计时):

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

我觉得我缺少 System.ComponentModel.Design 框架中的一个概念。

我也不认为有必要为每个控件编写自定义序列化程序,因为肯定已经有了这个 Visual Studio 能够序列化它们在 PropertyGrid 并在 运行 程序时加载它们。

我很想将设计器序列化到一个 .cs 文件中,但是怎么做呢?你如何序列化 controls/form 并将属性更改为像 VS 设计器一样的文件,我尝试并只查找了 xml 和二进制序列化程序。我理想的解决方案是用 CodeDom 构建一个 designer.cs

在设计时和 运行 之间完成这种序列化的正确方法是什么?

假设您有一个 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,这是我解决问题的方法:

您还可以稍微扩展示例并使用 ISelectionService 获取有关所选组件的通知,并在 运行 时使用 PropertyGrid:

更改属性

示例 - 在运行时间

从 DesignSurface 生成 C# 代码

在此示例中,我将展示如何在 运行 时主持 windows 表单设计器并设计包含一些控件和组件的表单并在 [=] 时生成 C# 代码86=]-时间和运行生成的代码。

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

创建 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)
{
    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();
    }
}

例如:

var code = GenerateCSFromDesigner(designSurface);

运行代码调用CSharpCodeProvider

然后到运行吧:

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

例如:

Run(GenerateCSFromDesigner(designSurface), "Form1");