How to use CTabCtrl in an MFC dialog based application?

I need to do what I expected was simple - create a tab control that has 2 tabs, which implies 2 modes of operation for my application. When the user presses Tab1, he will be presented with some buttons and text boxes, and when he presses Tab2, a different input method. I noticed there was a CTabCtrl class that was used in MFC to add tabs. However, once I added the ctrl tab using the UI designer, I couldn't specify how many tabs the properties window would use. Searching the net I found several examples, but they all required you to exit CtabCtrl, create 2 or more child dialogs, etc. And to write your own custom class. My question is that since I want to do something so basic,why don't I do it using the familiar Add Event handler / Add member and then handle everything else inside my application class? Surely the default CTabCtrl class can do something useful without having to extract from it?

+3


a source to share


3 answers


The MFC Tab Control is a fairly thin wrapper over the win32 tab control that works pretty much the way you describe. It is a window that allows you to switch between child windows using tabs. As it goes, in straight win32 it is the most useful way to work. If you want to do something more complex than switching between individual windows, you do it using child dialogs. MFC doesn't help you much, but outputting from CTabCtrl and using child dialogs is really not very hard to do, although if you're used to the way WinForms does tab controls, it seems unnecessary.



If you want the tab control to be at the root of the dialog, without any other controls next to it, you can look at CPropertySheet ( http://msdn.microsoft.com/en-us/library/d3fkt014(VS. 80) .aspx ) which is probably easier to use. If you don't want to use any of the wizard's functions, you don't even need to deduce from it - you just create a couple of child dialog classes, then wherever you want to create a property sheet, make an object, add pages to it and call it.

+1


a source


Forget about CTabCtrl and use CMFCTabCtrl, which is much easier to work with (assuming you are running VS2008 SP1).

Otherwise, you seem to be misunderstanding how the tab control works. It only contains a "tab" at the top and sends messages when the user clicks on another. It does not provide you with "tabbed canvases" on which you can place controls. Showing and hiding controls in a tab is something that a programmer should take care of. The resource editor has little support. As Stewart says, the most common way to work is to have child dialogs on a tab and hide all of them except one of the current tab.



You don't need to output from CTabCtrl, you can also implement switching behavior in the window that is the parent of CTabCtrl.

+3


a source


The approach I used in the MFC containing dialog CTabCtrl

was to create a small class to control the tab control and dialog templates to create the actual tab content.

It still works, so the source code isn't very clean, but here are some parts. For example, it CTabCtrlDialog

needs a constructor and a destructor to free an object that might have been created.

In a resource file, I have a dialog template with a tab control followed by three dialog templates for each of the content content windows inserted into the tab control. Although the dialog that displays the tab control is styled WS_POPUP

, the dialog templates for tab windows inserted into the tab control are styled WS_CHILD

. This change makes the child windows of tab windows so that when the dialog is moved everything stays aligned upward, without any extra effort on my part.

In my case, tab windows that are inserted into a tab control display a set of checkboxes to indicate various operating parameters. Using a dialog template template makes it very easy to create the necessary tab window content.

I am getting a class from CTabCtrl

which extends the standard MFC class with an additional method to insert into a tab window tab control based on the specified dialog template ID. Since this is just one dialog, I just put this class in the same .h and .cpp files as the dialog components themselves.

class CTabCtrlDialog : public CTabCtrl
{
public:
    void InsertItemDialogTemplate (UINT nIDTemplate, int nItem, TCITEM* pTabCtrlItem);

public:
    struct {
        UINT     nIDTemplate;
        CDialog  *pDialog;
    }  m_pDialogData[10];
};

      

The method InsertItemDialogTemplate()

looks like this:

/*
 *  InsertItemDialogTemplate ()
 *
 *  Insert into a tab control a tab pane based on the specified dialog template.  The
 *  dialog template describes what the tab pane looks like so far as controls, etc.
 *
 *  NOTE: The STYLE description must be WS_CHILD and not WS_POPUP.  Also the dialog
 *        needs to have as its top coordinate some distance in pixels so that the
 *        various tab descriptions are visible.  For instance an example dialog
 *        template in the resource file may look like:
 *            IDD_CASHIER_TAB_ONE DIALOGEX 0, 10, 178, 113
 *            STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
 *            FONT 8, "MS Shell Dlg", 400, 0, 0x1
 *            BEGIN
 *                LTEXT           "Dialog Tab one",IDC_STATIC,6,44,90,17
 *            END
 *
**/
void CTabCtrlDialog::InsertItemDialogTemplate (UINT nIDTemplate, int nItem, TCITEM* pTabCtrlItem)
{
    InsertItem (nItem, pTabCtrlItem);
    m_pDialogData[nItem].nIDTemplate = nIDTemplate;
    m_pDialogData[nItem].pDialog = new CDialog ();
    m_pDialogData[nItem].pDialog->Create (nIDTemplate, this);
    m_pDialogData[nItem].pDialog->ShowWindow (FALSE);
}

      

To handle the selection of a tab that displays different tabs, I have the following message map and then two event handlers in the dialog.

BEGIN_MESSAGE_MAP(CDiaCashierEdit, CDialog)
    ON_NOTIFY(TCN_SELCHANGE, IDC_TAB_CASHIER_EDIT_STATUS, &CDiaCashierEdit::OnTcnSelchangeTabCashierEditStatus)
    ON_NOTIFY(TCN_SELCHANGING, IDC_TAB_CASHIER_EDIT_STATUS, &CDiaCashierEdit::OnTcnSelchangingTabCashierEditStatus)
END_MESSAGE_MAP()


    void CDiaCashierEdit::OnTcnSelchangeTabCashierEditStatus(NMHDR *pNMHDR, LRESULT *pResult)
    {
        // TODO: Add your control notification handler code here
        *pResult = 0;

        int i = TabCtrl_GetCurSel(pNMHDR->hwndFrom);
        m_TabCtrl.m_pDialogData[i + 1].pDialog->ShowWindow (TRUE);

    }

    void CDiaCashierEdit::OnTcnSelchangingTabCashierEditStatus(NMHDR *pNMHDR, LRESULT *pResult)
    {
        // TODO: Add your control notification handler code here
        *pResult = 0;

        int i = TabCtrl_GetCurSel(pNMHDR->hwndFrom);
        m_TabCtrl.m_pDialogData[i + 1].pDialog->ShowWindow (FALSE);

    }

      

In the DoDataExchange()

dialog box method , I have the following which creates the tab control first and then creates each of the tab windows and inserts them into the tab control.

void CDiaCashierEdit::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_EDIT_CASHIER_NAME, m_CashierName);
    DDX_Control(pDX, IDC_EDIT_CASHIER_SUPNO, m_SupervisorId);
    DDX_Control(pDX, IDC_EDIT_CASHIER_TEAMNO, m_TeamNumber);
    DDX_Control(pDX, IDC_EDIT_CASHIER_GCSTART, m_GuestCheckStart);
    DDX_Control(pDX, IDC_EDIT_CASHIER_GCEND, m_GuestCheckEnd);
    DDX_Control(pDX, IDC_TAB_CASHIER_EDIT_STATUS, m_TabCtrl);
    if (pDX->m_bSaveAndValidate) {
        m_CashierName.GetWindowText (m_paraCashier.auchCashierName, 20);
        m_paraCashier.usSupervisorID = m_SupervisorId.GetWindowTextAsInt();
        m_paraCashier.uchTeamNo = m_TeamNumber.GetWindowTextAsInt();
        m_paraCashier.usGstCheckStartNo = m_GuestCheckStart.GetWindowTextAsInt();
        m_paraCashier.usGstCheckEndNo = m_GuestCheckEnd.GetWindowTextAsInt();
        for (int i = 0; i < sizeof(m_TabItemOneStatus)/sizeof(m_TabItemOneStatus[0]); i++) {
            int iTab = m_TabItemOneStatus[i].sTabItem;
            int iDlg = m_TabItemOneStatus[i].iDlgItem;
            int iOffset = m_TabItemOneStatus[i].sOffset;
            CButton *p = (CButton *) m_TabCtrl.m_pDialogData[iTab].pDialog->GetDlgItem(iDlg);
            if (p->GetCheck()) {
                m_paraCashier.fbCashierStatus[iOffset] |= m_TabItemOneStatus[i].uchBit;
            } else {
                m_paraCashier.fbCashierStatus[iOffset] &= ~(m_TabItemOneStatus[i].uchBit);
            }
        }
    } else {
        m_CashierName.SetWindowText(m_paraCashier.auchCashierName);
        m_SupervisorId.SetWindowTextAsInt (m_paraCashier.usSupervisorID);
        m_TeamNumber.SetWindowTextAsInt (m_paraCashier.uchTeamNo);
        m_GuestCheckStart.SetWindowTextAsInt (m_paraCashier.usGstCheckStartNo);
        m_GuestCheckEnd.SetWindowTextAsInt (m_paraCashier.usGstCheckEndNo);
        m_TabCtrl.InsertItemDialogTemplate (IDD_CASHIER_TAB_ONE, 1, &m_TabItemOne);
        m_TabCtrl.InsertItemDialogTemplate (IDD_CASHIER_TAB_TWO, 2, &m_TabItemTwo);
        m_TabCtrl.InsertItemDialogTemplate (IDD_CASHIER_TAB_THREE, 3, &m_TabItemThree);
        for (int i = 0; i < sizeof(m_TabItemOneStatus)/sizeof(m_TabItemOneStatus[0]); i++) {
            int iTab = m_TabItemOneStatus[i].sTabItem;
            int iDlg = m_TabItemOneStatus[i].iDlgItem;
            int iOffset = m_TabItemOneStatus[i].sOffset;
            CButton *p = (CButton *) m_TabCtrl.m_pDialogData[iTab].pDialog->GetDlgItem(iDlg);
            if (m_paraCashier.fbCashierStatus[iOffset] & m_TabItemOneStatus[i].uchBit) {
                p->SetCheck (1);
            } else {
                p->SetCheck (0);
            }
        }
        m_TabCtrl.m_pDialogData[1].pDialog->ShowWindow (TRUE);
    }
}

      

+2


a source







All Articles