c# - 托管 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
.
在设计时和运行时之间完成这种序列化的正确方法是什么?
解决方案
假设您必须将 aDesignSurface
显示Form
为设计器的根组件并在运行时使用CreateComponent
of 方法创建一些组件IDesignerHost
,这是我解决问题的方法:
- 获取
IDesignerHost
from的实例DesignSurface
- 创建新的
DesignerSerializationManager
TypeCodeDomSerializer
从序列化管理器中获取一个实例- 序列化
RootComponent
的IDesignerHost
- 创建一个实例
CSharpCodeProvider
- 通过调用
GenerateCodeFromType
和传递序列化的根组件来生成代码。
您还可以稍微扩展示例并使用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");
推荐阅读
- hibernate - 数据库回调,如 PreUpdate、PreInsert with Spring data jpa 和 QueryDSL
- c++ - 如何使用作为参数传递的信息来构建在其他 CPP 文件中定义的字符串
- google-apps-script - Google Form onsubmit 获取用户电子邮件和全名
- r - 在 R 中提取栅格的最快方法(提高可重现代码的时间)
- r - 使用 R 进行网页抓取,如何使用“打印('NA')”就是缺乏
的?
- excel - SUMIF 与 listdown 标准 pt.2
- javascript - React Collapse - 我如何切换列表中的项目?
- properties - wix 在 PublishProduct 之前将 INSTALLLOCATION 传递给我的自定义对话框中的编辑控件
- lambda - 是否存在两个外延相等但具有不同范式的 lambda 项?
- facebook - Facebook Bot 开始按钮