WinForm - 复制具有自己行为的 TabControl

WinForm - Copy TabControl with OWN behaviour

Copy TabControl Tab 是的,我已经检查过了。

我正在尝试复制一个 tabcontol,但此选项卡应该有它自己的 'behaviour'。

        TabControl tc = TC_Fields;
        TabPage tpOld = tc.TabPages[0];

        TabPage tpNew = new TabPage();

        fields += 1;
        tpNew.Name = "Field_" + fields;
        tpNew.Text = "Field-" + fields;

        foreach (Control c in tpOld.Controls)
        {
            Control cNew = (Control) Activator.CreateInstance(c.GetType());
            PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(c);
            foreach (PropertyDescriptor entry in pdc)
            {
                object val = entry.GetValue(c);
                if (entry.Name == "Name")
                {
                    val = (String) val + fields;    
                }
                entry.SetValue(cNew, val);
            }
            tpNew.Controls.Add(cNew);
        }

        tc.TabPages.Add(tpNew);

我试过上面的代码,所以它给新控件一个新的 "id",但我仍然无法单击新选项卡中的控件,它们只模仿第一个选项卡中的内容.

有没有一种方法可以复制所有控件而不模仿它们被复制的控件?

  1. 您的代码至少有两个与实际问题无关的问题。让我们先修复其中两个:

    • 您设置了所有属性,甚至是您不应该设置的属性;最值得注意的是你不应该触摸 "WindowTarget" 属性!它仅供内部使用,弄乱它至少会停止某些控件的工作。例如,我发现我无法检查 CheckBox

    • 如果原始 TabPage 也是 current 控件,您的代码只会创建 visible 控件.所有其他页面上的所有控件都是不可见的,如果您尝试克隆任何其他 TabPage 所有控件将被添加得很好但保持 invisible.

让我们添加一个过滤器:

if(entry.Name != "WindowTarget")     entry.SetValue(cNew, val);

并使新控件可见:

cNew.Visible = true;
tpNew.Controls.Add(cNew);

请注意,这是一个简单的解决方案,因为它还会使那些原本不可见的控件可见。您可以改为显示要克隆的页面或制作不可见控件的列表;用他们的名字你可以找到克隆的对应物..

最后一个问题也很重要,但超出了问题范围:代码仅直接在页面上克隆控件,而不是任何嵌套控件(如 GroupBox 中的 RadioButtons !)。为此,您必须编写 递归 版本!

  1. 现在是真正的问题:如何为克隆的控件提供它们自己的行为?

克隆所有属性后,它们就像新添加的控件一样,即它们根本没有事件处理程序

所以你需要

  • 为新控件的事件编写新的事件处理程序
  • 联系他们

虽然这并不难,但问题很多..

我们知道命名方案,因此我们可以知道要为哪些控件提供事件。事件名称可以自由选择,但要将它们连接起来,我们需要知道我们正在处理的是哪个新控件..

这是将 Button cb_hello 第一个 克隆与 Click 事件挂钩的示例:

  if (cNew.Name == "cb_hello1") cNew.Click += buttonNew_Click;

活动中的代码演示了更多问题:

private void buttonNew_Click(object sender, EventArgs e)
{
    Button btn = sender as Button;
    Console.WriteLine(btn.Name + " says hiho");
    btn.Parent.Controls["panel41"].BackColor = Color.ForestGreen;
    ((RadioButton)btn.Parent.Controls["radioButton11"]).Checked = true;  
}

虽然将 sender 转换为 Button 很正常,但是当我们尝试访问我们刚刚克隆的任何控件时,我们 运行 遇到了麻烦:

  • 由于它们是动态创建的,我们无法使用变量访问它们中的任何一个。相反,我们需要使用 TabPageControls collection 来找到它们。

  • 我们可以设置 BackColor 或继承自 Control 的任何其他 属性 但要设置 RadioButton.Checked 我们需要转换为 RadioButton;我们需要从 Controls collection 访问控件,例如通过它的 Name.

  • 升级到 recursive cloning 后,您将需要使用 Controls.Find(name, true) 来包含嵌套控件..

如您所见,它可以完成,但比编写原始控件需要更多的努力,而且代码感觉有些脆弱,因为我们引入了隐藏的依赖项:所有这些对 cloned[ 的引用=102=] 名称依赖 原始 名称!

最后说明:虽然常规属性会被克隆,但 datastructure 容器不会,即所有 ItemsListViewItemsRows , Columns collections etc, etc not cloned!

        TabControl tc = TC_Fields;
        TabPage tpOld = tc.SelectedTab;

        TabPage tpNew = new TabPage();

        fields += 1;
        tpNew.Name = "Field_" + fields;
        tpNew.Text = "Field-" + fields;

        foreach (Control c in tpOld.Controls)
        {
            Control cNew = (Control) Activator.CreateInstance(c.GetType());
            PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(c);
            foreach (PropertyDescriptor entry in pdc)
            {
                object val = entry.GetValue(c);
                if (entry.Name == "Name")
                {
                    val = (String) val + fields;
                }
                else if (entry.Name == "Location" || entry.Name == "Text" || entry.Name == "Bounds" || entry.Name == "Enabled" 
                    || entry.Name == "Visible" || entry.Name == "Checked" || entry.Name == "CheckState")
                {
                    //Nothing to do, but do continue!
                }
                else if (entry.Name == "Controls")
                {
                    Control.ControlCollection controllsInside = (Control.ControlCollection) val;
                    foreach (Control controllInside in controllsInside)
                    {
                        Control cNewInside = (Control) Activator.CreateInstance(controllInside.GetType());
                        PropertyDescriptorCollection pdcInside = TypeDescriptor.GetProperties(controllInside);
                        foreach (PropertyDescriptor entryInside in pdcInside)
                        {
                            object valInside = entryInside.GetValue(controllInside);
                            if (entryInside.Name == "Name")
                            {
                                valInside = (String) valInside + fields;
                            }
                            else if (entryInside.Name == "Location" || entryInside.Name == "Text" || entryInside.Name == "Bounds" || entryInside.Name == "Enabled" 
                                || entryInside.Name == "Visible" || entryInside.Name == "Checked" || entryInside.Name == "CheckState")
                            {
                                //Nothing to do, but do continue!
                            }
                            else
                            {
                                continue;
                            }
                            entryInside.SetValue(cNewInside, valInside);
                        }
                        cNew.Controls.Add(cNewInside);
                    }
                }
                else
                {
                    continue;
                }
                entry.SetValue(cNew, val);
            }
            tpNew.Controls.Add(cNew);
        }

        tc.TabPages.Add(tpNew);
        TC_Fields.SelectedIndex = fields - 1;

这也能解决问题:) @TaW 已经把解决方案放在那里了,但这里有一些复制粘贴代码:)