托管 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
,这是我解决问题的方法:
- 获取
IDesignerHost
from DesignSurface
的实例
- 新建
DesignerSerializationManager
- 从序列化管理器中获取
TypeCodeDomSerializer
的实例
- 序列化
RootComponent
of the IDesignerHost
- 创建
CSharpCodeProvider
的实例
- 通过调用
GenerateCodeFromType
并传递序列化的根组件来生成代码。
您还可以稍微扩展示例并使用 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");
我正在创建设计器表面并将控件加载到 运行 时间。 当 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
,这是我解决问题的方法:
- 获取
IDesignerHost
fromDesignSurface
的实例
- 新建
DesignerSerializationManager
- 从序列化管理器中获取
TypeCodeDomSerializer
的实例 - 序列化
RootComponent
of theIDesignerHost
- 创建
CSharpCodeProvider
的实例
- 通过调用
GenerateCodeFromType
并传递序列化的根组件来生成代码。
您还可以稍微扩展示例并使用 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");