Как использовать CTabCtrl в приложении, основанном на диалоге MFC? - PullRequest
3 голосов
/ 27 апреля 2010

Мне нужно сделать что-то, что, как я ожидал, было простым - создать элемент управления вкладками, который имеет 2 вкладки, подразумевающие 2 режима работы для моего приложения. Когда пользователь нажимает на Tab1, ему будут представлены некоторые кнопки и текстовые поля, а когда он нажимает на Tab2, другой метод ввода. Я заметил, что существует класс CTabCtrl, который используется в MFC для добавления вкладок. Однако, как только я добавил вкладку ctrl с помощью дизайнера пользовательского интерфейса, я не смог указать, сколько вкладок будет там, используя окно свойств. Ища в сети, я нашел несколько примеров, но все они требовали, чтобы вы извлекли из CtabCtrl, создали 2 или более диалогов child и т. Д. И написали свой собственный класс. Мой вопрос заключается в том, что, поскольку я хочу сделать что-то настолько простое, почему я не могу сделать это с помощью знакомого мастера Add Event handler / Add member variable и затем обработать все остальное внутри класса моего приложения? Конечно, класс CTabCtrl по умолчанию может сделать что-то полезное без необходимости извлекать из него информацию?

Ответы [ 3 ]

3 голосов
/ 27 апреля 2010

Забудьте о CTabCtrl и используйте CMFCTabCtrl, с которым гораздо проще работать (это предполагает, что вы работаете с VS2008 SP1).

В противном случае вы, похоже, неправильно понимаете, как работает элемент управления вкладками. Он предоставляет только «полоску вкладок» вверху и отправляет сообщения, когда пользователь нажимает на другую. Он не предоставляет вам «полотна вкладок», на которые вы можете поместить элементы управления. Показ и скрытие элементов управления на вкладке - это то, что нужно программисту. Редактор ресурсов там мало что поддерживает. Как говорит Стюарт, самый распространенный способ работы - это иметь дочерние диалоги на своей вкладке и скрывать их все, кроме одной из текущей вкладки.

Вам не нужно наследовать от CTabCtrl, вы также можете реализовать переключение в окне, которое является родительским для CTabCtrl.

2 голосов
/ 02 сентября 2015

Подход, который я использовал с диалоговым окном MFC, содержащим CTabCtrl, заключался в создании небольшого класса для управления элементом управления вкладками и использования шаблонов диалогов для создания фактического содержимого окна вкладки.

Это все еще работает, поэтому исходный код не очень чистый, но здесь есть несколько частей. Например, CTabCtrlDialog нужен конструктор и деструктор для освобождения объекта, который мог быть создан.

В файле ресурсов у меня есть шаблон диалога с элементом управления вкладкой, за которым следуют три шаблона диалога для каждого из различных окон содержимого вкладки, вставленных в элемент управления вкладкой. В то время как диалоговое окно, отображающее элемент управления вкладкой, имеет стиль WS_POPUP, шаблоны диалога для окон вкладок, которые вставляются в элемент управления вкладкой, вместо этого имеют стиль WS_CHILD. Это изменение делает окна вкладок дочерними окнами, чтобы при перемещении диалогового окна все оставалось в порядке, без каких-либо дополнительных усилий с моей стороны.

В моем случае окна вкладок, которые вставляются в элемент управления вкладками, отображают набор флажков для указания различных рабочих параметров. Использование подхода на основе шаблонов диалогов позволяет очень просто создать необходимый контент окна вкладок.

Я получил класс от CTabCtrl, который расширяет стандартный класс MFC дополнительным методом для вставки в элемент управления вкладки окна вкладки на основе указанного идентификатора шаблона диалога. Поскольку это всего лишь один диалог, я просто поместил этот класс в те же файлы, .h и .cpp, что и сами компоненты диалога.

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

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

Метод InsertItemDialogTemplate() выглядит так:

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

Для обработки выбора вкладок, отображающих различные вкладки, у меня есть следующая карта сообщений, а затем два диалоговых обработчика событий.

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

    }

В методе DoDataExchange() диалогового окна у меня есть следующее, которое сначала создает элемент управления вкладками, а затем создает каждое из окон вкладок и вставляет их в элемент управления вкладками.

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);
    }
}
1 голос
/ 27 апреля 2010

Элемент управления вкладками MFC представляет собой довольно тонкую оболочку над элементом управления вкладками win32, который работает практически так, как вы описали. Это окно, которое обеспечивает переключение между дочерними окнами с помощью вкладок. Как это бывает, в прямой win32 это самый полезный способ для работы. Если вы хотите сделать что-то более сложное, чем переключение между отдельными окнами, вы делаете это с помощью дочерних диалогов. MFC мало чем поможет, но унаследовать от CTabCtrl и использовать дочерние диалоги действительно не очень сложно, хотя если вы привыкли к тому, как WinForms управляет вкладками, это кажется ненужным.

Если вы хотите, чтобы элемент управления вкладками находился в корне диалогового окна, а рядом с ним нет других элементов управления, вы можете посмотреть на CPropertySheet (http://msdn.microsoft.com/en-us/library/d3fkt014(VS.80).aspx), который, вероятно, проще использовать. Если вы не хотите использовать любую из функций мастера, которую вам даже не нужно извлекать из нее - вы просто создаете пару дочерних диалоговых классов, затем в том месте, где вы хотите создать страницу свойств, создаете объект, добавляете к нему страницы и вызываете это.

...