1

I'm creating a simple notepad type of application with the tab functionality. I'm creating the TabControl, its TabPages and RichTextBoxes at run-time. I have them instantiated at class scope. And there is a MenuStrip item called New, by clicking that you can add more tab pages.

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

The problem I'm facing is a bit difficult to explain. I'll try my best. When the form loads, everything works as expected. The TabControl creates with a TabPage with a RichTextBox added. However if I click that New button to add another page, it goes bonkers. A new TabPage gets created but without a RichTextBox added. No errors are thrown either. 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? As you can see in the last line, I can use tbcEditor once again. But not tbPage and rtb.

Sure I can go on declaring them again at local scope but another issue arises then. If I want to say, add copy, paste functionality, I should do something like this,right?

Clipboard.SetDataObject(rtb.SelectedText);

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

I'm very baffled by this so any suggestions, ideas on how to overcome these 2 issues would be greatly appreciated.

Thank you.

4 Answers 4

2

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.

When you uncomment those lines, you are adding the same instance of the rich textbox and tab page to the container panel again which is meaningless. Instead add new controls foreach tabpage. (I hope thats the requirement)

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 is the parent control which has TabPages as child controls. You can have multiple tabs under one TabControl. So you need not create TabControls other than the tbcEditor you have already added. We do not add container controls more than once (unless its the requirement). Do we need more forms? No, just one form which can hold all the child controls right. Similarly just one TabControl which can hold a collection of TabPages. You would need more TabControls only if you want sub-tabs foreach new tab which I guess is not the requirement..

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

This is no big deal. You can do in two ways:

1) Search for your appropriate control by looping. The SelectedTab property gives what you want.

 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) Tag each rtb to the tabPage when you create it, and then you can get the tag element of the selected tab page to get the rich text box. I would go for this approach.

Edit: (In general pls make the following changes too to your code):

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

Now, you can call it like this:

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

What you have to consider here is which is the control that you first get and which is the one you do not get. You would get TabPage anyways but not the RichTextBox. So you have to tag RichTextBox to TabPage. You have to cast it since Tag is of type object, so you have to specify which kind of object it is. Finally, this method has the advantage that you need not loop through a list, so its more performant. And that you can have more RichTextBoxes in the TabPage (provided you want to copy text from only one set of RichTextBoxes, one from each TabPage)..

Sign up to request clarification or add additional context in comments.

15 Comments

This assumes he is accessing the selected tab, which is reasonable assumption, but not defacto. If he is, for example, swapping text between tabs (or whatever action that requires access to unselected tabs, and not via an event callback), then it may be expensive to iterate through all tabs and controls therein (or not - numbers determine). Also, don't like using Tag proerty personally if I can help it, apart from the fact it needs casting, it does not make for clear code IMHO. Another old way is to use a naming convention so tabs can be linked to child controls by name "TAB_1" "TAB_1A"
Tag method is the best. It doesnt make readability tough if u know to read it, which is fairly easy. In fact Tags are indeed there to tag an object to instances of controls. I still dont get what's so expensive about loopin thru default Controls list of .Net than a plain list of controls. If he needs to select and copy text from rich text box, then its a valid assumption that he needs it from active tab. Even if its not, its just little tweakin of the above logic. I am wondering isn't it more work to get the rich text box field of any tab if one is maintainin just 2 separate lists of controls
I have and probably will again, use the Tag. I just don't see it's need here (and I think its a hangover that is still useful sometimes - like ArrayLists and some other 1.1 implimentations). If we are only interested in the selected tab, then we can just grab the first child control by index 0 - or use the name property (as it says what it is on the tin). I'm not trying to get into a peeing match, just interested really.
Ok here are the needs of a tag: 1. He needn't loop thru controls. Really good. Any tab, anywhere, he has the rich textbox connected to that page! 2. what if there are more than one rich textboxes in a tab page? how can the tool know from which text box of a page should it grab the selected text? Association is a good way of coding in these scenarios
@nK0de Just debug to see which object is null. You can see it when hitting a breakpoint. I assume its because the event calling this is doing it before richtextbox or tabpage is initialized. Which event is being triggered to set clipboard data?
|
0

The commented lines are doing just what they are suppposed to do. The code does not associate the Richtextbox with the Tabpage .

TabPage tbPage = new TabPage(); // Creates a new tabpage

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

TabControl is a container , so one instance is just fine.

Also see this - http://sujay-ghosh.blogspot.in/2009/03/addingremoving-dynamically-created.html, nothing with do with tabcontrols, but how to create controls on the fly.

Hope this helps .

Comments

0

The code

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

Takes your exisitng textbox, adds it to the exisitng tab page, then adds that existing tab page to the editor. Since this has already been done, nothing happens.

When you add those two lines, you create new instances of the text box and a new tab page, which is exactly what you want. Your latter problem comes, because the newly declared variable rtb hides the one declared in the class -- in a different method you can only access the onde declared in the class (barring getting the control out of the tab)

To get around not being able to access the proper text box, you can maintain them in a list(*) (or some other collection) and refer to the one associated with the currently active tab. For this, you will have to create an event listener to see which tab is activated currectly.

(*) as opposed to having only one

Comments

0

OK you need to create fresh instances of the RichTextBox rathere than trying to add the same instance to each tab.

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

This simply add both the tab and the rtb to a collection which can be accessed by index (can also use Dictionary for named access etc). There are other ways of course, including just nameing the components and looping through for them when required etc.

11 Comments

There is absolutely no need of two member variables and cache UI controls here what so ever! :)
I disagree with that as a global statement. As I said, you could loop through (as your answer later states too) - or with List controls (and I doubt he actually wants/needs the TabPage one in the long run) he can go straight to it. It all depends on how many controls and what performance he wants. There are many ways to skin a cat and only he knows which will suit him best.
Looping is not the issue wolf here. He has to. In fact he need not do even that (the best approach in my option 2). But why create a separate list of UI controls when the very instance of the form itself holds all its controls? I mean isn't it better to loop through controls of tabpage rather than separately created redundant list? Tell me one good use of having separate list? Now leave that, tell me with your how are you going to detect the selected tabpage by looping through list?
I was not saying that looping is a bad solution (it can be if there are many controls and performance is an issue) - I am not even saying which is the best approach (I will maintain that that depends on the use required and not highlighted by the Op) - All I was saying was that a cartblanche dismissal of using a separate reference list is not correct. There are often times when maintaining a list of references is better than looping through a whole collection to find it. Is direct (Ok indirect indexed) access never better than sequential search?
In this problem, we could just as easily use the controls collection as you (I think) suggest. However, from the question in the Op, it seemed to me he was asking about addressing the controls directly from outside of specifying event callbacks. It seemed too obvious to me that he would ask how to access the control in a callback that identifies it (or its parent). This is why I offered the above solution (and stated that it can also be done with a search loop to). It all depends on the context of nthe problem at hand, and how we interpret the question I guess.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.