NOTE: This is an older article, so the information provided may no longer be accurate.

Tab control example - Windows API (Win32/64) [update 4]

tab example A very simple example
Looking for a simple and easy Windows Tab control example? You've found it! :-D

In my earlier days of learning Windows API C / C++ programming, I had a difficult time finding a simple Tab control example, so learning about it was difficult. The MSDN and other documentation were either too complicated, incomplete or just didn't mesh with the way my oddball brain works.

This example is complete and will teach you the very basics of creating a Tab control and how to display content when you click each tab item. If you are looking for an example with 400 tabs, animated backgrounds and 3D effects, so sorry, you won't find it here.

These days, I only use Code::Blocks with MinGW, so the code is tailored for that environment. Visual Studio users can grab the code from the source file contained within the Code::Blocks example file.

If you want the theming effects on Windows XP or newer, you'll need to use an external manifest file (See below for instructions). You could use a manifest embedded in a resource file, but that is beyond the scope of this example.

How the example works
The main program window is created, then a Tab control is created within. Two tab items, 'Tab1' and 'Tab2' are added to the Tab control. Then two Static controls are created within the Tab control, one for each tab item. At first the Tab1 item is automatically selected, so initially we only display the Static control for the Tab1 item.

When you click the Tab2 item, it then becomes the currently selected tab item. In response, the Static control for the Tab1 item is hidden and then the Static control for the Tab2 item is shown. If you then click the Tab1 item, it becomes the currently selected tab item, so the Static control for the Tab2 item is then hidden and the Static control for the Tab1 item is shown. Yes, it's a very basic example, but effective enough for learning.

[Click 'Tab1']
      |
      |--> Hide Static control 2
      |--> Show Static control 1

[Click 'Tab2']
      |
      |--> Hide Static control 1
      |--> Show Static control 2

A note about the Windows API Tab control
Unlike some other languages or GUI toolkits, the Windows API Tab control doesn't contain 'panels' that correspond to each tab item. The Tab control is just a container with a connected area that holds the tab items. In this example, we're simply showing and hiding child controls that correspond to the currently selected tab item. You could certainly create actual container windows within the Tab control to simulate tab panels that contain child controls, then show or hide the container windows based on tab item selection. For simplicity I didn't choose this method for the example. I may update this post again in the future with that type of method.

 

God loves geeks too!
Why Jesus?

Please let me know if you encounter any problems with the example files or the code listed below.

 

Tab Control Example file:
Code::Blocks example (6 KB - zip)
In order for controls theming to to work on Windows XP or newer, you will need to tell your linker about the common controls library, libcomctl32.a for MinGW and ComCtl32.Lib for VC++. If you miss this step, you will probably receive a linker error!
 
Adding comctl32 to CodeBlocks How to add ComCtl32 to Code Blocks:
• Click the 'Project' menu item, then click 'Build Options...'
• Click the 'Linker Settings' tab
• Click the 'Add...' button and type: comctl32
• Click 'Ok' to add, then 'Ok' to accept

Code:
#define _WIN32_IE 0x0500

#include <windows.h>
#include <commctrl.h>

/*
    *******************************************************
    You will need to include the common controls library
    in your programming environment, or controls theming
    will not work and you will probably receive a linker
    error.

    On MinGW, the library is called:
    libcomctl32.a in the 'lib' folder.  In VC++ 2005/2008
    it's: ComCtl32.Lib
    *******************************************************
*/

/*
    declare global variables
*/
HWND hMainWindow = NULL; // main window
HWND hTab = NULL;        // our tab control
HWND hTabView1 = NULL;   // view window for tab1
HWND hTabView2 = NULL;   // view window for tab2

HINSTANCE hiInst = NULL;

/*
    declare procedures
*/
void InitComCtls ();
void CreateTabControl ();
void CreateStaticViewTab1 ();
void CreateStaticViewTab2 ();
void SetDefaultFont (HWND);
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*
    begin main program
*/
INT WINAPI WinMain (
    HINSTANCE hThisInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpszArgument,
    INT nCmdShow
)
{
    MSG messages = {0};      // structure to hold Windows messages
    WNDCLASSEX wincl = {0};  // data structure for the window class

    hiInst = hThisInstance;

    // enable common controls
    InitComCtls();

    // window structure
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = "TabExampleApp";
    wincl.lpfnWndProc = WindowProcedure;     //  procedure called by Windows to handle messages
    wincl.style = CS_DBLCLKS;                // catch double-clicks
    wincl.cbSize = sizeof(WNDCLASSEX);

    // Use default icon and mouse-pointer
    wincl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;  // No menu
    wincl.cbClsExtra = 0;
    wincl.cbWndExtra = 0;
    wincl.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);  // 'normal' background color

    // Register the window class, and if it fails quit the program
    if (!RegisterClassEx(&wincl))
    {
        return GetLastError();
    }

    // the window class is registered, let's create the window
    hMainWindow = CreateWindowEx (
        0,                   // no extended styles
        "TabExampleApp",         // class name
        "Win32 Tab Control Example",   // window's title
        WS_OVERLAPPEDWINDOW,   // default window style
        CW_USEDEFAULT,       // Windows decides the X/Left position
        CW_USEDEFAULT,       // Windows decides the Y/Top position
        640,                 // window width
        480,                 // window height
        HWND_DESKTOP,        // window is child of the Desktop
        NULL,                // no menu
        hThisInstance,       // instance handler
        NULL                 // no extra window creation data
    );

    // create all of our example controls
    CreateTabControl();
    CreateStaticViewTab1();
    CreateStaticViewTab2();

    // make the main window visible on the screen
    ShowWindow(hMainWindow, nCmdShow);

    // run the message loop. It will run until GetMessage() returns 0
    while (GetMessage(&messages, NULL, 0, 0))
    {
        // translate virtual-key messages into character messages
        TranslateMessage(&messages);

        // send message to WindowProcedure
        DispatchMessage(&messages);
    }

    // end program, returning exit code
    return messages.wParam;
}

/*
    initialize common controls
*/
void InitComCtls()
{
    INITCOMMONCONTROLSEX icce = {0};

    icce.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icce.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&icce);
}

/*
    create our tab control
*/
void CreateTabControl()
{
    TCITEM tie = {0};  // tab item structure

    /* create tab control */
    hTab = CreateWindowEx(
        0,                      // extended style
        WC_TABCONTROL,          // tab control constant
        "",                     // text/caption
        WS_CHILD | WS_VISIBLE,  // is a child control, and visible
        5,                      // X position - device units from left
        5,                      // Y position - device units from top
        600,                    // Width - in device units
        400,                    // Height - in device units
        hMainWindow,            // parent window
        NULL,                   // no menu
        hiInst,                 // instance
        NULL                    // no extra junk
    );

    if (hTab == NULL)
    {
        // tab creation failed -
        // are the correct #defines in your header?
        // have you included the common control library?
        MessageBox(NULL, "Tab creation failed", "Tab Example", MB_OK | MB_ICONERROR);
        return;
    }

    // set tab control's font
    SetDefaultFont(hTab);

    /* start adding items to our tab control */

    // set up tab item structure for Tab1
    tie.mask = TCIF_TEXT;  // we're only displaying text in the tabs
    CHAR pszTab1 [] = "Tab1";  // tab1's text  (2-step process necessary to avoid compiler warnings)
    tie.pszText = pszTab1;  // the tab's text/caption

    // attempt to insert Tab1
    if (TabCtrl_InsertItem(hTab, 0, &tie) == -1)
    {
        // couldn't insert tab item
        DestroyWindow(hTab);
        MessageBox(NULL, "Couldn't add Tab1", "Tab Example", MB_OK | MB_ICONERROR);
        return;
    }

    // set up tab item structure for Tab2
    // (reusing same structure, just changing the text)
    CHAR pszTab2 [] = "Tab2";  // tab2's text  (2-step process necessary to avoid compiler warnings)
    tie.pszText = pszTab2;  // the tab's text/caption

    // attempt to insert Tab2
    if (TabCtrl_InsertItem(hTab, 1, &tie) == -1)
    {
        // couldn't insert tab item
        DestroyWindow(hTab);
        MessageBox(NULL, "Couldn't add Tab2", "Tab Example", MB_OK | MB_ICONERROR);
        return;
    }
}

/*
    create a Static control for our View 1
*/
void CreateStaticViewTab1 ()
{
    RECT tr = {0};  // rect structure to hold tab size

    // get the tab size info so
    // we can place the view window
    // in the right place
    TabCtrl_GetItemRect(hTab, 0, &tr);

    // create a Static control for our view window
    hTabView1 = CreateWindowEx(
        0,          // no extended style
        "STATIC",   // Static class name
        "Static Control on Tab1",   // Static control's text
        WS_CHILD | WS_VISIBLE | WS_BORDER | SS_CENTER | SS_CENTERIMAGE,  // control style
        75,         // x position
        75,         // y position
        200,        // control width
        60,         // control height
        hTab,       // parent control
        NULL,       // no menu/ID info
        hiInst,     // instance handler
        NULL        // no extra creation data
    );

    // Set this control's font
    SetDefaultFont(hTabView1);
}

/*
    create a Static control for our View 2
*/
void CreateStaticViewTab2 ()
{
    RECT tr = {0};  // rect structure to hold tab size

    // get the tab size info so
    // we can place the view window
    // in the right place
    TabCtrl_GetItemRect(hTab, 0, &tr);

    // create second Static control for our view window.
    // this control is hidden, so we do NOT include
    // the WS_VISIBLE control style on this control
    hTabView2 = CreateWindowEx(
        0,          // no extended style
        "STATIC",   // Static class name
        "Static Control on Tab2",   // Static control's text
        WS_CHILD | WS_BORDER | SS_CENTER | SS_CENTERIMAGE,  // control style - NOT WS_VISIBLE!!!
        75,        // x position
        (tr.bottom - tr.top) + 150,  // y position
        200,        // control width
        60,         // control height
        hTab,       // parent control
        NULL,       // no menu/ID info
        hiInst,     // instance handler
        NULL        // no extra creation data
    );

    // Set this control's font
    SetDefaultFont(hTabView2);
}

/*
    set a control's font to the default GUI font
    (is a 'little' better than the ugly default font)
*/
void SetDefaultFont (HWND hwnd)
{
    SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), (LPARAM)true);
}

/*
    this function is called by the Windows function DispatchMessage()
*/
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // handle messages based on their ID
    switch (message)
    {
        // tab message is contained in WM_NOTIFY message
        case WM_NOTIFY:
        {
            // get the tab message from lParam
            LPNMHDR lpnmhdr = (LPNMHDR)lParam;

            // if we received the TCN_SELCHANGE message, process it
            // (TCN_SELCHANGE is when the selection changes from
            // one tab item to another)
            if (lpnmhdr->code == TCN_SELCHANGE)
            {
                // get the newly selected tab item
                INT nTabItem = TabCtrl_GetCurSel(hTab);

                // hide and show the appropriate tab view
                // based on which tab item was clicked
                switch (nTabItem)
                {
                    // Tab1 (item 0) was clicked
                    case 0:
                    {
                        ShowWindow(hTabView2, SW_HIDE);  // first hide tab view 2
                        ShowWindow(hTabView1, SW_SHOW);  // then show tab view 1
                        return 0;
                    }
                    break;

                    // Tab2 (item 1) was clicked
                    case 1:
                    {
                        ShowWindow(hTabView1, SW_HIDE);  // first hide tab view 1
                        ShowWindow(hTabView2, SW_SHOW);  // then show tab view 2
                        return 0;
                    }
                    break;

                    default:
                        // don't do anything if the tab item isn't 0 or 1
                        break;
                }
            }
            return 0;
        }
        break;

        // message called when the main window is destroyed
        case WM_DESTROY:
        {
            PostQuitMessage(0);  // send a WM_QUIT to the message queue
        }
        break;

        default:
            // process messages we don't handle
            return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0;
}

Optional theming using a Manifest file
If you would like to use XP and newer theming with this example, make sure you add the Comctl32 library to your project (mentioned above), create a text file and copy/paste the code below into it, then save the file with the same name as your tab example's EXE file but with .Manifest added to the end. So if your example's EXE file-name is: Tab Control Example.exe, you'd save the Manifest file as: Tab Control Example.exe.Manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity name="Tab example" processorArchitecture="x86" version="0.0.2.0" type="win32" />
    <description>Tab Example program</description>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*" />
        </dependentAssembly>
    </dependency>
</assembly>

Update 1: I cleaned up the code below a little to make it easier to understand.

Update 2 [2010-12-03]: Again I cleaned up the code, added some initializers for variables and changed the Static controls a little. Enjoy!

Update 3 [2012-01-19]: I reformatted the HTML of this article and clarified many of the instructions and statements made. Just trying to help folks out the best I can! :-D

Update 4 [2012-03-09]: I manually added syntax highlighting on the code for better readability. If I missed anything or it's showing up strange, please use the Comment link at the top of this article to report it. Also changed the warning about including the Comctl library. Probably changed some other stuff too. :-P

 

Post A Comment

Your name:

Your e-mail address: (Will not be seen or used by anyone else but me)

To help cut down on spam, what do you get when you add two and four?:

Please type your message below: (Please limit message to less than 1,000 characters)

 

My Story   |   Business site   |   TGIF2   |   JWM   |   Openbox   |   Autism