65.9K
CodeProject 正在变化。 阅读更多。
Home

运行时绝对动态表单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (10投票s)

2014 年 6 月 17 日

MIT

4分钟阅读

viewsIcon

21059

downloadIcon

1427

如何在运行时添加和编码动态 .NET 控件

Sample Image - maximum width is 600 pixels

引言

本文将解释如何在运行时创建控件,在表单中移动控件,以及最终如何为控件添加功能。

为了完成这项任务,本项目分为三个部分

  • 动态添加和移动控件
  • 动态编码控件
  • 最后将包含所有控件和所有代码的表单序列化到硬盘上

如果您想跳过整个阅读部分并开始探索,请单击“下载演示项目”。
如果您想自己使用代码,只需在 redist 文件夹中添加对所有 DLL 的引用即可。

以下代码展示了如何完成这 3 个任务

using DynamicV2; 
using Dyncontrols; 

var Button=Extensions.Control_Create(typeof(Button), new Point(100, 100));

Extensions.Control_MoveandResize(Button);
        
new Codeeditor(Button).Show();

byte[] control=ControlFactory.Serialize(Button);

背景

有时,我们发现我们想更改表单的行为。我们需要添加按钮并添加事件,但我们可以在运行时完成所有这些吗?
是的,我们可以!

不久前,出现了一件小事。它被称为 CodeDom 提供程序类。它使我们能够在运行时从头开始动态编译类。
这基本上意味着我们可以凭空创建类实例,我们可以使用它们来订阅和取消订阅事件以及动态按钮的代码。

为了让您更方便,我在此项目中使用了其他人编写的几个程序集。我声称没有创建它们中的任何一个

在我看来,这是迄今为止最好的 C# 编辑器控件。看看吧

用于简单移动控件的基础。我添加了一个吸附到其他控制逻辑。

用于表单和控件序列化的基础。我将其递归化以处理带有控件的控件。(滚动条等)

创建可移动控件

Extensions.Control_MoveandResize(Button);

这还支持自动布局定位。非常方便。

使用编译器

以下行是自动为您创建的。您可以在编辑器窗口中输入新代码。所有变量都转换为正确的类型。双击事件 treeview 会添加 eventhandler 的代码,并启用简单的代码输入。关闭代码窗口后,可以更改所有变量名。这意味着您可以根据需要命名所有按钮和复选框。
您在下面看到实际发生的事情

Sample Image - maximum width is 600 pixels

#region Declarations
private System.Windows.Forms.CheckBox CheckBox0; 
private System.Windows.Forms.GroupBox GroupBox0; 
private System.Windows.Forms.Button Pressbutton; 
private System.Windows.Forms.Form Mainwindow; 
//All initializations: 
    public void ControlInit(object Controlobj,List<control> Customercollection ) 
{ 
    CheckBox0 = (System.Windows.Forms.CheckBox)Customercollection[0]; 
    GroupBox0 = (System.Windows.Forms.GroupBox)Customercollection[1]; 
    Pressbutton = (System.Windows.Forms.Button)Customercollection[2]; 
    Mainwindow = (System.Windows.Forms.Form)Customercollection[3]; 
    Pressbutton.Click+= new EventHandler(Pressbutton_Click); 
} 
#endregion Declarations

在“另一边”(表单的不可更改部分)是这段代码:它编译 string 并将对所有控件的引用添加到传递给 changeclass 的对象数组中,changeclass 由 codedom 创建。

provider.CompileAssemblyFromSource(Parameters, syntaxEdit1.Text););
var o = results.CompiledAssembly.CreateInstance("Dynamicform.Changeclass"); 
MethodInfo setevents = o.GetType().GetMethod("ControlInit"); 
object[] arr = new object[2]; 
arr[0] = Patient; 
arr[1] = Extensions.GetSelfAndChildrenRecursive(Patient.FindForm()).ToList(); 
try 
{ 
    setevents.Invoke();
}

如果您的代码在执行期间抛出异常,则会出现一条神秘的错误消息,例如:c826asdk.dll 中出现未处理的异常,这不会对您有所帮助。
因此,捕获所有异常并显示堆栈的前两个元素。 --> Button0_clickton0_click 中出现错误。

序列化表单

请查看

基本上,您要对控件进行序列化所要做的就是序列化所有标记为可序列化的属性,并存储父引用和子引用。
我扩展了该类以支持将表单或控件序列化到文件或 byte[]。如果您希望从文件加载表单,只需写入

ControlFactory.Serializetofile(this, "Visuals.inf");

var form = ControlFactory.Deserializefromfile("Visuals.inf") as Form; 
Application.Run(form);    

再简单不过了。

public static void Serializetofile(Control Serialize,string filename) 
{ 
var controls = GetSelfAndChildrenRecursive(Serialize); 
List<Tuple<CBFormCtrl, string,string>> 
dictionary=new List<System.Tuple<CBFormCtrl,string,string>>();
 
foreach (Control k in controls) 
{ 
    if (k.Parent != null) 
    { 
        dictionary.Add(new Tuple<CBFormCtrl, string,string>
        (new CBFormCtrl(k), k.GetHashCode().ToString(),k.Parent.GetHashCode().ToString())); 
    } 
    else 
    { 
        //parent
        dictionary.Add(new Tuple<CBFormCtrl, string, 
        string>(new CBFormCtrl(k), k.GetHashCode().ToString(), "null")); } 
    } 

    var file = File.Create(filename); 
    BinaryFormatter formatter = new BinaryFormatter(); 
    formatter.Serialize(file, dictionary); 
    file.Close(); 
} 

关注点

在编码过程中,我想为每个控件设置图标。我发现了一种方法可以从任何控件中提取工具箱图像!

var k=new BIcon.FromHandle(new Bitmap
(new System.Drawing.ToolboxBitmapAttribute(k.GetType()).GetImage(k)).GetHicon());

结论

您可以使用此项目创建可用的 Windows Forms 并在其他应用程序中导入它们。您可以将它的一部分用于控件序列化或控件重定位。您可以在运行时更改所有属性。

限制

在运行时创建按钮、移动它们并为它们编写代码很有趣,但它是有代价的

  • 每次您编辑代码时,都会创建一个新的程序集并将其加载到 appdomain 中。无法从一个 appdomain 卸载 DLL。
  • 并非所有属性都是可序列化的。这些按钮可能看起来相同,但它们并没有共享大多数属性。除父子关系之外的所有其他控件链接都将丢失。
  • 如果您在反序列化类实例中工作,调试会变得很困难。
  • 代码编辑器使用任何控件的 tag 属性作为源代码的存储空间。
© . All rights reserved.