How to Clone a Windows Forms Controls even with non-Serializable properties?

走远了吗. 提交于 2019-12-11 11:04:56

问题


How to Clone or Serialize a Windows Forms Control?
When I am trying to Clone windows forms controls using this code "CloneControl(Control ct1)", it allows me to duplicate controls with some Serializable properties, not with all properties.

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;
}

If you run the code... the you will get

As you can see in the main method I create a clone of dataGridView1, which has a few properties.
And actually each cell value is null in a cloned dataGridView. Also size of a columns are not cloned!

You may have a question: if Visual Studio or SharpDeveloper as IDE which is written in C# can handle this problem, then it might be possible to write that kind of code! Right?
In Visual Studio When you are trying drag and drop controls, or copy and paste controls, it not only duplicates that controls with all properties (including Serializable or non-Serializable) but also it changes the name of control itself from "dataGridView1" to "dataGridView2" as well as in SharpDeveloper!

What should I do?
What kind of method should I create?
Maybe another control has a many non-Serializable properties!
How to duplicate all of them?

Please anyone.....


回答1:


Like @Hans mentioned in the comment, Clone is not that easy. If you want to get some identical controls with only a bit different, you'd better use a function to define general behavior and pass the different properties in as parameters. For example, we define a function with some general properties which apply to 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");
}



回答2:


IDE (e.g. Visual Studio) is using PropertyDescriptors, DesignerSerializationVisibility and ShouldSerializeValue, but DataGrid Rows are something special, because you cannot add them at design time! IDE cannot copy something that is not there, so, the solution must be different (if you want to clone controls beyond what IDE/Designer can do - see other answers and comments for this). Try my code (everything except grid rows got cloned without the extra check - the columns got cloned).

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

Note: The above can be done better (by check for the class at the end), but is as it is to be obvious.

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:


I think I made more better method here.
This Method at first checks interface of property: if it is ICollection then it does the first job.
After this one loop ends in the method "DeepClone()", then it is necessary to do another loop without checking PropertyType Interface... I mean I could not mix these two operation into one loop?!
Also You can detect that there will be some kind of Run-time Exceptions and for this reason I put this code into try-catch block...

    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;
    }

You may say that this Method does not copy some kind of property, But it does copy of main properties and the Cloned control will look like an original control!




回答4:


Trying to clone a control is overkill except if you really need a totally generic control clone method. Most of the time, you only need to clone a specific control and you have an easy access to the code that created it (see the Form designer generated code, and the setup code you wrote yourself). But nevertheless, I once used a trick to duplicate many controls at once in order to fill the new tabs of a TabControl, choosing one out of ten tab designs. I also wanted to use the Form design tool of the C# IDE to edit and modify the 10 template. So, besides my Tab control form, and using the VS IDE, I created 10 "control factory dummy forms" in my project. I put a dummy Panel control in each of it. Each time I had to dynamically create a new Tab, I simply instantiated a new dummy window of the desired style. Then I simply moved the Parent pane to my ControlTab (using the Controls.Add() method of the new tab). This way, you must link the event handlers after the Tab creation (after the controls move). And the event handler's code should be written in you main window class, otherwise you will have "this" reference problems.

Obviously, you will have to store control references somewhere, to be able to access them. The easiest way to do this is to just keep track of each "dummy template Form" you instantiate and to set the "modifier" of your controls to be "public". You can use the Tag property of the destination tab page to store that reference. But, to avoid many casts, it is better to declare an array of each form class, and to store the references there.



来源:https://stackoverflow.com/questions/25227969/how-to-clone-a-windows-forms-controls-even-with-non-serializable-properties

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!