This is a bit of an advanced question but here goes.
I want to use a tab control in a docked dialog. Since CPropertySheet won't do this correctly, I had to code my own as well as my own docking system.
Docking system
CDockContWnd
Windows that are docked to the frame, act as containers for other windows.
CDockDialog
A docking dialog class compatible with CDockContWnd.
Usage in tile editor
CLayerDlg
A CDockDialog derived class that displays the current layers of the tile map and allows the user to change view and draw layers. Has a CListBox object - just used the default one in MFC.
CMyTabCtrl - a tab control derived from CTabCtrl that supports showing pages, adding pages, etc, etc.
CToolsDlg
A CDockDialog window contained in the CDockContWnd acting as a tools window for the tab control. Has a CMyTabCtrl object. This, in essence, is my replacement for CPropertySheet. Just wraps a CMyTabCtrl object.
The tab control inside of the CToolsDlg is NOT receiving TCN_SELCHANGE and is not painting correctly. I downloaded a sample from codeguru for MSVC 6, converted it to .NET 2005 and it works just fine.
However, when attempting to implement it into my own system, it does not paint correctly nor does it receive any messages.
I'm not quite sure how to add a CTabCtrl object to the dialog in the resource editor, but use my own class instead of CTabCtrl.
Here is the setup code from SetupMainView(), called by OnFileNewProject().
CMainFrame.h
Code:
void CMainFrame::SetupMainView(void)
{
//Get pointer to view first
m_pView=(CTileEditorView *)GetActiveView();
//Get client rect of frame
CRect rect;
GetClientRect(&rect);
//Create new tools dialog
m_pToolsDlg=new CToolsDlg;
m_pToolsDlg->Create(IDD_TOOLS,this);
//Dock the dialog to the left side of the frame
DockDialog(m_pToolsDlg,DLG_DOCK_LEFT);
//Add a layer dialog (type CDockDialog) to the tools dialog
//tab control
CLayerDlg *dlg=new CLayerDlg();
dlg->Create(IDD_LAYERWND,this);
m_pToolsDlg->AddPage(dlg,L"Layers");
...
ToolsDlg.cpp
Code:
// ToolsDlg.cpp : implementation file
//
#include "stdafx.h"
#include "TileEditor.h"
#include "ToolsDlg.h"
// CToolsDlg dialog
IMPLEMENT_DYNAMIC(CToolsDlg, CDockDialog)
CToolsDlg::CToolsDlg(CWnd* pParent /*=NULL*/)
: CDockDialog(CToolsDlg::IDD, pParent)
{
}
CToolsDlg::~CToolsDlg()
{
}
void CToolsDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_TAB_TOOLS, m_tabMyTabCtrl);
}
BEGIN_MESSAGE_MAP(CToolsDlg, CDockDialog)
END_MESSAGE_MAP()
// CToolsDlg message handlers
Pretty simple, just a dummy CDockDialog. MFC does not support custom subclassing via Class Wizard so I changed some things myself to get it to work. Namely the message map and constructor.
This works fine and dialogs receive messages. It's the tab control that isn't receiving messages. Also the layer dialog still receives it's messages even though it's inside of a tab control that is not. Quite odd.
The tab appears in the tab control, but you must move the mouse over areas to invalidate it before you see the controls. Also when you add a second page, you can see both tabs, with a white window area, and nothing else works. Not even drawing on the first tabbed window.
MyTabCtrl.h
Code:
#pragma once
// CMyTabCtrl
#include <vector>
class CMyTabCtrl : public CTabCtrl
{
DECLARE_DYNAMIC(CMyTabCtrl)
std::vector<CWnd *> m_vWnds;
public:
CMyTabCtrl();
virtual ~CMyTabCtrl();
void ShowPage(int iPage);
void AddPage(CWnd *pDlg,CString text);
protected:
DECLARE_MESSAGE_MAP()
public:
public:
afx_msg void OnTcnSelchange(NMHDR *pNMHDR, LRESULT *pResult);
};
MyTabCtrl.cpp
Code:
// MyTabCtrl.cpp : implementation file
//
#include "stdafx.h"
#include "TileEditor.h"
#include "MyTabCtrl.h"
#include "DockDialog.h"
#include "ToolsDlg.h"
// CMyTabCtrl
IMPLEMENT_DYNAMIC(CMyTabCtrl, CTabCtrl)
CMyTabCtrl::CMyTabCtrl()
{
}
CMyTabCtrl::~CMyTabCtrl()
{
//Cleanup vector of CWnds
for (int i=0;i<static_cast<int>(m_vWnds.size());i++)
{
delete m_vWnds[i];
}
}
BEGIN_MESSAGE_MAP(CMyTabCtrl, CTabCtrl)
ON_NOTIFY_REFLECT(TCN_SELCHANGE, OnTcnSelchange)
END_MESSAGE_MAP()
void CMyTabCtrl::AddPage(CWnd *pWnd,CString text)
{
ASSERT(pWnd->m_hWnd);
pWnd->ModifyStyle(WS_POPUP,WS_CHILD);
m_vWnds.push_back(pWnd);
InsertItem(m_vWnds.size()-1,text);
ShowPage(m_vWnds.size()-1);
}
void CMyTabCtrl::ShowPage(int iPage)
{
m_vWnds[iPage]->ShowWindow(SW_HIDE);
CRect rectClient;
CRect rectWnd;
GetClientRect(rectClient);
AdjustRect(FALSE,rectClient);
GetWindowRect(rectWnd);
GetParent()->ScreenToClient(rectWnd);
rectClient.OffsetRect(rectWnd.left,rectWnd.top);
for(int nCount=0; nCount < static_cast<int>(m_vWnds.size()); nCount++)
{
m_vWnds[nCount]->SetWindowPos(&wndTop,
rectClient.left,
rectClient.top,
rectClient.Width(),
rectClient.Height(),
SWP_HIDEWINDOW);
}
m_vWnds[iPage]->SetWindowPos(&wndTop,
rectClient.left,
rectClient.top,
rectClient.Width(),
rectClient.Height(),
SWP_SHOWWINDOW);
m_vWnds[iPage]->ShowWindow(SW_SHOW);
m_vWnds[iPage]->Invalidate();
Invalidate();
}
// CMyTabCtrl message handlers
void CMyTabCtrl::OnTcnSelchange(NMHDR *pNMHDR, LRESULT *pResult)
{
// TODO: Add your control notification handler code here
AfxMessageBox(L"On sel change");
*pResult = 0;
}
Any ideas?
Docking targets confusion
From the code sample you may be confused with DockDialog().
If you are confused as to how DockDialog() knows which CDockContWnd to use as the docking target, it is a hack for right now and always uses vector element 0 or m_vDockContWnds[0]. The docking system is not completely functional in that the layout manager is not yet taking into account all CDockContWnds attached to the frame. This is a simple fix, but right now I need to get this editor done so working on GUI while the game project just sits idle is not good.
The target of the docking operation is normally specified in a third parameter (CDockContWnd *pDockTarget) and still can be by directly accessing the vector of CDockContWnd's in CMainFrame.
void CMainFrame::DockDialog(CDockDialog *pDialog,
DLG_DOCK_MODE mode,
CDockContWnd *pDockTarget)
enum DLG_DOCK_MODE {
DLG_DOCK_TOP,
DLG_DOCK_LEFT,
DLG_DOCK_BOTTOM,
DLG_DOCK_RIGHT
};
However this is not the source of the problem.