Introduction
One of the most aggravating things about using a tab control is having to use property sheets or embedding child dialogs into the tab control.
Both approaches work, but cause the program to have more code than I think is necessary.
So I decided to create a very simple to use tab control class, which dynamically creates the child controls for you, and also cleans up the memory for you.
This class is very easy to use, and requires very little coding, and should save you a lot of time!
It supports as many tabs as you need right out of the box, and can create as many controls as you need as well!
It allows you to insert a child control into a tab with a single line of code!
It allows for easy access to control values and the ability to modify the controls very easily.
It also supports keyboard navigation though the child controls.
Background
The class was designed for one of my projects "Options" dialog.
The tab class takes care of all object memory cleanup, and dynamically creates and destroys all child window objects for you.
I've tested the class with Win2K and XP, I don't use Win98 anymore, but I'm sure it probably works fine with 98.
It is compatible with XP visual styles, many thanks for the idea I used from another codeproject article for the XP styles compatibility;
( http://www.codeproject.com/wtl/ThemedDialog.asp )
(I create a solid background brush from a pixel of the color we need, rather than using a repositioned patternbrush
of the bitmap)
Using the code
Using the class is simple, just add...
#include "CMyTabClass.h"
to your dialog's header file like so...
#pragma once
#include "afxcmn.h"
#include "MyTabCtrl.h"
class COptionsDlg : public CDialog
{
Next create a public member variable for your CTabCtrl
in your dialog, and add the WS_TABSTOP
style to your CTabCtrl
as well,
once you have done that, change your declared variable from "CTabCtrl" to "CMyTabCtrl" as shown below...
public:
CMyTabCtrl m_cTab;
Your now ready to use the tab class! Currently the class has support
for: GroupBoxes
, RadioBoxes
, Checkboxes
, Buttons
, and Statictext
You could very easily add support for more controls, just take a look at the code and modify it a little bit.
The following functions are what you use for inserting controls, here is a
explanation of how they work...
uID
is the ID of the control you are making, this must be unique of course.
sCaption
would be the text in the control.
iX
and iY
are your X and Y offsets, so you can move your control around within the tab control.
iTab
is the tab which you want to insert the control in.
iLen
would be the length of the control.
bCheck
is the check state of the item once it is created (Checked or Unchecked)
uLocation
is a Positioning Helper, it can be one or more of following: P_TOP
, P_LEFT
, P_RIGHT
, P_BELOW
Every time you insert a control, the class remembers the location of the last control inserted,
so you can use uLocation
to position the next control Below (P_BELOW
), and align it with the previous controls left side (P_LEFT
)
You can use a Bitwise OR to include multiple...
uLocation = P_BELOW|P_LEFT;
P_BELOW
= aligns the top of control you are inserting, with the bottom of the previous inserted control
P_TOP
= aligns the top of control you are inserting, with the top of the previous inserted control
P_LEFT
= aligns the left of control you are inserting, with the left of the previous inserted control
P_RIGHT
= aligns the left of control you are inserting, with the right of the previous inserted control
The BottomOf
, RightOf
, LeftOf
, and TopOf
functions are helper functions for positioning as well.
You can use them to obtain the location of a previously inserted control, then drop that value into your iX
or iY
to help position a new control.
void CreateCheckBox(BOOL bCheck, LPCTSTR sCaption, int nID,
int iTab, UINT uLocation = 0, int iX = 0, int iY = 0);
void CreateGroupBox(LPCTSTR sCaption, int nID, int iTab,
int width, int height, UINT uLocation = 0, int iX = 0,
int iY = 0);
void CreateRadioBox(BOOL bCheck, LPCTSTR sCaption, int nID,
int iTab, UINT uLocation = 0, int iX = 0, int iY = 0,
DWORD dwStyle = 0);
void CreateButton(LPCTSTR sCaption, int nID, int iTab,
UINT uLocation = 0, int iX = 0, int iY = 0,
int iLen = 50);
void CreateEdit(LPCTSTR sCaption, int nID, int iTab,
UINT uLocation = 0, int iX = 0, int iY = 0,
int iLen = 100);
void CreateStatic(LPCTSTR sCaption, int nID, int iTab,
UINT uLocation = 0, int iX = 0, int iY = 0);
int BottomOf(int nID);
int RightOf(int nID);
int LeftOf(int nID);
int TopOf(int nID);
CString GetItemText(int nID);
int GetItemTextLength(int nID);
int GetItemTextAsInt(int nID);
BOOL GetItemCheckState(int nID);
Below is an example of the code in use, 9 controls are created in 2 Tabs with only
12 lines of code.
BOOL COptionsDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_cTab.Init();
m_cTab.InsertItem(0,"Connections");
m_cTab.InsertItem(1,"Notifications");
m_cTab.CreateGroupBox("Startup",1,0,310,52);
m_cTab.CreateCheckBox(FALSE,"Start Program with
Windows", 2, 0, 0, 10, 15);
m_cTab.CreateCheckBox(FALSE,"Connect to All Contacts
on Startup", 3, 0, P_BELOW | P_LEFT);
m_cTab.CreateGroupBox("New Contact Acceptance", 4, 0,
310, 52, P_BELOW, 0, 10);
m_cTab.CreateRadioBox(FALSE,"Auto Accept All New
Contacts", 5, 0, P_LEFT | P_TOP, 10, 15, WS_GROUP);
m_cTab.CreateRadioBox(TRUE,"New Contacts Must Match
Connection Password", 6, 0, P_BELOW | P_LEFT, 0,
0, 0);
m_cTab.CreateGroupBox("Sounds",8,1,310,52);
m_cTab.CreateCheckBox(FALSE,"Play Sound When Contact
Comes Online", 9, 1, 0, 10, 15);
m_cTab.CreateCheckBox(FALSE,"Play Sound On New
Messages", 10, 1, P_BELOW | P_LEFT);
CString s;
m_cTab.GetDlgItem(8)->GetWindowText(s);
TRACE("TEST: %s\n",s);
return TRUE;
}
As you can see, inserting controls into your tab control looks very nice and neat, and is easy.
But how do we make pushbuttons call functions you ask? The class sends a message to your dialog
with the WPARAM
containing the uID of the button which was pressed, so you can handle any
button event with a single function, and a simple switch, or if/then statement.
BEGIN_MESSAGE_MAP(COptionsDlg, CDialog)
ON_MESSAGE(WM_BUTTONPRESSED,ButtonPressed)
END_MESSAGE_MAP()
LRESULT COptionsDlg::ButtonPressed(WPARAM w, LPARAM l)
{
CString s;
m_cTab.GetDlgItem((int)w)->GetWindowText(s);
TRACE("BUTTON %d was pressed [%s]\n",w,s);
return 0;
}
Points of Interest
Buttons and Keyboard Navigation were the tricky parts of the class, examining the code will shed some light on how I addressed the issues,
Using PreTranslateMessage
I trapped some specific messages for keyboard and button events, then I reposted them back to the class as a new type of message for processing,
I did this so that a minimal amount of code would appear in the PreTranslateMessage
message function, to prevent slowing down the message pump.
In order to maneuver through child controls without a mouse, you can use the tab button to move between controls in your dialog (make sure your tab control has WS_TABSTOP
style)
Then once you tab to your TabControl
, use the up and down arrow keys on your keyboard to select the child controls, and space bar to activate or deactivate them.
History
Jan 2007, Public Release.