【问题标题】:Hosting Windows Forms Designer - Serialize designer at runtime and generate C# code托管 Windows 窗体设计器 - 在运行时序列化设计器并生成 C# 代码
【发布时间】:2023-04-05 17:56:01
【问题描述】:

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

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

问题面临的例子:

  • 控件仍受设计时约束
  • 并非所有属性都使用所有属性(即嵌套属性)反序列化。
  • 似乎确实遵循了控件关联,即面板中的按钮将不再出现在面板中,即使该属性在加载后仍然是父级。

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

主要有sn-ps代码:

从设计时序列化

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 和二进制序列化程序。我理想的解决方案是使用CodeDom 构建designer.cs

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

【问题讨论】:

  • 你应该序列化整个表单。然后加载表单,编译并运行它。
  • @RezaAghaei 它产生了确切的示例问题:(
  • 通过序列化,我的意思是序列化到一个文本文件,就像 VS 为你做的那样,当你打开文件时,你应该看到类似designer.cs 文件的东西。然后就需要加载编译运行了。
  • @RezaAghaei 我很想这样做,但是怎么做呢?你如何序列化控件/表单并将属性更改为像设计器这样的文件,我尝试并只查找 xml 和二进制序列化程序。我理想的解决方案是按照您的建议使用 CodeDom 构建 Designer.cs。你能提供一个解决方案吗?

标签: c# .net winforms windows-forms-designer codedom


【解决方案1】:

假设您有一个 DesignSurface 来将 Form 显示为设计器的根组件,并使用 IDesignerHostCreateComponent 方法在运行时创建一些组件,这是我解决问题的方法:

您还可以稍微扩展示例并使用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");

【讨论】:

  • 感谢您提供非常完整且有用的答案!这似乎解决了我在运行时的所有问题,尽管重新创建了另一个问题,为设计器重新加载生成的代码。我整个上午都在尝试将生成的代码从 GenerateCodeFromType 返回到 TypeCodeDomSerializer Deserializer 方法,我得到的是 CodeUnit 类。为了完整性,将您的答案扩展到包括此内容?
  • 假设您已经使用此设计器生成了一个 c# 代码,那么您是否想使用该生成的代码加载设计器?
  • 完全正确,就像 VS Designer 所做的那样。因为我不希望像在运行时那样编译生成的代码。我认为它使用 TypeCodeDomSerializer Deserialize 方法,但是从生成代码到 CodeTypeDeclaration 是我卡住的地方。除非有别的办法?
  • @TommyBoii 我试了一下(但失败了)。看起来它使这个问题过于宽泛且没有重点。我建议您发布另一个问题,包括您尝试使用一段序列化代码加载设计器。如果您认为它可能会有所帮助。也可以参考这个问题。
  • 如果您提出了新问题,请随时通知我,我也会试一试,同时您也有机会从其他社区成员那里获得答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-25
  • 2010-11-25
  • 2015-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多