This article has been updated and the details are available in the Updates section.
It's amazing what a bit of illness can do for your productivity at home! I've been ill for the last couple of days and here it is, yet another extended control. This time my target is the tab control. It seems to me that the tab control is a bit out of fashion these days with people preferring property sheets and pages instead. Well, I tried to use them and couldn't get the result I wanted. I needed some controls above the tabbed area (a bit like the demo, but a few more controls) and I just couldn't get that with property sheets and pages.
The problem with the standard tab control is that involves loads of effort to get pages in to the tab control. A quick in the MSDN located an example by Jeff Prosise that implemented a tabbed view, CTabView, allowing the tabs to be specified as dialog resources. Well, when it comes to code I'm a bit like I was as a ten year old with watches: I've got to fiddle about with it and see how it works and wonder how it could be better.
The first problem was that Jeff's solution was for a view class and I needed a control. The transition here is fairly easy as the view just wraps up the control. The second issue I had was that it didn't give much flexibility over what you could do with the tab pages. "How do I handle events within the page?" I asked myself. The answer was to allow the client to inherit from the tab page class and allow tab page objects to be added to the control and not just dialog resources.
Why is that solutions always present a new problem? By allowing this approach I then introduced a tricky situation with releasing the tab pages. Internally the tab control was creating the tab pages on the heap and maintaining a list of the pages that had been added. When the control was destroyed delete was called on each page in turn. This obviously causes problems if the pages are created outside of the tab control and passed in to it. In this case the tab control would destroy the pages created externally and then the parent dialog would try and do the same!!
delete
After a lengthy discussion in the Visual C++ forum with Tomasz Sowinski we came up with the answer. The tab control has a protected function for adding pages to the control and the public functions call this one. What is actually passed in to the protected function is a structure containing a pointer to a tab page and a flag indicating if the tab control should destroy it. This way if the tab page is passed in then the flag is set to false, but if the tab control creates the tab page the flag is set to true. At destruction time only the pages with the flag set are destroyed by the tab control. All others are someone else's problem.
CTabCtrlSSL
OnInitDialog
BOOL CCTabCtrlSSL_demoDlg::OnInitDialog () {
CDialog::OnInitDialog ();
// Setup the tab control
int nPageID = 0;
m_tabDemo.AddSSLPage (_T("Basic Tab"), nPageID++, IDD_TAB_BASIC);
m_advancedTab.Create (IDD_TAB_ADVANCED, this);
m_tabDemo.AddSSLPage (_T("Advanced Page"), nPageID++, &m_advancedTab);
m_aboutTab.Create (IDD_TAB_ABOUT, this);
m_tabDemo.AddSSLPage (_T("About"), nPageID++, &m_aboutTab);
return TRUE; // return TRUE unless you set the focus to a control
}
As you can see from the above code sample (taken from the demo), to add a basic tab call AddSSLPage and just pass it the resource ID of the dialog resource. For an advanced tab, create the dialog resource setting the window style to Child and the border style to none, then call AddSSLPage and pass in a pointer to your CTabPageSSL-derived object.
AddSSLPage
CTabPageSSL
To continue the style I started with CButtonSSL, here is the documentation for the classes:
Overview | Class members | Known problems
A "tab control" is analogous to the dividers in a notebook or the labels in a file cabinet. By using a tab control, an application can define multiple pages for the same area of a window or dialog box. Each page consists of a set of information or a group of controls that the application displays when the user selects the corresponding tab. CTabCtrlSSL extends the standard tab control by providing an interface for adding tabs to the control that are defined as dialog resources. These tabs are defined internally as CTabPageSSL objects. There is also an interface for adding objects of this type to the tab control allowing custom objects to be defined by inheriting from CTabPageSSL.
Any tab pages that are created by CTabCtrlSSL (those where the tab is added by passing in a dialog resource id) are destroyed by CTabCtrlSSL. It is the clients responsibility to destroy any CTabPageSSL-derived objects that are passed in to CTabCtrlSSL.
#include "TabCtrlSSL.h"
CTabCtrlSSL ();
Constructs a CTabCtrlSSL object.
int AddSSLPage (LPCTSTR pszTitle, int nPageID, int nTemplateID);
int AddSSLPage (LPCTSTR pszTitle, int nPageID, LPCTSTR pszTemplateName);
int AddSSLPage (LPCTSTR pszTitle, int nPageID, CTabPageSSL* pTabPage);
Zero-based index of the new tab if successful; otherwise –1.
pszTitle
Address of a null-terminated string that contains the tab text.
nPageID
The zero-based numeric ID for referring to the page to be added.
nTemplateID
Specifies the tab page’s resource ID.
pszTemplateName
Points to a null-terminated string that contains the name of the dialog resource to load.
pTabPage
Points to the CTabPageSSL object to be added.
Call this function to insert a new tab in an existing tab control.
See Also CTabCtrlSSL::RemoveSSLPage, CTabCtrlSSL::GetSSLPage
BOOL RemoveSSLPage (int nIndex);
Nonzero if successful; otherwise 0.
nIndex
Zero-based value of the item to delete.
Call this function to remove the specified item from a tab control.
See Also CTabCtrlSSL::AddSSLPage, CTabCtrlSSL::GetSSLPage
int GetSSLPageCount ();
Number of items in the tab control.
Call this function to retrieve the number of tabs in the tab control.
BOOL GetSSLPageTitle (int nIndex, CString& strTitle);
Zero-based value of the relevant tab page.
strTitle
A CString reference to receive the title of the tab page.
CString
Call this function to retrieve the title of the specified item in a tab control.
See Also CTabCtrlSSL::SetSSLPageTitle
BOOL SetSSLPageTitle (int nIndex, LPCTSTR pszTitle);
nIndexZero-based value of the relevant tab page.
Points to a null-terminated string that contains the title to set for the specified tab page.
Call this function to set the title of the specified item in a tab control.
See Also CTabCtrlSSL::GetSSLPageTitle
int GetSSLPageID (int nIndex);
Zero-based page identifier if successful; otherwise -1.
Zero-based index of the relevant tab page.
Call this function to retrieve the page identifier as specified when the tab was added of the specified item in a tab control. The page identifier may also be altered with the SetSSLPageID function.
See Also CTabCtrlSSL::SetSSLPageID
int SetSSLPageID (int nIndex, int nPageID);
The previous zero-based page identifier if successful; otherwise -1.
The new zero-based page identifier to be set.
Call this function to alter the page identifier for a specified tab page.
See Also CTabCtrlSSL::GetSSLPageID
BOOL ActivateSSLPage (int nIndex);
Zero-based index of the tab page to activate.
Call this function to change the active tab.
See Also CTabCtrlSSL::GetSSLActivePage
int GetSSLACtivePage ();
Zero-based index of the selected tab if successful or –1 if no tab is selected.
Call this function to retrieve the currently selected tab in a tab control.
See Also CTabCtrlSSL::ActivateSSLPage
CWnd* GetSSLPage (int nIndex);
A pointer to the requested tab page if successful; otherwise NULL.
Zero-based index of the tab page to get.
Call this function to get a specific tab in the tab control.
int GetSSLPageIndex (int nPageID);
Zero-based index of the specified tab if successful; otherwise -1.
Zero-based page identifier of the relevant tab page.
Call this function to retrieve the zero-based index of a tab page in the tab control.
BOOL OnInitPage (int nIndex, int nPageID);
Zero-based index of the tab page being initialized.
Zero-based page identifier of the page being initialized.
Override in derived class to initialize pages.
void OnActivatePage (int nIndex, int nPageID);
Zero-based index of the tab page being activated.
Zero-based page identifier of the page being activated.
Override in derived class to respond to page activations.
void OnDeactivatePage (int nIndex, int nPageID);
Override in derived class to respond to page deactivations.
void OnDestroyPage (int nIndex, int nPageID);
Override in derived class to free resources.
As far as I know there aren't any, but then the advanced tab part hasn't been tested much. If you find any let me know.
The most common question I have had about CTabCtrlSSL is about x, y, or z control that someone can't receive messages for.
I have explained the problem and the solution many times to many people and now, with thanks to John A Johnson, I have a solution that really works and can be applied to the source code for all.
The problem was that WM_COMMAND, WM_NOTIFY and WM_CMDMSG were passed on to the parent of the tab page, the tab control, which then passed them on to its parent (usually a dialog) so that the parent dialog could handle notifications, etc. of the tab pages. Unfortunately, this meant that any derived tab page would never get a chance to handle its own control notifications, unless you removed the OnCommand, OnNotify and OnCmdMsg handlers in CTabPageSSL.
WM_COMMAND
WM_NOTIFY
WM_CMDMSG
OnCommand
OnNotify
OnCmdMsg
The real solution is to turn off this command routing by default as most people want to handle control notifications in the class that owns them directly, but allow people to turn command routing back on, if they want the parent dialog to handle them instead. This resulted in a few extra methods in CTabPageSSL. But if you only want to handle commands/notifications in your derived tab page, you won't need to change your code at all, just drop in the new TabPageSSL.h and TabPageSSL.cpp.
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
BOOL CMainFlashDlg::PreTranslateMessage(MSG* pMsg)
{
if (m_hAccelTable)
{
if (::TranslateAccelerator(m_hWnd, m_hAccelTable, pMsg))
{
return(TRUE);
}
}
return CMyCommDlg::PreTranslateMessage(pMsg);
}
Error 2 error C2440: 'static_cast' : cannot convert from 'LRESULT (__thiscall CButtonSSL::* )(void)' to 'LRESULT (__thiscall CWnd::* )(WPARAM,LPARAM)' c:\documents and settings\hari om prakash\my documents\downloads\ctabctrlssl_demo\buttonssl.cpp 63 CTabCtrlSSL_demo
Error 3 error C2440: 'static_cast' : cannot convert from 'LRESULT (__thiscall CButtonSSL::* )(WPARAM)' to 'LRESULT (__thiscall CWnd::* )(WPARAM,LPARAM)' c:\documents and settings\hari om prakash\my documents\downloads\ctabctrlssl_demo\buttonssl.cpp 64 CTabCtrlSSL_demo
typedef struct {
WORD dlgVer;
WORD signature;
DWORD helpID;
DWORD exStyle;
DWORD style;
WORD cDlgItems;
short x;
short y;
short cx;
short cy;
} DLGTEMPLATEEX;
typedef struct {
WORD dlgVer;
WORD signature;
DWORD helpID;
DWORD exStyle;
DWORD style;
WORD cDlgItems;
short x;
short y;
short cx;
short cy;
sz_Or_Ord menu;
sz_Or_Ord windowClass;
WCHAR title[titleLen];
WORD pointsize;
WORD weight;
BYTE italic;
BYTE charset;
WCHAR typeface[stringLen];
} DLGTEMPLATEEX;
#if _MFC_VER < 0x0700
#include <afxtempl.h>
#include <..\src\afximpl.h>
#else
#include <afxtempl.h>
#include <..\src\mfc\afximpl.h>
#endif
General News Suggestion Question Bug Answer Joke Rant Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.