【问题标题】:How to Clone a Windows Forms Controls even with non-Serializable properties?即使具有不可序列化的属性,如何克隆 Windows 窗体控件?
【发布时间】:2014-10-03 09:04:10
【问题描述】:

如何克隆或序列化 Windows 窗体控件?
当我尝试使用此代码“CloneControl(Control ct1)”克隆 Windows 窗体控件时,它允许我复制具有一些可序列化属性的控件,而不是所有属性。

public Form1()
{
    InitializeComponent();
        Columns = new DataGridViewTextBoxColumn[2];
        for (int i = 0; i < 2; i++)
        {
            Columns[i] = new System.Windows.Forms.DataGridViewTextBoxColumn();
            // 
            // Columns[i]
            // 
            Columns[i].HeaderText = "j" + (i + 1);
            Columns[i].Name = "Column" + (i + 1);
            Columns[i].Width = 50;
        }
        dataGridView1 = new System.Windows.Forms.DataGridView();
        dataGridView1.Name = "dataGridView1";
        dataGridView1.Location = new System.Drawing.Point(100, 100);
        dataGridView1.RowHeadersWidth = 50;
        dataGridView1.RowTemplate.Height = 25;
        dataGridView1.Size = new System.Drawing.Size(55 + 50 * 2, 25 + dataGridView1.RowTemplate.Height * 2);
        dataGridView1.Anchor = System.Windows.Forms.AnchorStyles.None;
        dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
        dataGridView1.Columns.AddRange(Columns);
        dataGridView1.TabIndex = 3;
        dataGridView1.AllowUserToAddRows = false;
        dataGridView1.Rows.Add();
        dataGridView1.Rows.Add();
        dataGridView1.Rows[0].HeaderCell.Value = "i" + 1;
        dataGridView1.Rows[1].HeaderCell.Value = "i" + 2;
        dataGridView1.Rows[0].Cells[0].Value = "value1";
        Controls.Add(dataGridView1);

        Control cloned1 = CloneControl(dataGridView1); 
        cloned1.SetBounds(cloned1.Location.X, cloned1.Location.Y + 300, cloned1.Width, ct1.Height);
        Controls.Add(cloned1);
        cloned1.Show();
}    

public Control CloneControl(Control ct1)
{
    Hashtable PropertyList = new Hashtable();
    PropertyDescriptorCollection Properties = TypeDescriptor.GetProperties(ct1);
    Assembly controlAsm = Assembly.LoadWithPartialName(ct1.GetType().Namespace);
    Type controlType = controlAsm.GetType(ct1.GetType().Namespace + "." + ct1.GetType().Name);
    Control cloned1 = (Control)Activator.CreateInstance(controlType);
    foreach (PropertyDescriptor pr1 in Properties)
    {
        if (pr1.PropertyType.IsSerializable)
        {
            PropertyList.Add(pr1.Name, pr1.GetValue(ct1));
        }
        if (PropertyList.Contains(pr1.Name))
        {
            try
            {
                 Object obj = PropertyList[pr1.Name];
                 pr1.SetValue(cloned1, obj);
            }
            catch (Exception ex)
            {

            }
        }
    }
    return ct2;
}

如果你运行代码......你会得到

正如您在 main 方法中看到的,我创建了 dataGridView1 的克隆,它具有一些属性。
实际上,克隆的 dataGridView 中的每个单元格值都是空的。 列的大小也不会被克隆!

您可能有一个疑问:如果用 C# 编写的 Visual Studio 或 SharpDeveloper 作为 IDE 可以处理这个问题,那么编写这种代码是可能的!对吧?
在 Visual Studio 中,当您尝试拖放控件或复制和粘贴控件时,它不仅会复制具有所有属性(包括可序列化或不可序列化)的控件,还会将控件本身的名称从“dataGridView1”更改为“ dataGridView2" 以及在 SharpDeveloper 中!

我该怎么办?
我应该创建什么样的方法?
也许另一个控件有许多不可序列化的属性!
如何复制所有这些?

请任何人.....

【问题讨论】:

  • 克隆控件是失败的鲸鱼。 Control 类具有数百个 属性和字段,其中许多严重依赖于运行时状态。您甚至无法参加活动,它们被小心地隐藏在墙后。创建一个 TextBox 总是用代码完成的。只需重新运行该代码以获取另一个代码。您可能必须从 InitializeComponent() 方法中删除该代码的明确要求。
  • 忘记事件...
  • 您的方向正确,只需在 IsSerializable 上放一个 else 并注意您缺少许多重要的属性。
  • 也许这篇文章对你有一些帮助codeproject.com/Articles/12976/…
  • 一个没人问过的问题。你为什么要这样做呢?没有人试图这样做。如果要复制表单,通常会共享控件和数据模型。创建表单的克隆,我不太确定你为什么要这样做

标签: c# winforms serialization clone deep-copy


【解决方案1】:

就像评论中提到的@Hans,Clone 并不是那么容易。如果你想获得一些相同的控件,只是有一点不同,你最好使用一个函数来定义一般行为将不同的属性作为参数传递。例如,我们定义了一个具有一些适用于 DataGridView 的通用属性的函数:

private void InitDataGridView(DataGridView dataGridView, string name)
{
    dataGridView.Name = name;

    // configure other properties here
    dataGridView.Location = new System.Drawing.Point(100, 100);
    dataGridView.RowHeadersWidth = 50;
    dataGridView.RowTemplate.Height = 25;
    dataGridView.Size = new System.Drawing.Size(55 + 50 * 2, 25 + dataGridView1.RowTemplate.Height * 2);
    dataGridView.Anchor = System.Windows.Forms.AnchorStyles.None;
    dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
    // remember to initialize your columns, or pass it in as a parameter
    dataGridView.Columns.AddRange(Columns);
    dataGridView.AllowUserToAddRows = false;
    dataGridView.Rows.Add();
    dataGridView.Rows.Add();
    dataGridView.Rows[0].HeaderCell.Value = "i" + 1;
    dataGridView.Rows[1].HeaderCell.Value = "i" + 2;
    dataGridView.Rows[0].Cells[0].Value = "value1";
}

public Form1()
{
    InitializeComponent();

    var dataGridView1 = new DataGridView();
    var dataGridView2 = new DataGridView();

    InitDataGridView(dataGridView1, "dataGridView1");
    InitDataGridView(dataGridView2, "dataGridView2");
}

【讨论】:

  • 这不仅对其他控件没有用...甚至对其他DataGridView控件到DeepClone也没有用!
【解决方案2】:

IDE(例如 Visual Studio)正在使用 PropertyDescriptorsDesignerSerializationVisibilityShouldSerializeValue但 DataGrid Rows 很特别,因为 您不能在设计时添加它们时间! IDE 无法复制不存在的东西,因此,解决方案必须不同(如果您想克隆 IDE/Designer 无法执行的控件 - 请参阅其他答案和 cmets) 。试试我的代码(除了网格行之外的所有内容都在没有额外检查的情况下被克隆 - 克隆)

foreach(PropertyDescriptor pd in TypeDescriptor.GetProperties(src)) {
    if(!pd.ShouldSerializeValue(src)) {
        if(src is DataGridView && pd.Name == "Rows")
            CopyDataGridRows((DataGridView)src, (DataGridView)dst);
        continue; }

注意:上面可以做得更好(通过检查最后的类),但它是显而易见的。

using System;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace CloneControls {
    public partial class Form1: Form {
        public Form1() { InitializeComponent(); }
        private void Form1_Load(object sender, EventArgs e) {
            dataGridView1.Rows.Add();
            dataGridView1.Rows.Add();
            foreach(Control c in splitContainer1.Panel1.Controls)
                splitContainer1.Panel2.Controls.Add((Control)Clone(c));
        }

        static object Clone(object o) {
            return Copy(o, Activator.CreateInstance(o.GetType()));
        }
        static object Copy(object src, object dst) {
            IList list = src as IList;
            if(list != null) {
                IList to = dst as IList;
                foreach(var x in list)
                    to.Add(Clone(x));
                return dst; }
            foreach(PropertyDescriptor pd in TypeDescriptor.GetProperties(src)) {
                if(!pd.ShouldSerializeValue(src)) {
                    if(src is DataGridView && pd.Name == "Rows")
                        CopyDataGridRows((DataGridView)src, (DataGridView)dst);
                    continue; }
                switch(pd.SerializationVisibility) {
                default: continue;
                case DesignerSerializationVisibility.Visible:
                    if(pd.IsReadOnly) continue;
                    pd.SetValue(dst, pd.GetValue(src));
                    continue;
                case DesignerSerializationVisibility.Content:
                    Copy(pd.GetValue(src), pd.GetValue(dst));
                    continue;
                }
            }
            return dst;
        }
        static void CopyDataGridRows(DataGridView src, DataGridView dst) {
            foreach(DataGridViewRow row in src.Rows)
                if(!row.IsNewRow) dst.Rows.Add((DataGridViewRow)Clone(row));
        }
    }
}

【讨论】:

    【解决方案3】:

    我想我在这里做了更好的方法。
    此方法首先检查属性的接口:如果是 ICollection,则执行第一项工作。
    在这个循环以“DeepClone()”方法结束后,就需要在不检查 PropertyType Interface 的情况下再做一个循环...我的意思是我不能将这两个操作混合到一个循环中?!
    您还可以检测到会有某种运行时异常,因此我将此代码放入 try-catch 块中......

        Control cloned1 = (Control)DeepClone(dataGridView1); 
        cloned1.SetBounds(cloned1.Location.X, cloned1.Location.Y + 300, cloned1.Width, ct1.Height);
        Controls.Add(cloned1);
        cloned1.Show();
    
    
    
    
        public dynamic DeepClone(dynamic ob1)
        {
            dynamic ob2 = null;
            if (ob1.GetType().IsSerializable && !ob1.GetType().IsArray)
            {
                if (ob1 != null)
                {
                    using (MemoryStream ms = new MemoryStream())
                    {
                        BinaryFormatter formatter = new BinaryFormatter();
                        formatter.Serialize(ms, ob1);
                        ms.Position = 0;
                        ob2 = formatter.Deserialize(ms);
                    }
                }
            }
            else
            {
                if (ob1.GetType().IsArray)
                {
                    var r1 = ob1.Rank;
                    object[] d1 = new object[r1];
                    long[] V1 = new long[r1];
                    for (int i = 0; i < r1; i++)
                    {
                        V1[i] = 0;
                        d1[i] = ob1.GetUpperBound(i) + 1;
                    }
                    ob2 = Activator.CreateInstance(ob1.GetType(), d1);
                    for (long i = 0; i <= ob2.Length; i++)
                    {
                        ob2.SetValue(DeepClone(ob1.GetValue(V1)), V1);
                        for (int j = 0; j <= V1.GetUpperBound(0); j++)
                        {
                            if (V1[j] < ob2.GetUpperBound(j))
                            {
                                V1[j]++;
                                break;
                            }
                            else
                            {
                                V1[j] = 0;
                            }
                        }
                    }
                }
                else
                {
                    PropertyInfo[] P1 = ob1.GetType().GetProperties();
                    ob2 = Activator.CreateInstance(ob1.GetType());
                    foreach (PropertyInfo p1 in P1)
                    {
                        try
                        {
                            if (p1.PropertyType.GetInterface("System.Collections.ICollection", true) != null)
                            {
                                dynamic V2 = p1.GetValue(ob1) as IEnumerable;
                                MethodInfo gm1 = p1.PropertyType.GetMethods().Where(m => m.Name == "Add").Where(p => p.GetParameters().Count() == 1).First(f => V2[0].GetType().IsSubclassOf(f.GetParameters()[0].ParameterType) || f.GetParameters()[0].ParameterType == V2[0].GetType());
                                if (V2[0].GetType().IsSubclassOf(gm1.GetParameters()[0].ParameterType) || gm1.GetParameters()[0].ParameterType == V2[0].GetType())
                                {
                                    for (int i = 0; i < V2.Count; i++)
                                    {
                                        dynamic V3 = DeepClone(V2[i]);
                                        gm1.Invoke(p1.GetValue(ob2), new[] {V3});
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
    
                        }
                    }
                    foreach (PropertyInfo p1 in P1)
                    {
                        try
                        {
                            if (p1.PropertyType.IsSerializable && p1.CanWrite)
                            {
                                var v2 = p1.GetValue(ob1);
                                p1.SetValue(ob2, v2);
                            }
                            if (!p1.PropertyType.IsSerializable && p1.CanWrite)
                            {
                                dynamic V2 = p1.GetValue(ob1);
                                if (p1.PropertyType.GetMethod("Clone") != null)
                                {
                                    dynamic v1 = V2.Clone();
                                    p1.SetValue(ob2, v1);
                                }
                            }
                        }
                        catch (Exception ex)
                        {
    
                        }
                    }
                }
            }
            return ob2;
        }
    

    你可能会说这个方法没有复制某种属性,但是它复制了主要属性,克隆的控件看起来就像一个原始控件!

    【讨论】:

    • 如果您可以优化或最小化此方法,即使对于任何人来说都将很容易理解......
    【解决方案4】:

    除非你真的需要一个完全通用的控件克隆方法,否则试图克隆一个控件是多余的。大多数时候,您只需要克隆一个特定的控件,就可以轻松访问创建它的代码(请参阅表单设计器生成的代码,以及您自己编写的设置代码)。 但是,我曾经使用过一次复制多个控件的技巧,以填充 TabControl 的新选项卡,从十个选项卡设计中选择一个。 我也想用C# IDE的Form设计工具来编辑修改10个模板。 所以,除了我的 Tab 控件表单,并使用 VS IDE,我在我的项目中创建了 10 个“控件工厂虚拟表单”。我在每个面板中都放置了一个虚拟面板控件。 每次我必须动态创建一个新选项卡时,我只需实例化一个所需样式的新虚拟窗口。然后我简单地将父窗格移动到我的 ControlTab(使用新选项卡的 Controls.Add() 方法)。 这样,您必须在选项卡创建后(控件移动后)链接事件处理程序。并且事件处理程序的代码应该写在你的主窗口类中,否则你会遇到“this”引用问题。

    显然,您必须将控件引用存储在某处,以便能够访问它们。最简单的方法是跟踪您实例化的每个“虚拟模板表单”并将控件的“修改器”设置为“公共”。您可以使用目标选项卡页的 Tag 属性来存储该引用。但是,为了避免多次强制转换,最好为每个表单类声明一个数组,并将引用存储在那里。

    【讨论】:

      猜你喜欢
      • 2014-05-19
      • 2018-08-24
      • 2010-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-17
      • 1970-01-01
      相关资源
      最近更新 更多