选项卡控件创造了一种错觉,即分隔线和显示区域是同一控件的一部分。事实并非如此。选项卡控件实际上只是标签,加上占位符显示区域。使显示区域的内容栩栩如生是应用程序的责任。
一般来说,实现一个功能齐全的选项卡控件需要以下步骤:
- 创建选项卡控件和标签。
- 创建显示区域的子控件。通常将包含单个“页面”的控件放置在对话框中。
- 订阅TCN_SELCHANGE消息,并动态更新控件的可见性,即隐藏所有不属于当前“页面”的控件并显示所有控件。将“页面”的所有控件放置在对话框中,只需切换对话框的可见性,就可以更轻松地完成此操作。
这是选项卡控件如何工作的粗略概述。鉴于您的代码,您需要更改一些内容。具体来说,需要注意以下几点:
- 引用的选项卡控件
IDC_TAB1
需要有WS_CLIPCHILDREN
样式,这样显示区域就不会覆盖子控件。
m_richEditCtrl
需要用WS_CHILD
样式创建。
- 使用CTabCtrl::AdjustRect计算显示区域的大小,并使用它来调整大小
m_richEditCtrl
以填充整个显示区域(如果这是你想要的)。
通过这些更改,您应该会看到一个选项卡控件,其显示区域由 Rich Edit 控件填充。在选项卡之间切换不会改变显示区域的内容。这是您需要根据应用程序的要求实现的东西。
以下代码示例基于向导生成的名为MfcTabCtrl的基于对话框的应用程序。生成的对话框资源 ( IDD_MFCTABCTRL_DIALOG
) 已删除所有内容,只留下一个空白对话框模板。
同样,主对话框实现的大部分功能都被剥离了,只留下了重要部分。这是MfcTabCtrlDlg.h标头:
#pragma once
#include "afxdialogex.h"
// Control identifiers
UINT constexpr IDC_TAB{ 100 };
UINT constexpr IDC_RICH_EDIT{ 101 };
class CMfcTabCtrlDlg : public CDialogEx
{
public:
CMfcTabCtrlDlg(CWnd* pParent = nullptr);
protected:
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnTabChanged(NMHDR* pNMHDR, LRESULT* pResult);
// Convenience implementation to calculate the display area
RECT GetDisplayArea();
virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()
private:
CTabCtrl m_TabCtrl{};
CRichEditCtrl m_richEditCtrl{};
};
实现文件MfcTabCtrlDlg.cpp也不是很广泛:
#include "MfcTabCtrlDlg.h"
CMfcTabCtrlDlg::CMfcTabCtrlDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCTABCTRL_DIALOG, pParent)
{
}
void CMfcTabCtrlDlg::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
// Resize tab control only after it has been created
if (IsWindow(m_TabCtrl)) {
m_TabCtrl.MoveWindow(0, 0, cx, cy);
// Determine display area
auto const disp_area{GetDisplayArea()};
// Resize child control(s) to cover entire display area
if (!IsRectEmpty(&disp_area) && IsWindow(m_richEditCtrl)) {
m_richEditCtrl.MoveWindow(&disp_area);
}
};
}
void CMfcTabCtrlDlg::OnTabChanged(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
auto const cur_sel{ m_TabCtrl.GetCurSel() };
switch (cur_sel) {
// First tab selected
case 0:
m_richEditCtrl.ShowWindow(SW_SHOW);
break;
// Second tab selected
case 1:
m_richEditCtrl.ShowWindow(SW_HIDE);
break;
}
// Allow other subscribers to handle this message
*pResult = FALSE;
}
// Returns the display area in client coordinates relative to the dialog.
// Returns an empty rectangle on failure.
RECT CMfcTabCtrlDlg::GetDisplayArea()
{
RECT disp_area{};
if (IsWindow(m_TabCtrl)) {
m_TabCtrl.GetWindowRect(&disp_area);
m_TabCtrl.AdjustRect(FALSE, &disp_area);
this->ScreenToClient(&disp_area);
}
return disp_area;
}
// The message map registers only required messages
BEGIN_MESSAGE_MAP(CMfcTabCtrlDlg, CDialogEx)
ON_WM_SIZE()
ON_NOTIFY(TCN_SELCHANGE, IDC_TAB, &CMfcTabCtrlDlg::OnTabChanged)
END_MESSAGE_MAP()
BOOL CMfcTabCtrlDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Set up tab control to cover entire client area
RECT client{};
GetClientRect(&client);
m_TabCtrl.Create(WS_VISIBLE | WS_CHILD | WS_CLIPCHILDREN, client, this, IDC_TAB);
m_TabCtrl.InsertItem(0, L"Stats");
m_TabCtrl.InsertItem(1, L"Settings");
// Set up rich edit control.
// The WS_BORDER style is set strictly to make it visible.
auto const disp_area{ GetDisplayArea() };
m_richEditCtrl.Create(WS_BORDER | WS_VISIBLE | WS_CHILD,
disp_area, &m_TabCtrl, IDC_RICH_EDIT);
return TRUE; // Let the system manage focus for this dialog
}
结果是一个对话框,其中包含一个带有两个标签的选项卡控件。包含的丰富编辑控件的可见性在TCN_SELCHANGE
通知处理程序中切换,仅在选择第一个选项卡时显示。更复杂的 GUI 也会根据此处当前选择的选项卡更新所有控件的可见性。
请注意,选项卡控件显示区域内的控件在对话框的生命周期内永远不会被破坏。即使在选项卡之间切换时,通常也希望保留用户数据。如有必要,也可以在切换选项卡时销毁和(重新)创建部分或全部子控件。