首页 > 解决方案 > 托管 Windows 窗体设计器 - 在运行时序列化设计器并生成 C# 代码

问题描述

我正在创建一个设计器表面并将控件加载到运行时。将控件反序列化/加载到运行时时遇到问题。

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

发行面临例如:

我在这里在 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文件中,但是如何?你如何序列化控件/表单并将属性更改为像 VS 设计器这样的文件,我尝试并只查找 xml 和二进制序列化程序。我理想的解决方案是designer.cs使用CodeDom.

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

标签: c#.netwinformswindows-forms-designercodedom

解决方案


假设您必须将 aDesignSurface显示Form为设计器的根组件并在运行时使用CreateComponentof 方法创建一些组件IDesignerHost,这是我解决问题的方法:

您还可以稍微扩展示例并使用ISelectionService以下命令在运行时获取有关所选组件的通知和更改属性PropertyGrid

在此处输入图像描述

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

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

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

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

运行代码 sing 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");

推荐阅读