【问题标题】:C# - Creating controls dynamically and accessing themC# - 动态创建控件并访问它们
【发布时间】:2012-04-05 16:29:15
【问题描述】:

我正在创建一个带有标签功能的简单记事本类型的应用程序。我正在运行时创建 TabControl、它的 TabPages 和 RichTextBoxes。我让它们在类范围内实例化。还有一个MenuStrip项叫做New,点击可以添加更多的标签页。

TabControl tbcEditor = new TabControl();
TabPage tbPage = new TabPage();
RichTextBox rtb = new RichTextBox();

private void frmTextEditor_Load(object sender, EventArgs e)
{
     Controls.Add(tbcEditor);
     tbcEditor.Dock = DockStyle.Fill;
     tbcEditor.TabPages.Add(tbPage);
     tbPage.Controls.Add(rtb);
     rtb.Dock = DockStyle.Fill;
}

private void newToolStripMenuItem_Click(object sender, EventArgs e)
{
     //TabPage tbPage = new TabPage();
     //RichTextBox rtb = new RichTextBox();

     tbPage.Controls.Add(rtb);
     rtb.Dock = DockStyle.Fill;
     tbcEditor.TabPages.Add(tbPage);
}

我面临的问题有点难以解释。我会尽力的。当表单加载时,一切都按预期工作。 TabControl 使用添加了 RichTextBox 的 TabPage 创建。但是,如果我单击该新建按钮来添加另一个页面,它就会变得疯狂。创建了一个新的 TabPage,但没有添加 RichTextBox。也不会抛出任何错误。如果我取消注释掉这 2 行(在 MenuItem 单击事件下),它创建了 TabPage 和 RichTextBox 的 2 个实例,一切都按我的意愿工作。

现在我的第一个问题是为什么我必须再次创建只有这两种类型(TabPage、RichTextBox)的新实例,而不是 TabControl?正如你在最后一行看到的,我可以再次使用tbcEditor。但不是tbPagertb

当然,我可以继续在本地范围内再次声明它们,但随后会出现另一个问题。如果我想说,添加复制、粘贴功能,我应该这样做,对吧?

Clipboard.SetDataObject(rtb.SelectedText);

但我无法访问rtb,因为它被声明为本地。

对此我感到非常困惑,因此非常感谢任何有关如何克服这两个问题的建议和想法。

谢谢。

【问题讨论】:

    标签: c# dynamic-controls


    【解决方案1】:

    If I un-comment out those 2 lines(under MenuItem click event), which creates 2 instances of TabPage and RichTextBox, everything works as I want.

    当您取消注释这些行时,您正在将富文本框和标签页的相同实例再次添加到容器面板中,这是没有意义的。而是为每个标签页添加新控件。 (我希望这是要求)

    Now my first question is why do I have to make new instances of only those 2 types(TabPage, RichTextBox) again but not TabControl?

    TabControl 是以 TabPages 作为子控件的父控件。您可以在一个 TabControl 下拥有多个选项卡。因此,除了您已经添加的tbcEditor 之外,您不需要创建 TabControls。我们不会多次添加容器控件(除非有要求)。我们需要更多表格吗?不,只有一种可以正确控制所有子控件的表单。同样,只有一个 TabControl 可以容纳一组 TabPages。仅当您想要每个新选项卡的子选项卡时才需要更多 TabControls,我猜这不是必需的..

    But I can't access rtb since it is declared as local.

    这没什么大不了的。您可以通过两种方式进行:

    1) 通过循环搜索合适的控件。 SelectedTab 属性给出了你想要的。

     private void someEvent(object sender, EventArgs e)
     {           
            foreach (Control c in tbcEditor.SelectedTab.Controls)
            {
                if (c is RichTextBox)
                {
                    Clipboard.SetDataObject(((RichTextBox)c).SelectedText);
                    break; //assuming u have just one main rtb there
                }
            }
     }
    

    2)在创建时将每个rtb标记到tabPage,然后就可以获取选中的tab页的tag元素来获取富文本框。我会采用这种方法。

    编辑:(一般请对您的代码进行以下更改):

     TabControl tbcEditor = new TabControl();
    
     private void frmTextEditor_Load(object sender, EventArgs e)
     {
          Controls.Add(tbcEditor);
          tbcEditor.Dock = DockStyle.Fill;
          AddMyControlsOnNewTab();
     }
    
    private void AddMyControlsOnNewTab()
    {
        TabPage tbPage = new TabPage();
        RichTextBox rtb = new RichTextBox();
        tbPage.Tag = rtb; //just one extra bit of line.
    
        tbcEditor.TabPages.Add(tbPage);
        tbPage.Controls.Add(rtb);
        rtb.Dock = DockStyle.Fill;
    }
    
    private void newToolStripMenuItem_Click(object sender, EventArgs e)
    {
        AddMyControlsOnNewTab();
    }
    

    现在,你可以这样称呼它:

     private void someEvent(object sender, EventArgs e)
     {           
            RichTextBox rtb= (RichTextBox)tbcEditor.SelectedTab.Tag;
            Clipboard.SetDataObject(rtb.SelectedText);
            //or even better in just a line,
            //Clipboard.SetDataObject(((RichTextBox)tbcEditor.SelectedTab.Tag).SelectedText);
     }
    

    您在这里必须考虑的是,您首先获得的控制权,以及您没有获得的控制权。无论如何你都会得到 TabPage,但不会得到 RichTextBox。所以你必须将 RichTextBox 标记为 TabPage。由于Tag 是对象类型,因此您必须对其进行转换,因此您必须指定它是哪种对象。最后,这种方法的优点是您不需要遍历列表,因此它的性能更高。并且您可以在 TabPage 中有更多 RichTextBoxes(前提是您只想从一组 RichTextBoxes 中复制文本,每个 TabPage 一个)..

    【讨论】:

    • 这假设他正在访问选定的选项卡,这是合理的假设,但不是事实。例如,如果他在选项卡之间交换文本(或任何需要访问未选择选项卡的操作,而不是通过事件回调),那么遍历其中的所有选项卡和控件可能会很昂贵(或者不是 - 数字确定) .另外,如果我能提供帮助,不喜欢亲自使用 Tag proerty,除了它需要强制转换之外,恕我直言,它并不能生成清晰的代码。另一种旧方法是使用命名约定,以便标签可以通过名称“TAB_1”“TAB_1A”链接到子控件
    • 标记方法是最好的。如果您知道阅读它,它不会使可读性变得困难,这相当容易。事实上,标签确实可以将对象标记为控件的实例。我仍然不明白通过 .Net 的默认控件列表循环输入比普通控件列表更昂贵的东西。如果他需要从富文本框中选择和复制文本,那么这是一个有效的假设,即他需要来自活动选项卡的文本。即使不是,也只是对上述逻辑的一点调整。我想知道如果仅在 2 个单独的控件列表中维护一个选项卡,获取任何选项卡的富文本框字段不是更多的工作
    • 我已经并且可能会再次使用该标签。我只是不认为这里需要它(而且我认为它有时仍然有用——比如 ArrayLists 和其他一些 1.1 实现)。如果我们只对选定的选项卡感兴趣,那么我们可以通过索引 0 获取第一个子控件 - 或使用 name 属性(因为它说明了它在锡上的内容)。我不是想参加小便比赛,只是真的很感兴趣。
    • 好的,这里需要一个标签: 1. 他不需要循环通过控件。真的很好。任何选项卡,任何地方,他都有连接到该页面的富文本框! 2.如果标签页中有多个富文本框怎么办?该工具如何知道它应该从页面的哪个文本框中获取选定的文本?在这些场景中,关联是一种很好的编码方式
    • @nK0de 只需调试即可查看哪个对象为空。你可以在遇到断点时看到它。我假设它是因为调用它的事件是在初始化richtextbox 或标签页之前执行的。触发了哪个事件来设置剪贴板数据?
    【解决方案2】:

    注释行正在做他们应该做的事情。该代码没有将 Richtextbox 与 Tabpage 关联。

    TabPage tbPage = new TabPage(); // Creates a new tabpage
    
    RichTextBox rtb = new RichTextBox(); // Creates a new RichtextBox control.
    

    TabControl 是一个容器,所以一个实例就可以了。

    另请参阅 - http://sujay-ghosh.blogspot.in/2009/03/addingremoving-dynamically-created.html,与 tabcontrols 无关,而是如何动态创建控件。

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      代码

      tbPage.Controls.Add(rtb); 
      rtb.Dock = DockStyle.Fill; 
      tbcEditor.TabPages.Add(tbPage); 
      

      获取现有文本框,将其添加到现有标签页,然后将现有标签页添加到编辑器。由于这已经完成,所以什么也没有发生。

      当您添加这两行时,您会创建文本框的新实例和新的标签页,这正是您想要的。你的后一个问题来了,因为新声明的变量 rtb 隐藏了类中声明的变量——在不同的方法中,你只能访问类中声明的 onde(除非将控件从选项卡中取出)

      要避免无法访问正确的文本框,您可以将它们保存在列表 (*)(或其他一些集合)中,并参考与当前活动选项卡关联的选项卡。为此,您必须创建一个事件侦听器以查看当前激活了哪个选项卡。

      (*) 而不是只有一个

      【讨论】:

        【解决方案4】:

        好的,您需要创建 RichTextBox 的新实例,而不是尝试将相同的实例添加到每个选项卡。

         TabControl tbcEditor = new TabControl();
         //Get rid off this line --- TabPage tbPage = new TabPage();
         //Get rid off this line --- RichTextBox rtb = new RichTextBox();
        
         List<TabPage> _tabs = new  List<TabPage>();
         List<RichTextBox> _tbx = new  List<RichTextBox>();
        
         private void frmTextEditor_Load(object sender, EventArgs e) 
         {
              Controls.Add(tbcEditor);
              tbcEditor.Dock = DockStyle.Fill;
        
               AddNewTab();
         }
        
         private void newToolStripMenuItem_Click(object sender, EventArgs e) 
         {   
               AddNewTab();
         } 
        
         private void AddNewTab()
         {
            //TabPage 
                var tbPage = new TabPage();
                _tabs.Add(tbPage);
        
                //RichTextBox 
                var rtb = new RichTextBox();
                _tbx.Add(rtb);
        
                tbPage.Controls.Add(rtb);
                rtb.Dock = DockStyle.Fill;
                tbcEditor.TabPages.Add(tbPage); 
         }
        

        这只是将选项卡和 rtb 添加到可以通过索引访问的集合中(也可以使用 Dictionary 进行命名访问等)。当然还有其他方法,包括命名组件并在需要时循环访问它们等。

        【讨论】:

        • 这里绝对不需要两个成员变量和缓存 UI 控件。 :)
        • 我不同意这是一个全球性的声明。正如我所说,您可以循环遍历(正如您稍后的回答所说) - 或者使用 List 控件(我怀疑他是否真的想要/需要 TabPage 从长远来看)他可以直接使用它。这完全取决于他想要多少控件和性能。给猫剥皮的方法有很多种,只有他知道哪种方法最适合他。
        • 循环不是这里的问题。他必须。事实上,他甚至不需要这样做(我的选项 2 中的最佳方法)。但是,当表单本身包含所有控件时,为什么要创建一个单独的 UI 控件列表呢?我的意思是循环浏览标签页控件而不是单独创建冗余列表不是更好吗?告诉我一个单独列表的好用处?现在离开那个,告诉我你将如何通过循环列表来检测选定的标签页?
        • 我并不是说循环是一个糟糕的解决方案(如果有很多控件并且性能是一个问题,它可能是一个问题) - 我什至没有说哪个是最好的方法(我会坚持认为取决于所需的使用,而不是由 Op 突出显示)-我所说的只是使用单独的参考列表的 carblanche 解雇是不正确的。很多时候,维护一个引用列表比遍历整个集合来找到它要好。直接(好的间接索引)访问永远不会比顺序搜索更好吗?
        • 在这个问题中,我们可以像您(我认为)建议的那样轻松使用控件集合。但是,从 Op 中的问题来看,在我看来,他似乎是在询问有关直接从指定事件回调之外解决控件的问题。对我来说,他会问如何在标识控件(或其父级)的回调中访问控件似乎太明显了。这就是我提供上述解决方案的原因(并表示它也可以通过搜索循环来完成)。这一切都取决于手头问题的背景,以及我们如何解释我猜想的问题。
        猜你喜欢
        • 1970-01-01
        • 2011-02-28
        • 2013-01-13
        • 2010-12-02
        • 1970-01-01
        • 1970-01-01
        • 2017-12-01
        • 2017-04-20
        • 1970-01-01
        相关资源
        最近更新 更多