Link to home
Start Free TrialLog in
Avatar of San24
San24

asked on

Access Controls, Tab Controls, User Controls, C#, Windows forms

Guys,

Let me explain the windows application,

">" symbol stands for Contains [Eg. Form > Main Tab Control ... Form Contains Main Tab Control]

I have - Form1 > Main Tab Control > Main Tab Page > User Control > Sub Tab Control > Sub Tab Page > User Control > Contols/Text Box.

Form - The main form

Main Tab Control - This contains Main Tab Pages.

Main Tab Pages - Each Tap Page is a separate file. Each Main Tap Page contains a User Control which has a Sub Tab Control.

Sub Tab Control - This contains various Tab Pages [Lets say TabOne, TabTwo, TabThree]. Only the TabOne is different for each Main Tab Page.

Sub Tab Pages - Have various controls - Text Boxes, Buttons etc.


I can create the GUI and it works great. But I want a way to access Controls in the Sub Tab Pages keeping in mind what file is open[Tab Pages in the the Main Tab Control]

Lets say I have a File Save Event from the Main Menu Bar, I need only the contents of the active file to be saved. Simplest form like a webbrowser, when you save the page, only the page in the

active tab is saved. In my case of course I need to keep track of the Main Tab Control active tab and the Sub Tab Control active tab.

How do I keep track of it?

In the most simplest form, to get an idea - Create a Tab Control with a tab page [TP1] . Embed a another User Cntrl which contains a Sub Tab Control in TP1. The Sub Tab Control has tap pages [STP1,

STP2]. STP1 has some text boxes, other controls. Now duplicate TP1 thru an event [New file event] so that there is TP1, TP2, TP3, ....Now lets say we fire an external event [Like File Save, File

Save As evenst from Main Menu], how do I keep track of the values in textboxes/controls wrt to what Main Tab Page is open.

Should I be following a different approach? What is the best programming practice?

Also what is the best way to communicate values and parameters between different User Controls.

I`m using C#, VS 2008

Ideas are welcome!
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

So your main TabControl will host UserControls...are these all instances of the SAME UserControl?...or will there be different types of UserControls hosted in each tab?

Similary, INSIDE the UserControl, will the contents of each "sub tabcontrol" will be the same?

Speaking from a high level, generic OOP standpoint, I would create an Interface that all of your UserControls must Implmenent.  It could have a single method called Save().

Then all you have to do is cast the UserControl on the current tabpage to your Interface Type and tell it to save itself.

Now it is up to the UserControl itself to save the contents of the current "sub tabcontrol".  You could either hard code routines for your different "subforms" or take the same concept above and make each subform Implement another Interface to force themselves to save in whatever manner is applicable.
Avatar of San24
San24

ASKER

Okay let me answer your questions -

Q - So your main TabControl will host UserControls...are these all instances of the SAME UserControl?...or will there be different types of UserControls hosted in each tab?

A - It`s going to be different instances of the same User Control , i.e, Sub Tab Control. Lets say this has 5 Tab Pages. [Page1, Page2, Page3, Page4, Page5]

Q - Similary, INSIDE the UserControl, will the contents of each "sub tabcontrol" will be the same?

A - The contents of each Sub Tab Control are different depending on what File extension is loaded.

For example - If extension *.abc is loaded the contents on Page 1 has a user control ABC, if extension *.xyz are loaded the contents on Page 1 has user control XYZ. Basically Page 1 takes in the vlaues and the remaining pages [Page 2- Page 5] have calculations, graphs and simulations.

I would really like to save the contents from an External event - File Save, File Save As. Also lets say i have function from the Menu bar [lets say Calculate] I only want the values from the active form to be used in the calculation.
"I would really like to save the contents from an External event - File Save, File Save As."

Yes...the event can be generated externally by the forms menu...but the actual code that does the saving and/or calculations would be done from within the UserControl itself.

Lemme whip up a simplified example...
Avatar of San24

ASKER

That`ll be very helpful!
Ok...here's a super simplified example.  Hopefully you can see how it applies to the bigger picture.

My Interface is simply one function:

    public interface MyUserControl
    {
        string ReportIn();
    }

I created TWO UserControls:
(a) UserControl1 with nothing but a TextBox on it.
(b) UserControl2 with nothing but a NumericUpDown on it.

Here is the code showing the ReportIn() method being implemented by each UserControl:

    public partial class UserControl1 : UserControl, MyUserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        public string ReportIn()
        {
            return this.textBox1.Text;
        }

    }

    public partial class UserControl2 : UserControl, MyUserControl
    {
        public UserControl2()
        {
            InitializeComponent();
        }

        public string ReportIn()
        {
            return this.numericUpDown1.Value.ToString();
        }

    }

As you can see, each UserControl "reports" back something that is specific to its content.  The outside world doesn't know where the string came from!

In my main form I added a TabControl and 3 buttons.  Button1 and Button2 simply open up a new TabPage with UserControl1 or UserControl2 on it.  Here is that code:

        void btn_Click(object sender, EventArgs e)
        {
            Control ctl;
            if (sender == button1)
            {
                ctl = new UserControl1();
            }
            else
            {
                ctl = new UserControl2();
            }
            ctl.Dock = DockStyle.Fill;

            TabPage tp = new TabPage((tabControl1.TabCount + 1).ToString());
            tp.Controls.Add(ctl);

            tabControl1.TabPages.Add(tp);
        }

Button3 is where the "magic" happens though.  It grabs the current TabPage and finds the first control it can that implements the MyUserControl interface.  Then it casts that control to the MyUserControl interface and tells it to report in.  Note that it doesn't know if that control is UserControl1 or UserControl2...and it doesn't matter.  We could add many more types of controls and it will still work as long as they implement MyUserControl.  This gives you the ability to be flexible...

        void button3_Click(object sender, EventArgs e)
        {
            TabPage tp = tabControl1.SelectedTab;
            if (tp != null)
            {
                foreach (Control ctl in tp.Controls)
                {
                    if (ctl is MyUserControl)
                    {
                        MyUserControl muc = (MyUserControl)ctl;
                        MessageBox.Show(muc.ReportIn());
                    }
                }
            }
        }

In this example, we used the Interface to generically communicate from the UserControl to the main Form.  You don't necessarily have to pass anything out though.  We could just as easily add a METHOD to the MyUserControl such as Save() which would cause the UserControl to do something with its internal data without ever communicating that data back out to the main form.
The crux of it boils down to ENCAPSULATION.

The FORM should NOT be doing the dirty work of saving the data...it should simply ASK the UserControl to save itself.

After all, it's the UserControl that has all the ~custom~ data right?...so it should know how best to deal with it!

There is always a grey area, though, as the user interacts with both the Form and the UserControl.  So sometimes you just have to find the right blend of techniques for your situation.

You'll get many different opinions on the "right" way to do this kind of thing...  ;)
Avatar of San24

ASKER

What if the TWO UserControls:

(a) UserControl1 with nothing but a TextBox on it.
(b) UserControl2 with nothing but a NumericUpDown on it.

are themselves in a user control - Lets say a Tabpage in a Tab Control. Will it be the same appraoch?

The flow is this - Main Tab Control > Main Tab Page > Sub Tab Control > Sub Tab Pages [Pages 1..Pages 5] > Page 1 [UserControl1 or UserControl2].

I`m in the process of testing your solution. I`ll get back to you asap, thanks for your patience!
Yes...you can extrapolate the concept to a situation where the UserControls themselves are inside something else.  If each UserControl was different enough then you could create a second Interface that the sub UserControls implement.

Then you end up "daisy chaining", or passing on the request:
(1) Convert the main UserControl to the main Interface type and make the request.
(2) Main UserControl receives the request and then casts its sub Control to the proper sub Interface and tells it to do its thing.

It's possible to do it all from one Interface...just depends on how complex your controls are and how much work you want to do at which level...
Avatar of San24

ASKER

This might seem like a dumb question...Where should this be included, new classfile?

    public interface MyUserControl
    {
        string ReportIn();
    }
Avatar of San24

ASKER

Never Mind - I did`t use Form1.MyUserControl while declaring inheritence.

Let me use use a Sub Tab Control and test it and let you know. Thanks a lot Idle Mind, you`ve been veryhelpful so far. I`ll do a test app and let you know tomorrow.
Avatar of San24

ASKER

This is waht I created, I still have some doubts.

I have a Main form with a main Tab Control - MainTC

I created a User Control[TabUserCntrl] with a Sub Tab Control with two text boxes[TB] - ParamATB and ParamBTB and one single Panel - panel1. I can access the the values of ParamATB and ParamBTB just the way I wanted, I followed the example you wrote up.

panel1 will host one of the User Controls depending on a condition. [XYZUserCntrl or ABCUserCntrl]

public void SetUP(String Param)
        {
            switch (Param)
            {
                case "XYZ":
                    XYZUserCntrl XYZ = new XYZUserCntrl();
                    panel1.Controls.Add(XYZ);
                    break;

                case "ABC":
                    ABCUserCntrl ABC = new ABCUserCntrl();
                    panel1.Controls.Add(ABC);
                    break;

                default:
                    break;
            }
        }

ABCUserCntrl and XYZUserCntrl have two different text boxes each.

So I have ABCUserCntrl as

public partial class ABCUserCntrl : UserControl, Form1.ABCInterface
    {
        public ABCUserCntrl()
        {
            InitializeComponent();
        }

        public String GetValueABCParamA()
        {
            return ABCParam1.Text;
        }

        public String GetValueABCParamB()
        {
            return ABCParam2.Text;
        }
    }


and XYZUserCntrl as

  public partial class XYZUserCntrl : UserControl, Form1.XYZInterface
    {
        public XYZUserCntrl()
        {
            InitializeComponent();
        }

        public String GetValueXYZParamA()
        {
            return XYZParam1.Text;
        }

        public String GetValueXYZParamB()
        {
            return XYZParam2.Text;
        }
    }

I`m having trouble accessing the values of the testboxes within the XYZUserCntrl and ABCUserCntrl through an external event

My Form1.cs is

 private void SaveAsTSMI_Click(object sender, EventArgs e)
        {

            //How do I access values of GetValueABCParamA(), GetValueABCParamB(), GetValueXYZParamA(), GetValueXYZParamB() here or anywhere in the code???


            TabPage TP = MainTC.SelectedTab;
            if (TP != null)
            {
                foreach (Control Ctl in TP.Controls)
                {
                    MessageBox.Show(Ctl.ToString());

                    if (Ctl is MyUserControl)
                    {
                        MyUserControl MUC = (MyUserControl)Ctl;
                        MessageBox.Show(MUC.GetValueParamA() + MUC.GetValueParamB());
                    }

                }
            }
        }



        public interface MyUserControl
        {
            string GetValueParamA();
            string GetValueParamB();
            int AddAB();
        }


        public interface ABCInterface
        {
            string GetValueABCParamA();
            string GetValueABCParamB();
        }


        public interface XYZInterface
        {
            string GetValueXYZParamA();
            string GetValueXYZParamB();
        }

       

        private void newToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TabUserCntrl TCtl = new TabUserCntrl();
            //Depending on Condition - Load ABC User Control or XYZ User Control
            TCtl.SetUP("ABC");
            TabPage TP = new TabPage("New File " + (MainTC.TabCount + 1).ToString());
            TP.Controls.Add(TCtl);
            MainTC.Controls.Add(TP);
        }
Well, here:

                foreach (Control Ctl in TP.Controls)
                {
                    MessageBox.Show(Ctl.ToString());

                    if (Ctl is MyUserControl)
                    {
                        MyUserControl MUC = (MyUserControl)Ctl;
                        MessageBox.Show(MUC.GetValueABCParamA() + MUC.GetValueABCParamB());
                    }

                }

In the loop you're looking for a control of type MyUserControl but the controls you've shown are derived from UserControl and Implement either ABCInterface or XYZInterface so you'll never get a match.

Look for one of the Interfaces you've actually created:

                    if (Ctl is Form1.ABCInterface)
                    {
                        Form1.ABCInterface abc = (Form1.ABCInterface)Ctl;
                        MessageBox.Show(abc.GetValueABCParamA() + abc.GetValueABCParamB());
                    }

As it stands, this is overkill since you only have ONE UserControl that implements each Interface.  This becomes useful if you end up developing more than one UserControl that implements a particular Interface.  Then you can cast it the different types of UserControls to the base Interface Type and work on them without knowing whats "under the hood" of each.

If there really isn't any common way to describe how to get data from ALL of the UserControls with one Interface then you might as well just code up a huge switch statement that checks the type of the UserControl on the current tab and then casts it to the proper type and extracts the data with hard coded names of the internal controls.  You could just set the Modifiers propert of the controls to public to make them accessible outside the UserControl (or wrap them in public property).
Avatar of San24

ASKER

Idle Mind,

I tried this before, but it doesn`t work.  Anything to do with the XYZInterface being placed in the TabPage at runtime?

private void SaveTSMI_Click(object sender, EventArgs e)
        {
            TabPage TP = MainTC.SelectedTab;

            if (TP != null)
            {
                foreach (Control Ctl in TP.Controls)
                {

                    if (Ctl is Form1.XYZInterface)
                    {
                        Form1.XYZInterface xyz = (Form1.XYZInterface)Ctl;
                        MessageBox.Show(xyz.GetValueXYZParamA() + xyz.GetValueXYZParamB());
                    }
                }
            }
        }


Regarding your question - I do have more than one interface type, so that requires a different User Control. I don`t know if this is the best approach, but this is what I`ve come up with.
Not sure where it's going wrong...  =\

Can you upload the project to EE-Stuff?  (I've used it before myself)
http://www.ee-stuff.com/
Avatar of San24

ASKER

Just Uploaded it . Here`s the link

View all files for Question ID: 25045373
https://filedb.experts-exchange.com/incoming/ee-stuff/7831-TestExpExc.zip 

Direct link to your file
https://filedb.experts-exchange.com/incoming/ee-stuff/7831-TestExpExc.zip 
Alrighty.  Gotta pick up the kids from school though!  Be back in about an hour...
Avatar of San24

ASKER

Drive safe!
Ok...got a better picture now.

You had it correct originally at the form level searching for MyUserControl.  Sorry about that!

So in the form you should have:

        private void SaveTSMI_Click(object sender, EventArgs e)
        {
            TabPage TP = MainTC.SelectedTab;

            if (TP != null)
            {
                foreach (Control Ctl in TP.Controls)
                {
                    if (Ctl is Form1.MyUserControl )
                    {
                        Console.WriteLine("MyUserControl detected");
                        Form1.MyUserControl muc = (Form1.MyUserControl)Ctl;
                        MessageBox.Show(muc.GetValueParamA() + muc.GetValueParamB());
                    }
                }
            }
        }

Then, in TabUserCntrl, you need to turn around and basically do the same thing:
(a) Grab the current tab
(b) iterate the controls looking for ABC or XYZ
(c) Cast it and call the Interface method.

Looks like this possibly:
    public partial class TabUserCntrl : UserControl, Form1.MyUserControl
    {
        public TabUserCntrl()
        {
            InitializeComponent();
        }

        public String GetValueParamA()
        {
            TabPage tp = this.SubTabCntrl.SelectedTab;
            if (tp != null)
            {
                foreach (Control ctl in tp.Controls)
                {
                    if (ctl is Form1.ABCInterface)
                    {
                        Form1.ABCInterface abc = (Form1.ABCInterface)ctl;
                        return abc.GetValueABCParamA();
                    }
                    else if (ctl is Form1.XYZInterface)
                    {
                        Form1.XYZInterface xyz = (Form1.XYZInterface)ctl;
                        return xyz.GetValueXYZParamA();
                    }
                }
            }
            return string.Empty;
        }

        public String GetValueParamB()
        {
            TabPage tp = this.SubTabCntrl.SelectedTab;
            if (tp != null)
            {
                foreach (Control ctl in tp.Controls)
                {
                    if (ctl is Form1.ABCInterface)
                    {
                        Form1.ABCInterface abc = (Form1.ABCInterface)ctl;
                        return abc.GetValueABCParamB();
                    }
                    else if (ctl is Form1.XYZInterface)
                    {
                        Form1.XYZInterface xyz = (Form1.XYZInterface)ctl;
                        return xyz.GetValueXYZParamB();
                    }
                }
            }
            return string.Empty;
        }

    }

Open in new window

Avatar of San24

ASKER

Idle Mind,

With minor adjustments, it works great! Thanks a lot.

I was just wondering instead of looping thru the controls, is there a way to explicity mention it.

For example - Instead of this

foreach (Control ctl in MainTab.Controls)
            {
                if (ctl is Form1.ABCInterface)
                {
                    Console.WriteLine(ctl.ToString());
                    Form1.ABCInterface abc = (Form1.ABCInterface)ctl;
                    return abc.GetValueABCParamA();
                }
                else if (ctl is Form1.XYZInterface)
                {
                    Console.WriteLine(ctl.ToString());
                    Form1.XYZInterface xyz = (Form1.XYZInterface)ctl;
                    return xyz.GetValueXYZParamA();
                }
            }

can I just have

Form1.XYZInterface cba = (Form1.XYZInterface)Ctl;
return cba.GetValueXYZParamA();

Whats the value of Ctl? Is it advisable to do it this way? I tried to plug in different values, a trial and error approach but looks like I`m getting a casting error.


You can...but you need to give your dynamic UserControls a NAME:

    XYZUserCntrl H = new XYZUserCntrl();
    H.Name = "xyz";

Then you could do something like:

    Form1.XYZInterface xyz = (Form1.XYZInterface)tp.Controls["xyz"];
Avatar of San24

ASKER

Idle Mind,

I get a "object reference not set to instance of object error"

 public String GetValueOOParamA()
        {
            XYZUserCntrl H = new XYZUserCntrl();
            H.Name = "xyz";

            Form1.XYZInterface xyz = (Form1.XYZInterface)SubTab.Controls[H.Name];
            return xyz.GetValueXYZParamB();  //Exception here
        }
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of San24

ASKER

Very knowledgeabale, very patient and very helpful! Thanks Idle Mind.
Avatar of San24

ASKER

I had another question off topic..How are "Module Tabs" created in Windows forms?

[http://www.smashingmagazine.com/2009/06/24/module-tabs-in-web-design-best-practices-and-solutions/]

Are they just a group of panels, where you Hide and Show panels depending on events? What is the best way to approach this?

Something like the tabs on the top this page [Browse All, Microsoft, Apple..etc]
Sure...that would be one way to do it.

Another is to use actually FORMS or UserControls instead of Panels...and use only ONE Panel to house the Forms/UserControls.
(you switch which form/usercontrol is currently displayed)

The secret for using Forms is to set the TopLevel property of the Form to false first.  Then you embed the form into the panel just like any other control at run-time.  This makes it easier to work with since you don't have to keep shuffling Panels around in the IDE to see your content.  =\

Just set the FormBorderStyle to None, ShowInTaskBar property to False, and Dock to Fill and it looks seamless.

        private void button1_Click(object sender, EventArgs e)
        {
            Form f2 = new Form2();
            f2.FormBorderStyle = FormBorderStyle.None;
            f2.ShowInTaskbar = false;
            f2.TopLevel = false; // won't work without this
            f2.Dock = DockStyle.Fill;
            panel1.Controls.Add(f2);
            f2.Show();
        }

If you need the Form to make things happen in the main form then you could use custom Events or Interfaces as in your example above.
Avatar of San24

ASKER

Interesting...Let me try both the approaches, I`m guessing going with the Form approach is going to make the UI heavier...? Whats the better programming practice?
Not sure how much "heavier" it is...both a Form and a Panel have a Handle so it's not like a lightweight control vs a heavyweight control.

Don't know which is a better programming practice...more of a personal preference on how it should look.

A good combination I've seen is to use a ListView in LargeIcon mode across the top of the Form so the user can click on Icons to cause the content in the bottom of the app to change.  The bottom of the app could then be done like the code above.