Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

CMyTabCtrl - Very Simple Dynamic Tab Control for MFC

4.59/5 (29 votes)
31 Jan 20074 min read 1   14.3K  
A Very Simple to use and expandable CTabCtrl Class
Sample image

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" //<-- Add here

// COptionsDlg dialog

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:
     //CTabCtrl m_cTab; //<-- comment this out
     CMyTabCtrl m_cTab; //<-- add this

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.

//control creation functions
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);

//placment helpers
int BottomOf(int nID);
int RightOf(int nID);
int LeftOf(int nID);
int TopOf(int nID);

//get value helpers
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();

    //Must call this first to initialize the tab class
    m_cTab.Init();

    m_cTab.InsertItem(0,"Connections");
    m_cTab.InsertItem(1,"Notifications");

    //CONNECTIONS
    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);
    
    //NOTIFICATIONS
    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);    
    
    //Just a simple test to demonstrate how easy it is to 
    //access the controls
    CString s;
    
    //get the text from control number 8
    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here