背景: 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 函数,未测试。