BrainOut!
The mumblings of a Christian autistic husband, dad, IT guy and amateur radio operator - Will Brokenbourgh / AF7EC
Tab control example - Windows API (Win32/64) [update 4]
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.
Please let me know if you encounter any problems with the example files or the code listed below.
libcomctl32.a
for MinGW
and ComCtl32.Lib
for VC++. If you miss this step, you will probably
receive a linker error!
• 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
#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