MFC笔记:初始化tab控件一点改进

背景:
MFC工具带有tab标签,作为导航。需要在对话框初始化时进行初始化。

一、流程简述

实现此功能需要做如下事情:

  • 创建主对话框、多个子对话框。
  • 子对话框需要设置属性:
    外观Style为Child,Boarder选None。
  • 关联tab标签控件变量(也可以直接用控件ID)。
  • 初始化,子对话框添加到tab标签上。
  • 响应点击函数,以便切换对话框。

下面先列出原始版本,分析问题,再解决问题。
本文省略MFC控件布局的说明。

二、原始版本

2.1 变量声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace NSONVIF {
enum PAGE_TYPE {
PAGE_DISCOVER = 0,
PAGE_DEVICE,
PAGE_MEDIA,
PAGE_IMAGING,
PAGE_DEBUG,

PAGE_MAX,
};
}

std::vector<CDialog *> m_pvPage;
NSONVIF::PAGE_TYPE m_nCurTab;
CDiscover m_cDlgDiscover;
CDeviceService m_cDlgDevice;
CMediaService m_cDlgMedia;
CImagingService m_cDlgImaging;
CDebugInfo m_cDlgDebug;

CTabCtrl m_ctrTab;

使用m_pvPage存放子对话框指针。m_cDlg开头的变量为子对话框。m_ctrTab为Tab控件关联的类。

2.2 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
m_ctrTab.InsertItem(0, "Discover");
m_ctrTab.InsertItem(1, "Device");
m_ctrTab.InsertItem(2, "Media");
m_ctrTab.InsertItem(3, "Imaging");
m_ctrTab.InsertItem(4, "Debug");

m_pvPage.resize(NSONVIF::PAGE_MAX);

m_cDlgDiscover.Create(IDD_DLG_DISCOVER, &m_ctrTab);
m_pvPage[NSONVIF::PAGE_DISCOVER] = &m_cDlgDiscover;
m_cDlgDiscover.SetOnvifProxy(&m_cProxy);

m_cDlgDevice.Create(IDD_DLG_DEVICE, &m_ctrTab);
m_pvPage[NSONVIF::PAGE_DEVICE] = &m_cDlgDevice;
m_cDlgDevice.SetOnvifProxy(&m_cProxy);

m_cDlgMedia.Create(IDD_DLG_MEDIA, &m_ctrTab);
m_pvPage[NSONVIF::PAGE_MEDIA] = &m_cDlgMedia;
m_cDlgMedia.SetOnvifProxy(&m_cProxy);

m_cDlgImaging.Create(IDD_DLG_IMAGING, &m_ctrTab);
m_pvPage[NSONVIF::PAGE_IMAGING] = &m_cDlgImaging;
m_cDlgImaging.SetOnvifProxy(&m_cProxy);

m_cDlgDebug.Create(IDD_DLG_DEBUG, &m_ctrTab);
m_pvPage[NSONVIF::PAGE_DEBUG] = &m_cDlgDebug;
m_cDlgDebug.SetOnvifProxy(&m_cProxy);

CRect rc;
m_ctrTab.GetClientRect(rc);
rc.top += 22;
rc.bottom -= 1;
rc.left += 1;
rc.right -= 1;

for (unsigned int i = 0; i < m_pvPage.size(); i++)
{
m_pvPage[i]->MoveWindow(&rc);
}

// first page
m_nCurTab = NSONVIF::PAGE_DISCOVER;
m_pvPage[m_nCurTab]->ShowWindow(SW_SHOW);

2.3 响应点击事件

即添加OnTcnSelchangeTab事件并实现

1
2
3
4
5
6
7
8
9
void COnvifClientDlg::OnTcnSelchangeTab(NMHDR *pNMHDR, LRESULT *pResult)
{
*pResult = 0;

m_pvPage[m_nCurTab]->ShowWindow(SW_HIDE);
m_nCurTab = static_cast<NSONVIF::PAGE_TYPE>(m_ctrTab.GetCurSel());
if (m_pvPage[m_nCurTab])
m_pvPage[m_nCurTab]->ShowWindow(SW_SHOW);
}

三、存在问题

原始版本的代码有点死板,不够灵动:

  • 多了控件顺序的宏定义。命名空间可删除。
  • 重复但又有差异的代码较多。如初始化代码。

四、改良版本

4.1 变量声明

1
2
3
4
5
6
7
8
9
std::vector<CDialog *> m_pvPage;
int m_nCurTab; // 直接用int即可
CDiscover m_cDlgDiscover;
CDeviceService m_cDlgDevice;
CMediaService m_cDlgMedia;
CImagingService m_cDlgImaging;
CDebugInfo m_cDlgDebug;

CTabCtrl m_ctrTab;

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
struct cDlgCtrl_t {
LPCTSTR name;
CDialogEx* dlg;
int id;
};

LPCTSTR lpName[10]; // make bigger...
int i = 0;
lpName[i++] = L"Discover";
lpName[i++] = L"Device";
lpName[i++] = L"Media";
lpName[i++] = L"Imaging";
lpName[i++] = L"Debug";

i = 0;
struct cDlgCtrl_t dlgCtrls[10]; // make bigger...
dlgCtrls[i].name = lpName[i];
dlgCtrls[i].dlg = &m_cDlgDiscover;
dlgCtrls[i].id = IDD_DLG_DISCOVER;
dlgCtrls[++i].name = lpName[i];
dlgCtrls[i].dlg = &m_cDlgDevice;
dlgCtrls[i].id = IDD_DLG_DEVICE;
dlgCtrls[++i].name = lpName[i];
dlgCtrls[i].dlg = &m_cDlgMedia;
dlgCtrls[i].id = IDD_DLG_MEDIA;
dlgCtrls[++i].name = lpName[i];
dlgCtrls[i].dlg = &m_cDlgImaging;
dlgCtrls[i].id = IDD_DLG_IMAGING;
dlgCtrls[++i].name = lpName[i];
dlgCtrls[i].dlg = &m_cDlgDebug;
dlgCtrls[i].id = IDD_DLG_DEBUG;

CRect rc;
m_ctrTab.GetClientRect(rc);
rc.top += 22;
rc.bottom -= 1;
rc.left += 1;
rc.right -= 1;

m_pvPage.resize(i+1);
for (unsigned int i = 0; i < m_pvPage.size(); i++)
{
m_ctrTab.InsertItem(i, dlgCtrls[i].name);
dlgCtrls[i].dlg->Create(dlgCtrls[i].id, &m_ctrTab);
dlgCtrls[i].dlg->SetOnvifProxy(&m_cProxy);

m_pvPage[i] = dlgCtrls[i].dlg;
m_pvPage[i]->MoveWindow(&rc);
}

m_nCurTab = 0;
m_pvPage[m_nCurTab]->ShowWindow(SW_SHOW);

4.2 响应点击事件

1
2
3
4
5
6
7
8
9
void COnvifClientDlg::OnTcnSelchangeTab(NMHDR *pNMHDR, LRESULT *pResult)
{
*pResult = 0;

m_pvPage[m_nCurTab]->ShowWindow(SW_HIDE);
m_nCurTab = m_ctrTab.GetCurSel();
if (m_pvPage[m_nCurTab])
m_pvPage[m_nCurTab]->ShowWindow(SW_SHOW);
}

相对而言,改良后的代码更好维护。

五 切换页面

在初始化时,遍历每个子对话框,并调用 Create 创建窗口,注意,此时会调用到子对话框的OnInitDialog函数(哪怕当时没有显示出来)。
当切换 Tab 时,子对话框并不会再次初始化。因此,需要在切换响应函数 OnTcnSelchangeTab 中显示进行。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void COnvifClientDlg::OnTcnSelchangeTab(NMHDR *pNMHDR, LRESULT *pResult)
{
*pResult = 0;

m_pvPage[m_nCurTab]->ShowWindow(SW_HIDE);
m_nCurTab = m_ctrTab.GetCurSel();
if (m_pvPage[m_nCurTab])
{
m_pvPage[m_nCurTab]->ShowWindow(SW_SHOW);
if (m_nCurTab == 0) // 当切换到第0个子对话框时
{
CDiscover* dlg = (CDiscover*)m_pvPage[m_nCurTab];
dlg->Reinit(); // 重新初始化
}
}
}

注:是否可以直接调用子对话框的 OnInitDialog 函数,未测试。