Onglets multicolores (win32 api)

Description

Si vous aviez déjà essayé de mettre un peu de couleur aux onglets d'un TabControl, vous auriez sans doute compris que le seul moyen de le faire est de tout dessiner soi-même. En examinant de près la composition d'un TabControl, on se rend-compte que ce n'est pas du tout compliqué de le dessiner entièrement en lui donnant l'apparence voulue. Il suffit d'utiliser les fonctions API de la GDI. En gros, Voici les cinq étapes à suivre pour dessiner un TabControl standard:
1- Effacer la partie haute derrière les onglets en utilisant une couleur d'arrière-plan.
2- Dessiner tous les onglets non sélectionnés.
3- Remplir le corps du TabControl avec une couleur (celle de l'onglet sélectionné).
4- Dessiner la bordure du corps du TabControl.
5- Dessiner l'onglet sélectionné en écrasant la partie de la bordure juste en dessous.
Dans ce code source, je sous-classe le TabControl afin d'intercepter le message WM_PAINT. Il sera donc entièrement dessiné pendant le traitement de ce message. La couleur de chaque onglet est définie au moment de son ajout grâce à l'utilisation du membre lParam de la structure TCITEM. La couleur d'arrière-plan des onglets est quant à elle mémorisée comme attribut USERDATA du TabControl avec SetWindowLong(). Il s'agit de la couleur de fond de la boîte de dialogue ou la fenêtre mère. Ainsi, elle sera récupérée et utilisée dans la procédure de sous-classement du TabControl. Vous pouvez donc prendre la procédure de sous-classement et l'utiliser directement dans vos programmes. Seuls les TabControls normaux, c'est à dire ceux avec des onglets en haut, sont pris en charge. Si j'avais du temps j'aurais pris en charge tous les autres styles.
Ce projet est réalisé avec Visual C/C++ 2005. Le code n'utilise que les fonctions API, ce qui permet de l'utiliser avec d'autres compilateurs pour Windows.
Pour tester l'exécutable, renommez le en ColorTabControl.exe
Vos remarques et commentaires sont les bienvenus.

Source / Exemple :


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

WNDPROC OldTabProc;

// Fonction de dessin des coins arrondis:
void DrawCorners(HDC hdc, RECT* itemrect, HBRUSH fond)
{
	RECT rect;
	LOGBRUSH lb;
	// Récupérer les infos du HBRUSH de la couleur d'arrière-plan:
	GetObject(fond,sizeof(LOGBRUSH),&lb);
	// Définir le COLORREF récupéré comme couleur d'arrière-plan:
	SetBkColor(hdc, lb.lbColor);

	// Calculer la zone du dessin du coin haut gauche:
	rect.left = itemrect->left;
	rect.top = itemrect->top;
	rect.right = rect.left + 2;
	rect.bottom = rect.top + 2 + 1;
	// Effacer le coin haut gauche:
	ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rect, 0, 0, 0);
	// Dessiner le coin haut gauche:
	rect.top++;
	DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDTOPRIGHT);

	// Calculer la zone du dessin du coin haut droit:
	rect.left = itemrect->right - 2;
	rect.top = itemrect->top;
	rect.right = itemrect->right;
	rect.bottom = rect.top + 2 + 1;
	// Effacer le coin haut droit:
	ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rect, 0, 0, 0);
	// Dessiner le coin haut droit:
	rect.top++;
	DrawEdge(hdc, &rect, EDGE_RAISED, BF_SOFT|BF_DIAGONAL_ENDBOTTOMRIGHT);

	return ;
}

// Procédure de sous-classement du TabControl:
LRESULT CALLBACK TabProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	char buffer[100];
	switch(message)
	{
	case WM_PAINT:
		{
			// Récupérer le style du TabControl:
			LONG style=GetWindowLong(hwnd,GWL_STYLE);
			// Aller directement à la procédure originale si le style n'est pas pris en charge:
			if((style & TCS_VERTICAL) || (style & TCS_BOTTOM) || (style & TCS_BUTTONS)) break; 
			PAINTSTRUCT ps;
			RECT corps,entete,itemrect;
			HDC hdc=BeginPaint(hwnd,&ps);
			// Récupérer les dimenstions du TabControl:
			GetClientRect(hwnd,&corps);
			// Sélectionner la police utilisée par le TabControl:
			HFONT hOldFont = (HFONT)SelectObject (hdc, (HFONT)SendMessage(hwnd,WM_GETFONT,0,0));
			// Récupérer les dimensions de cette police:
			TEXTMETRIC fontMetrics;
			GetTextMetrics(hdc, &fontMetrics);
			// Récupérer le nombre de lignes des onglets:
			int nbrlignes=SendMessage(hwnd,TCM_GETROWCOUNT,0,0);
			// Définir les dimenstions de l'en-tête du TabControl:
			CopyRect(&entete,&corps);
			entete.bottom=(fontMetrics.tmHeight + 5)*nbrlignes+2;//(2=taille bordure de l'onglet, 5= décalage vertical du texte)
			//Récupérer le HBRUSH de la couleur d'arrière plan des onglets:
			HBRUSH couleurfond=(HBRUSH)GetWindowLong(hwnd,GWL_USERDATA);
			// Remplir l'arrière-plan des onglets avec la couleur de fond:
			FillRect((HDC)hdc, &entete,couleurfond );
			// Définir la position verticale du corps du TabControl:
			corps.top=entete.bottom;
			// Déclarer et initialiser une structure TCITEM:
			TCITEM tci;
			tci.mask=TCIF_TEXT | TCIF_PARAM;
			tci.pszText=buffer;
			tci.cchTextMax=100;
			// Récupérer l'index de l'onglet sélectionné:
			int curitem=SendMessage(hwnd,TCM_GETCURSEL,0,0);
			// Récupérer le nombre total des onglets du TabControl:
			int count=SendMessage(hwnd,TCM_GETITEMCOUNT,0,0);
			// Dessiner tous les onglets non sélectionnés:
			int i;
			for(i=0; i<count; i++)
			{
				// Eviter de dessiner l'onglet sélectionné:
				if(i==curitem)continue;
				// Récupérer la position et les dimensions de l'onglet:
				SendMessage(hwnd,TCM_GETITEMRECT,i,(LPARAM)&itemrect);
				// Eviter de dessiner les onglets  invisibles:
				if(itemrect.left<0 || itemrect.left>entete.right) continue;
				// Récupérer les données de l'onglet:
				SendMessage(hwnd,TCM_GETITEM,i,(LPARAM)&tci);
				// Définir la couleur de fond de l'onglet à partir de la valeur stockée dans tci.lParam:
				SetBkColor(hdc,(COLORREF)tci.lParam);
				// Desssiner le fond de l'onglet et afficher son texte:
				ExtTextOut(hdc,itemrect.left+6, itemrect.top+3, ETO_OPAQUE | ETO_CLIPPED, &itemrect, buffer, lstrlen(buffer), 0);
				// Déssiner les contours de l'onglet:
				DrawEdge(hdc, &itemrect, EDGE_RAISED, BF_SOFT | BF_RIGHT | BF_TOP | BF_LEFT);
				// Arrondir les coins supérieur gauche et supérieur droit:
				DrawCorners(hdc,&itemrect,couleurfond);
			}
			// Récupérer la position et les dimensions de l'onglet sélectionné: 
			SendMessage(hwnd,TCM_GETITEMRECT,curitem,(LPARAM)&itemrect);
			// S'assurer qu'un onglet est sélectionné et qu'il est visible:
			if(curitem>=0 && itemrect.left>=0 && itemrect.left<entete.right)
			{
				// Récupérer les données de l'onglet sélectionné:
				SendMessage(hwnd,TCM_GETITEM,curitem,(LPARAM)&tci);
				// Définir la couleur de fond de l'onglet à partir de la valeur stockée dans tci.lParam:
				SetBkColor(hdc,(COLORREF)tci.lParam);
				// Remplir le corps du TabControl avec la couleur de l'onglet sélectionné:
				ExtTextOut(hdc,0,0,ETO_OPAQUE,&corps,0,0,0);
				// Dessiner la bordure du corps du TabControl:
				DrawEdge(hdc, &corps, EDGE_RAISED, BF_SOFT | BF_RECT);
				// Calculer le rectangle de dessin de l'onglet sélectionné:
				itemrect.top = itemrect.top-2;
				itemrect.left = itemrect.left-2;
				itemrect.right = itemrect.right+2;
				itemrect.bottom=entete.bottom+2;
				// Dessiner le fond de l'onglet sélectionné et afficher son texte:
				ExtTextOut(hdc,itemrect.left+8, itemrect.top+3, ETO_OPAQUE | ETO_CLIPPED, &itemrect, buffer, lstrlen(buffer), 0);
				// Déssiner les contours de l'onglet sélectionné:
				DrawEdge(hdc, &itemrect, EDGE_RAISED, BF_SOFT | BF_LEFT | BF_TOP | BF_RIGHT);
				// Arrondir les coins supérieur gauche et supérieur droit:
				DrawCorners(hdc,&itemrect,couleurfond);
			}
			// Sélectionner la police originale:
			SelectObject (hdc, hOldFont);
			EndPaint(hwnd,&ps);
			return 0;
		}

	default:
		break;
	}
	// Appeler la procédure originale:
	return CallWindowProc(OldTabProc, hwnd, message, wParam, lParam);
}

BOOL CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hbquitter, htab, hstitre,hsauteur,hsdate,hsdescription;
	static HBRUSH couleurfond;

	switch(message)
	{

	case WM_INITDIALOG:
		// Définir le titre de la boîte de dialogue:
		SetWindowText(hwnd,"Onglets multicolores");
		// Créer le HBRUSH de la couleur de fond de la boîte:
		couleurfond=CreateSolidBrush(RGB(0,180,240));
		// Créer notre TabContriol:
		htab = CreateWindowEx(0,WC_TABCONTROL, 0,  WS_CHILD  | WS_VISIBLE, 20, 20, 400, 200, hwnd,  0, 0, 0);
		//Sous-classer le TabControl:
		OldTabProc=(WNDPROC) SetWindowLong(htab, GWL_WNDPROC, (LONG)TabProc);
		// Ajouter des onglets à notre TabControl. La couleur est stockée dans le membre lParam de la structure TCITEM:
		TCITEM tie;
		tie.mask = TCIF_TEXT | TCIF_PARAM;
		tie.pszText = "Titre";
		tie.lParam=RGB(255,135,135);//Rouge
		SendMessage(htab,TCM_INSERTITEM,0,(LPARAM)&tie);
		tie.pszText = "Auteur";
		tie.lParam=RGB(125,255,125);//Vert
		SendMessage(htab,TCM_INSERTITEM,1,(LPARAM)&tie);
		tie.pszText = "Date";
		tie.lParam=RGB(245,245,140);//Jaune
		SendMessage(htab,TCM_INSERTITEM,2,(LPARAM)&tie);
		tie.pszText = "Description";
		tie.lParam=RGB(255,190,130);//Orange
		SendMessage(htab,TCM_INSERTITEM,3,(LPARAM)&tie);
		// Créer les Statics et le bouton "Quitter":
		hstitre=CreateWindowEx(0,"static","Onglets multicolores",  WS_CHILD | SS_CENTER,40,120,360,20,hwnd,0,0,0);
		hsauteur=CreateWindowEx(0,"static","RACPP  pour le site:  www.cppfrance.com",  WS_CHILD | SS_CENTER,40,120,360,20,hwnd,0,0,0);
		hsdate=CreateWindowEx(0,"static","Déposé le 25 août 2008",  WS_CHILD | SS_CENTER,40,120,360,20,hwnd,0,0,0);
		hsdescription=CreateWindowEx(0,"static","Ce petit programme démontre l'utilisation\nd'un TabControl ayant des\nonglets de différentes couleurs.",  WS_CHILD | SS_CENTER,40,100,360,54,hwnd,0,0,0);
		hbquitter=CreateWindowEx(0,"button","Quitter", WS_CHILD | WS_VISIBLE,180,240,80,24,hwnd,0,0,0);
		// Stocker le HBRUSH de couleur de fond pour l'utiliser dans la procédure de sous-classement du TabControl:
		SetWindowLong(htab,GWL_USERDATA,(LONG)couleurfond);
		// Forcer la sélection du premier onglet:
		NMHDR nmhdr;
		nmhdr.code=TCN_SELCHANGE;
		nmhdr.hwndFrom=htab;
		SendMessage(hwnd,WM_NOTIFY,0,(LPARAM)&nmhdr);
		return 0;

	case WM_CTLCOLORDLG:
		// Définir la couleur de fond de notre boîte de dialogue:
		return (BOOL)couleurfond;

	case WM_CTLCOLORSTATIC:
		// Assurer la transparence des Statics:
		SetBkMode((HDC)wParam,TRANSPARENT);
		return (BOOL) GetStockObject(NULL_BRUSH);

    case WM_NOTIFY:
        NMHDR* pnmh;
		pnmh = (NMHDR*)lParam;
		// S'assurer qu'il s'agit d'un changement d'onglet:
 		if(pnmh->hwndFrom == htab && pnmh->code == TCN_SELCHANGE)
		{
			// Forcer le redessin du TabControl:
			InvalidateRect(htab,0,0);
			// Obtenir l'index de l'onglet sélectionné:
			int cursel=SendMessage(htab,TCM_GETCURSEL,0,0);
			// N'afficher que le Static correspondant à l'onglet sélectionné:
			ShowWindow(hstitre,cursel==0);
			ShowWindow(hsauteur,cursel==1);
			ShowWindow(hsdate,cursel==2);
			ShowWindow(hsdescription,cursel==3);
			return 0;
		}
		break;

	case WM_COMMAND:
		// Envoyer le message de fermeture à notre boîte de dialogue si le bouton "Quitter" est cliqué:
		if((HWND)lParam==hbquitter)SendMessage(hwnd,WM_CLOSE,0,0);
		break;

	case WM_CLOSE:
		// Détruire le HBRUSH de la couleur de fond:
		DeleteObject(couleurfond);
		// Fermer la boîte de dialogue:
		EndDialog(hwnd,0);
		return 0;

	default:
		break;
	}
	return 0;
}

int APIENTRY WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmd, int show)
{
	// Charger La DLL pour les Common Controls:
	HINSTANCE hCmLib= LoadLibrary("comctl32.dll");
	// Déclarer et allouer de la mémoire pour notre dialog template:
	LPDLGTEMPLATE lpdt = ( LPDLGTEMPLATE) GlobalAlloc(GPTR, 512);  
	// Définir les styles de la boite de dialogue:
	lpdt->style = DS_CENTER | WS_DLGFRAME | WS_SYSMENU | WS_MINIMIZEBOX  |WS_CAPTION ; 
	lpdt->dwExtendedStyle=0;
	// Obtenir les unités d'affichage des boites de dialogue:
	long baseunits=GetDialogBaseUnits();
	// Définir la largeur et la hauteur de la boite de dialogue:
	lpdt->cx = MulDiv(440, 4,LOWORD(baseunits)); 
	lpdt->cy = MulDiv(280, 8, HIWORD(baseunits));;         
	//Lancer la boite de dialogue
	DialogBoxIndirectParam(GetModuleHandle(0),lpdt,0,(DLGPROC)DlgProc,0);
	//Libération de la mémoire allouée:
	GlobalFree((HGLOBAL)lpdt);
	// Libérer la librairie comctl32.dll:
	FreeLibrary(hCmLib);
	return 0;
}

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.