Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

TabControl Adding Items 1

Status
Not open for further replies.

ronnyjljr

IS-IT--Management
Nov 23, 2003
249
US
Hello,


Ok, I have a tab control in my application. I have added the tabs. How in the world do I add other controls on to these different tabs?

For example, how do I had a CListBox control that is on tab index 0? I can't find anything online.

Any help is much appreciated.

-Ron


After a while of studying Engineering at PSU you just want to sit down and eat a bowl of cereal. Thats all you really want.
 
You actually don't use the tab control in most cases for a tabbed dialog. It's too much effort - you have to worry about showing and hiding all of these controls whenever a different tab is clicked, and it's a nightmare to design with the resource editor. What you want is a property sheet. Look up the CPropertySheet and CPropertyPage MFC classes. Basically, you create a dialog resource for each page you want, then make classes for each derived from CPropertyPage instead of CDialog. Then when you want to create the dialog, you create a CPropertySheet object (you don't make your own class derived from it most of the time) and call AddPage for each page you want to add. Call the sheet's DoModal() to create the dialog, which will be tabbed appropriately.
 
Thanks a million!

It took me a moment to understand how to construct everything but this is exactly what I wanted.


-Thanks,
Ron

/**************
Me: When will the project be finished?
Them: What project?
Me: The one you have been working on for the last 3 years!
Them: Ohh, we can't finish it?
Me: Why?
Them: We don't know how to program...
 
CPropertySheet is a nice thing but if you want to change its appearance e.g. you dialog should have not only a TabCtrl, but a Static and CEdit over the TabCtrl - you cannot do it.
CTabCtrl is pretty ugly, but with this code snippet life can be beautiful!
So first create the main dialog (e.g. [tt]COurDlg[/tt]) with TabCtrl in it. The size of TabCtrl is important - the pages will get it's size.
Then create template dialogs - our future pages - with following styles :
[ul]
[li]Style = child[/li]
[li]Border = none[/li]
[li]Titlebar = no[/li]
[/ul]
The size of the dialogs doesn't matter - they'll be adapted to the size of the Tab.
Then add a member variable to our class [tt]COurDlg[/tt]
Code:
HWND m_TabPages[_d_TabPagesNum];
In the header of [tt]COurDlg[/tt] class define the number of the pages you want to have.
In the constructor of [tt]COurDlg[/tt] class add
Code:
ZeroMemory(m_TabPages, sizeof(m_TabPages));
In [tt]COurDlg::OnInitDialog()[/tt]
Code:
//.........................
//.........................

//Loops iterator
int i;

//Create the pages if they're not created yet
//(!!!) it is assumed that dialog ressources ID's are IDD_DIALOG1, IDD_DIALOG1 + 1 and so on.
      if (!m_TabPages[0])
      {
            for (i = 0; i < _d_TabPagesNum; i++)
            {
                  m_TabPages[i] = CreateDialog(AfxGetInstanceHandle(),
                                               MAKEINTRESOURCE(IDD_DIALOG1 + i), m_TabCtrl, NULL);
            }
      }

      //Give to pages correct size and position on TabCtrl
      CRect l_Rect;
      m_TabCtrl.GetClientRect(l_Rect);
      l_Rect.top            += 25;
      l_Rect.left           += 2;
      l_Rect.right          -= 2;
      l_Rect.bottom         -= 2;

      for (i = 0; i < _d_TabPagesNum; i++)
      {
            ::MoveWindow(m_TabPages[i], l_Rect.left, l_Rect.top, l_Rect.Width(), l_Rect.Height(), true);
      }
      
      //Make tabs on the control
      for (i = 0; i < _d_TabPagesNum; i++)
      {
            CString c_Caption;
            c_Caption.Format("%d", i);
            m_TabCtrl.InsertItem(i, c_Caption /*Here you can type you names for tabs*/);
      }

      //Activate first page
      m_TabCtrl.SetCurSel(0);
      ActivateTabPage(0);
Next create a member function of the [tt]COurDlg[/tt] class (this function will show the clicked page and hide the rest)
Code:
void COurDlg::ActivateTabPage(int p_NumPageToActivate)
{
      for (int i = 0; i < _d_TabPagesNum; i++)
      {
            ::ShowWindow(m_TabPages[i], ((i == p_NumPageToActivate) ? SW_SHOW : SW_HIDE));
      }
}
Add a message handler for our TabCtrl for the message [tt]TCN_SELCHANGE[/tt]
Code:
void COurDlg::OnSelchange***(NMHDR* pNMHDR, LRESULT* pResult) 
{
      ActivateTabPage(m_TabCtrl.GetCurSel());
      
      *pResult = 0;
}
And at least if you want to control the change of the page i.e. change the page depending on some conditions:
Add a message handler for our TabCtrl for the message [tt]TCN_SELCHANGING[/tt]
Code:
void COurDlg::OnSelchanging***(NMHDR* pNMHDR, LRESULT* pResult)
{
      int l_OldPageNum = m_TabCtrl.GetCurSel();

      //change the page
      *pResult = 0;
      //permit to change the page
      *pResult = 1;
}
<- here life begins to be beautiful
:)))))))))))))
 
Hey firelex!

Thank you for posting these directions! I've been trying to figure out an easy way to implement the tab ctrl without success for some time now. I've been using the CPropertySheet with its many limitations, such as the ones you mentioned (i.e. adding other controls over the tab ctrl), but haven't been very happy about it. Your post answered a lot of questions for me!

You're right! "Here life begins to be beautiful!"

Thank you! [2thumbsup]
 
Hi firelex,

Thanks for the solution! Question for you, I'm not getting any of the messages from the controls I've added to any of the dialogs. I add event handlers, but the message isn't being sent to my class. Before reading your solution, I had placed all the controls directly on the tab control, and the event handlers worked fine. After reading your message, I've created the templates for the child dialogs and have moved the controls to the child dialogs. Now the tab control itself works great - I can move between the tabs just fine and all the controls display appropriately. But, for example, if I put an event handler on a button, then click the button, the event handler never gets hit. Any ideas? Thanks in advance.
 
dochart wrote:
Question for you, I'm not getting any of the messages from the controls I've added to any of the dialogs. I add event handlers, but the message isn't being sent to my class.
What class do you mean? The one of the dialog where [tt]TabCtrl[/tt] is? This class cannot get any of the messages sent from the controls placed on the pages of the [tt]TabCtrl[/tt]. Each control sends messages to the parent dialog.

Look:
You have a class that is parent to the [tt]TabCtrl[/tt]
[tt]TabCtrl[/tt] is parent to the pages it displays
and pages are parents to the controls placed on them.

That's why the only place to catch a message from a control is a class for that page where control is put on.
 
Okay, let me see if I'm following:

> CMyDlg1 contains (is parent of) CMyTabCtrl
> CMyTabCtrl contains (is parent of) CMyDlg2
> CMyDlg2 contains (is parent of) CMyButton

...the only place to catch a message from a control is a class for that page where control is put on.

So, to catch the click of the CMyButton control, I need to have the handler as CMyDlg2::OnMyButtonClick()...?
 
Well, it seems I have discovered a way to make this work. I don't know if this is the way you had in mind, firelex, and I'd appreciate any feedback.

The information I found is here:

Since we're working with modeless dialog boxes, it is up to the parent dialog of the CTabCtrl to capture and handle the messages from controls on any of the tab pages.

To start with, instead of passing NULL we need to specify a callback procedure when we create the dialogs for the tab control:
Code:
m_TabPages[i] = CreateDialog(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDD_DIALOG1 + i), m_TabCtrl, (DLGPROC) GoToProc);
Then we have to add the procedure:
Code:
BOOL CALLBACK GoToProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) 
{ 
	
    switch (message) 
    { 
        case WM_INITDIALOG: 
            return TRUE; 
 
        case WM_COMMAND: 
			switch (LOWORD(wParam)) 
            { 
                case IDC_BUTTON1: 
					AfxMessageBox("BUTTON1");
					return TRUE;
                case IDC_BUTTON2: 
					AfxMessageBox("BUTTON2");
					return TRUE;
   
            } 

            return TRUE; 

    } 
    return FALSE; 
}
This displays a message box whenever button 1 or button 2 are pressed.

This doesn't differentiate between tabs so you want to make sure your controls have different ID's.

It works, but as with all things there is probably an easier and better way of doing it. Again, any feeback is appreciated!
 
Just as a follow-up, I've found that if you want to know which tab the control is on you can use CreateDialogParam() and pass the dialog's resource ID (IDD_DIALOG1+i) as the lParam. You can then add a switch(LOWORD(lParam)) block to test for which tab the message control is coming from.

I also had a question. Since CMyDlg1 has to handle all of the messages in the callback, including WM_INITDIALOG, is there any way for the CMyDlg2::OnInitDialog() to get called?
 
As a beginner I can't follow this. I understand some things but not others and I can not get this to work.

My suggestions, write a tutorial (if you have time).
I am going to stick with PropertySheets right now because I can understand them.


Thanks though,
Ron

/**************
Me: When will the project be finished?
Them: What project?
Me: The one you have been working on for the last 3 years!
Them: Ohh, we can't finish it?
Me: Why?
Them: We don't know how to program...
 
2 bluecrush
If you don't want to handle all the messages in your [tt]CMyDlg1[/tt] class you can go another way. It is not so comfortable but it also works.

let's follow my class notation...

Create a class for each dialog-page of [tt]TabCtrl[/tt].
Add to each of those classes a procedure like:
Code:
BOOL CMyPage1::CreatePage(CWnd* pParent)
{
	return CreateDlg(MAKEINTRESOURCE(IDD), pParent);
}

Then, instead of
Code:
HWND m_TabPages[_d_TabPagesNum];
take
Code:
CDialog* m_TabPages[_d_TabPagesNum];

Add to your [tt]COurDlg[/tt] header a variable of each of your page classes.
Code:
//COurDlg.h

CMyPage1 dlg_MyPage1;
...

Modify [tt]COurDlg::OnInitDialog[/tt] like follows:
Code:
//Create pages
int i=0;	
if (!m_TabPages[0])
{
	dlg_MyPage1.CreatePage(&m_TabCtrl);
	m_TabPages[i++] = &dlg_MyPage1;
}

//Give to pages correct size and position on TabCtrl
CRect l_Rect;
m_TabCtrl.GetClientRect(l_Rect);
l_Rect.top            += 25;
l_Rect.left           += 2;
l_Rect.right          -= 2;
l_Rect.bottom         -= 2;

for (i = 0; i < _d_TabPagesNum; i++)
{
	m_TabPages[i]->MoveWindow(l_Rect, TRUE);
}
//Make Tabs on the control
CString c_Caption;
for (i = 0; i < _d_TabPagesNum; i++)
{
	c_Caption.Format("%d", i);
	m_ctrlGeoTab.InsertItem(i, c_Caption);
}

//Activate first page
m_TabCtrl.SetCurSel(0);
ActivateTabPage(0);

And at last change a bit the [tt]ActivateTabPage[/tt] procedure:
Code:
void COurClass::ActivateTabPage(int p_NumPageToActivate)
{
	for (int i = 0; i < _d_TabPagesNum; i++) {
		m_TabPages[i]->ShowWindow((i == p_NumPageToActivate) ? SW_SHOW : SW_HIDE);

	}
}

Now you have every of you page as an instance of the dialog class and can call it's procedures etc.
 
You'll have to excuse my last post, it was late and the gremlins were talking to me again =)

I got it working, and boy is it sweet!

Question though, these tabbed pages are just like having another dialog with a seperate class open right?

I can add functions to the different classes and it would work the same as if I called DoModal() and just delt with it individually?

Thanks!
Ron

/**************
Me: When will the project be finished?
Them: What project?
Me: The one you have been working on for the last 3 years!
Them: Ohh, we can't finish it?
Me: Why?
Them: We don't know how to program...
 
Btw,

I can't seem to get the second one to work, the tabs show up but none of the pages do. Any suggestions?

/**************
Me: When will the project be finished?
Them: What project?
Me: The one you have been working on for the last 3 years!
Them: Ohh, we can't finish it?
Me: Why?
Them: We don't know how to program...
 
Wonderful, firelex! While the construction of the each of the dlgs is a bit of a pain, I appreciate the ability of each of the dlgs to handle their own messages. Thank you for the great post! Everything works flawlessly! [bigsmile]

2ronnyjljr
In answer to your first question, with the second method you should be able to treat each of the dlg objects as stand-alone dlgs...meaning each handles its own messages, etc. Yeah, kinda like if you called domodal().

As far as getting it to work, without seeing your code I'd suggest you start by looking here:
Code:
	int i=0;

	//Create the pages if they're not created yet
	if (!m_TabPages[0])
	{
		dlg_Tab1.CreatePage(&m_TabCtrl);
		m_TabPages[i++] = &dlg_Tab1;
		dlg_Tab2.CreatePage(&m_TabCtrl);
		m_TabPages[i++] = &dlg_Tab2;
		dlg_Tab3.CreatePage(&m_TabCtrl);
		m_TabPages[i++] = &dlg_Tab3;
		dlg_Tab4.CreatePage(&m_TabCtrl);
		m_TabPages[i++] = &dlg_Tab4;
		dlg_Tab5.CreatePage(&m_TabCtrl);
		m_TabPages[i++] = &dlg_Tab5;
		dlg_Tab6.CreatePage(&m_TabCtrl);
		m_TabPages[i++] = &dlg_Tab6;
		dlg_Tab7.CreatePage(&m_TabCtrl);
		m_TabPages[i++] = &dlg_Tab7;
	}
You need to create and add an instance of each of your dlg objects. I have 7 tabs, each with its own class, so have to add each one seperately.

If your tabs aren't working, your dlg objects aren't getting added to the tabctrl.

I hope that gets you a little closer!
 
Superb!

It turned out I was placing my variables in the wrong place =) You guys need to post this to a website or a
faq for future reference. I know that by searching on google you won't find a whole lot (MSDN is 10 times worse)

I am much in your debt firelex and bluecrush.

Cheers,
Ron

/**************
Me: When will the project be finished?
Them: What project?
Me: The one you have been working on for the last 3 years!
Them: Ohh, we can't finish it?
Me: Why?
Them: We don't know how to program...
 
Ok ok, so I can't quit asking questions =)

When you define CDialog* m_TabPages[_d_TabPagesNum];
in the .h file it will not let me place the variable there, it is requiring me to have an actual number.

Any thoughts?

-Ron

/**************
Me: When will the project be finished?
Them: What project?
Me: The one you have been working on for the last 3 years!
Them: Ohh, we can't finish it?
Me: Why?
Them: We don't know how to program...
 
You need to declare the variable as...
Code:
const int _d_TabPagesNum = 7;
...and put it at the very top of your CMyDlg.h file outside of your class definition:
Code:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
[COLOR=red]
const int _d_TabPagesNum = 7;
[/color]
class CMyDlg : public CDialog
{
   ...
}
 
could someone help guide me on this

Code:
Then when you want to create the dialog, you create a CPropertySheet object (you don't make your own class derived from it most of the time) and call AddPage for each page you want to add.  Call the sheet's DoModal() to create the dialog, which will be tabbed appropriately.

i dont understand where to do this, i have already created a new dialog using class CPropertySheet, but now i dont know what to do using AddPage or DoModal()
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top