IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

La programmation de l'interface utilisateur

Date de publication : 22 mai 2008. Date de mise à jour : 23 mars 2009.


I-C. Les contrôles communs
I-C-1. Introduction
I-C-2. Initialisation
I-C-3. Les versions
I-C-4. Choix des bibliothèques
I-C-5. Exemple : Le contrôle ListView


I-C. Les contrôles communs


I-C-1. Introduction

Les contrôles communs sont les contrôles spécifiques d'une version donnée de Windows (barre d'outils, barre d'état, barre de progression, glissière, etc.). Ils sont implémentés dans le fichier comctl32.dll. Pour les utiliser, il faut se lier avec comctl32.lib et inclure commctrl.h.


I-C-2. Initialisation

Il ne nous est plus à rappeler que la création d'une fenêtre se fait toujours à partir d'une classe de fenêtre existante. Lorsqu'un programme se lie avec user32.dll, le système enregistre les classes de fenêtre standard (les contrôles standard) avant même que la fonction WinMain ne soit appelée. C'est pour cette raison que nous n'avons jamais eu à enregistrer ces classes nous-mêmes. Ce n'est pas le cas avec les contrôles communs et l'application doit donc initialiser ces contrôles elle-même avant de pouvoir les utiliser. Cela se fait tout simplement en appelant la fonction InitCommonControlsEx (qui est venue remplacer la fonction InitCommonControls, devenue obsolète).
void InitCommonControls(VOID);
BOOL InitCommonControlsEx(LPINITCOMMONCONTROLSEX lpInitCommCtrls);
Le type INITCOMMONCONTROLSEX est tout simplement une structure comportant deux champs : dwSize et dwICC. Le premier doit indiquer la taille en octets de la structure et le dernier la ou les classes de fenêtre qu'on veut enregistrer (nous y reviendrons là-dessus un peu plus bas). La fonction InitCommonControls ne permet pas de spécifier individuellement les contrôles à initialiser.


I-C-3. Les versions

Le fichier comctl32.dll n'est pas forcément le même sur tous les ordinateurs car sa version dépend du système d'exploitation et/ou logiciels installés (en particulier Internet Explorer). Il n'est pas exclu non plus que plusieurs versions soient simultanément présentes sur un même ordinateur (évidemment séparées dans des répertoires différents). Par exemple, Windows XP et Vista sont accompagnés des versions 5 (en l'occurrence 5.82) et 6 alors que Windows 2000 n'était accompagné que de la version 5 (5.81). Si vous avez Windows 95, vous avez la version 4.0 mais si vous installez ensuite Internet Explorer 3.x, vous bénéficierez de la version 4.70. Chaque nouvelle version est un sur ensemble des versions antérieures.

La version 6 intègre non seulement les contrôles supplémentaires mais aussi les contrôles standard, cependant ces derniers ne sont pas les mêmes que ceux de user32 (mais ils continuent à envoyer leurs notifications via le message WM_COMMAND, et non WM_NOTIFY comme le reste des contrôles communs). En effet, Windows XP est venu avec une nouvelle interface utilisateur personnalisable, permettant à l'utilisateur de choisir luimême le style visuel à appliquer. Les contrôles de la version 6 utilisent ce style alors que ceux de user32 et des versions antérieures à 6 sont basés sur le style classique. Ainsi, si vous voulez utiliser les styles visuels dans vos applications, vous devez utiliser les contrôles de la version 6 ou plus récente. Nous en reparlerons plus tard.

En outre, il faut également savoir que la fonction InitCommonControlsEx n'a été introduite que depuis la version 4.70 (livrée avec IE 3.x). Selon la version de votre fichier d'en-tête, il se peut donc que cette fonction ne soit déclarée que si vous définissez explicitement la macro _WIN32_IE à 0x0300 ou supérieur avant d'inclure commctrl.h (sous Visual Studio .NET, elle vaut par défaut 0x0500).


I-C-4. Choix des bibliothèques

Comme nous venons tout juste de le dire, la version 6.0 de la Common Controls Library est la première à avoir supporté les styles visuels. Or les applications compilées avec Visual Studio .NET et 2005 utilisent par défaut les contrôles de user32.dll et de comctl32.dll version 5 pour être compatibles avec les anciennes versions de Windows. Pour utiliser la version 6 (ou une autre ...), c'est à l'application de le spécifier. En effet, quand on se lie avec comctl32.lib, il n'est spécifié nulle part quelle version de comctl32.dll veut-on utiliser. C'est là qu'interviennent les fichiers MANIFEST.

Un fichier manifest est un fichier texte, utilisant une grammaire XML, permettant dans Windows XP et plus récents (en effet il est ignoré par les versions antérieures) de spécifier entre autres les composants (les « dépendances ») requis par une application pour fonctionner. Le nom d'un fichier manifest doit être le nom de l'application suivi de l'extension .manifest (par exemple hello.exe.manifest). Par exemple, pour utiliser comctl32.dll version 6.0, il faut créer le manifest suivant :
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>

<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type='Win32'
                              name='Microsoft.Windows.Common-Controls'
                              version='6.0.0.0'
                              processorArchitecture='x86'
                              publicKeyToken='6595b64144ccf1df'
            />
        </dependentAssembly>
    </dependency>
</assembly>
Les paramètres sont plutôt parlants.

De plus, si vous utilisez par exemple Visual Studio 2005, vos applications utiliseront la version 8.0 du C Run-Time Library (msvcr80.dll), sauf bien sûr si vous utilisez la version statique. Dans ce cas, il faut également le spécifier dans le manifest. Donc si vous utilisez msvcr80.dll et comctl32.dll version 6.0, vous devez créez le manifest suivant :
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>

<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>

    <!-- Microsoft C Run-Time Library version 8.0 -->
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type='win32'
                              name='Microsoft.VC80.CRT'
                              version='8.0.50608.0'
                              processorArchitecture='x86'
                              publicKeyToken='1fc8b3b9a1e18e3b'
            />
        </dependentAssembly>
    </dependency>

    <!-- Microsoft Common Controls Library version 6.0 -->
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type='Win32'
                              name='Microsoft.Windows.Common-Controls'
                              version='6.0.0.0'
                              processorArchitecture='x86'
                              publicKeyToken='6595b64144ccf1df'
            />
        </dependentAssembly>
    </dependency>

</assembly>
En Debug vous mettrez plutôt VC80.DebugCRT à la place de VC80.CRT.

Il existe deux manières différentes d'utiliser un manifest (dans tous les cas, n'oubliez pas que le manifest n'est lu qu'à l'exécution) :

  • En le plaçant dans le même répertoire que l'exécutable
  • En l'embarquant à l'intérieur même de l'exécutable, autrement dit le mettre en ressource.

Lorsqu'on place un manifest dans le répertoire de l'exécutable, celui qui se trouve en ressource sera tout simplement ignoré par Windows. Sachez également que lorsqu'un manifest est utilisé, il n'est plus nécessaire d'appeler la fonction InitCommonControlsEx().

En fait, Visual Studio .NET et plus récents génèrent automatiquement un manifest, ne serait-ce que pour spécifier la version du CRT utilisée. Le fichier final, créé à partir d'un fichier "intermédiaire" nommé votreapp.exe.intermediate.manifest et des éventuelles options de génération que vous avez spécifiées, nommé votreapp.exe.embed.manifest, est ensuite embarqué dans l'exécutable de sorte que ce dernier soit plus ou moins autonome. Si vous voulez néanmoins l'utiliser en tant que fichier à part, accompagnant votre exécutable, vous n'avez qu'à renommer le fichier votreapp.exe.embed.manifest en votreapp.exe.manifest ou, tout simplement (et plus proprement !), de dire à Visual Studio de ne pas embarquer le manifest.

Visual Studio dispose d'une interface simple et intuitive permettant de contrôler très simplement et efficacement la génération de manifest. Vous pouvez par exemple spécifier des fichiers à fusionner avec le fichier par défaut utilisé pour générer le manifest grâce à l'option Additional manifest Files du gestionnaire de fichiers manifest (donc dans vos fichiers, vous ne devez plus lister la CRT parmi les dépendances requis car elle est déjà listée dans le fichier par défaut (votreapp.exe.intermediate.manifest)).

Ceci étant, voyons maintenant comment embarquer un manifest à l'intérieur de l'exécutable sans utiliser l'interface de Visual Studio. Et bien, pour mettre un manifest dans la section ressources de votre fichier exécutable, il suffit de créer un script de ressource contenant la ligne suivante (inclure windows.h) :
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "hello.exe.manifest"

I-C-5. Exemple : Le contrôle ListView

Le contrôle ListView (WC_LISTVIEW) est un contrôle permettant, comme son nom l'indique, d'afficher une liste. La zone d'affichage des dossiers et des fichiers dans l'explorateur Windows, la zone d'affichage des processus en cours d'exécution dans le gestionnaire des tâches, etc. sont par exemple des contrôles ListView.

Le contrôle ListView peut afficher les éléments de la liste de 4 façons différentes dont voici les plus utilisées :

  • Affichage en icônes (style LVS_ICON) : Les éléments sont affichés avec des grandes icônes avec leur en dessous de l'icône.
  • Affichage en liste (style LVS_LIST) : Les éléments sont affichés en liste, organisée en colonnes, avec des petites icônes avec leur nom à droite de l'icône.
  • Affichage en liste détaillée (style LVS_REPORT) : Chaque élément occupe une ligne et est chaque ligne comporte une ou plusieurs colonnes. La colonne la plus à gauche affiche l'icône et nom de l'élément (placé à droite de l'icône). Les autres colonnes servent à afficher d'autres informations. Chaque colonne est pourvu d'un en-tête qui sert à afficher son nom sauf si le style LVS_NOCOLUMNHEADER a été spécifié.

Depuis Windows XP (plus précisément comctl32.dll version 6.0), il est également possible d'utiliser d'autres styles d'affichage comme l'affichage en mosaïques (messages en jeu : LVM_SETVIEW, LVM_SETTILEVIEWINFO et LVM_SETTILEINFO) ou par groupe (messages en jeu : LVM_ENABLEGROUP, LVM_SETGROUPINFO et LVM_SETGROUPMETRICS) par exemple.

Comme la plupart des contrôles communs, le contrôle ListView envoie ses notifications à la fenêtre parent via le message WM_NOTIFY avec dans wParam son identifiant (ID), comme le fait n'importe quel contrôle commun, et dans lParam un pointeur vers une structure dérivée de la structure de base NMHDR. En fait, cette dérivation (spécialisation) de la structure NMHDR est utilisée par la quasi-totalité des contrôles communs. Il faut lire la documentation du contrôle pour savoir quelle structure est utilisée dans quelles circonstances.

L'exemple suivant a pour but de vous aider à comprendre le fonctionnement des contrôles communs à travers la création et l'utilisation d'un contrôle ListView.
#include <windows.h>
#include <commctrl.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct);
void lvInitColumns(HWND hwndLV);
void lvInsertItems(HWND hwndLV);
void OnNotify(HWND hwnd, LPNMHDR lpnmhdr);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    INITCOMMONCONTROLSEX icc;
    WNDCLASS wc;
    HWND hWnd;
    MSG msg;
    
    icc.dwSize = sizeof(icc);
    icc.dwICC = ICC_LISTVIEW_CLASSES;
    
    InitCommonControlsEx(&icc);
    
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = 0;
    wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance      = hInstance;
    wc.lpfnWndProc    = WndProc;
    wc.lpszClassName  = "Classe 1";
    wc.lpszMenuName   = NULL;
    wc.style          = CS_HREDRAW | CS_VREDRAW;

    RegisterClass(&wc);

    hWnd = CreateWindow( "Classe 1", "Contrôle ListView",
        WS_POPUP | WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
        300, 150, 400, 200,
        NULL, NULL, hInstance, NULL
    );

    ShowWindow(hWnd, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
    case WM_CREATE:
        OnCreate(hwnd, (LPCREATESTRUCT)lParam);
        break;

    case WM_NOTIFY:
        OnNotify(hwnd, (LPNMHDR)lParam);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0L;
}

void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
    HWND hwndLV;
    RECT r;

    GetClientRect(hwnd, &r); 

    hwndLV = CreateWindow( WC_LISTVIEW, "", WS_CHILD | WS_VISIBLE | LVS_REPORT,
        r.left, r.top, r.right, r.bottom,
        hwnd, (HMENU)1, lpCreateStruct->hInstance, NULL
    );

    lvInitColumns(hwndLV);
    lvInsertItems(hwndLV);
}

void lvInitColumns(HWND hwndLV)
{
    LVCOLUMN lvc;

    /* Paramètres communs à toutes les colonnes. */

    lvc.mask = LVCF_TEXT | LVCF_FMT;
    lvc.fmt = LVCFMT_LEFT;

    /* Colonne 0 (première colonne). */

    lvc.pszText = "Langage";
    ListView_InsertColumn(hwndLV, 0, &lvc);
    ListView_SetColumnWidth(hwndLV, 0, 100);

    /* Colonne 1 (deuxième colonne). */

    lvc.pszText = "Créateur";
    ListView_InsertColumn(hwndLV, 1, &lvc);
    ListView_SetColumnWidth(hwndLV, 1, 200);
}

void lvInsertItems(HWND hwndLV)
{
    LVITEM lvi;

    /* Paramètres communs à tous les éléments que nous allons insérer. */

    lvi.mask = LVIF_TEXT;

    /* Ligne 0 (première ligne). */

    lvi.iItem = 0;

    /* Ligne 0 - Colonne 0. */
    lvi.iSubItem = 0;
    lvi.pszText = "C";
    ListView_InsertItem(hwndLV, &lvi);
    
    /* Ligne 0 - Colonne 1. */
    lvi.iSubItem = 1;
    lvi.pszText = "Brian Kernighan & Denis Ritchie";
    ListView_SetItem(hwndLV, &lvi);
}

void OnNotify(HWND hwnd, LPNMHDR lpnmhdr)
{
    if (lpnmhdr->idFrom == 1) /* 1 est l'ID de notre ListView. */
    {
        HWND hwndLV = lpnmhdr->hwndFrom;

        if (lpnmhdr->code == NM_DBLCLK)
        {
            /* ComCtl32 version 4.71 et plus récents : Quand lpnmhdr->code vaut NM_DBLCLK, */
            /* la structure complète est une structure de type NMITEMACTIVATE.             */
            /* Cette structure contient entre autres les coordonnées du point du clic.     */

            LPNMITEMACTIVATE lpnmia = (LPNMITEMACTIVATE)lpnmhdr;
            LVHITTESTINFO lvhti; /* Contiendra le numéro de ligne et de colonne de la "cellule" double-cliquée. */
            int ret;

            /* Récupérons le numéro de ligne et de colonne de la cellule double-cliquée. */

            lvhti.pt = lpnmia->ptAction; /* Coordonnées du point du clic. */
            ret = ListView_SubItemHitTest(hwndLV, &lvhti);

            /* Il faut tester ret car le clic a pu avoir lieu hors d'une cellule ... */

            if (ret != -1)
            {
                /* Récupérons puis affichons le texte de la cellule. */

                LVITEM lvi;
                char lpBuffer[256];

                lvi.mask = LVIF_TEXT;
                lvi.pszText = lpBuffer;
                lvi.cchTextMax = sizeof(lpBuffer);
                lvi.iItem = lvhti.iItem;
                lvi.iSubItem = lvhti.iSubItem;
                ListView_GetItem(hwndLV, &lvi);

                /* Il n'est pas garanti que le texte ait réellement été placé dans lpBuffer.  */
                /* Il est possible que le système ait utilisé un autre buffer.                */
                /* Il faut donc toujours utiliser lvi.pszText pour récupérer le texte.        */
                /* Il y a aussi la macro ListView_GetItemText qui est plus simple à utiliser. */

                MessageBox(hwnd, lvi.pszText, "", MB_OK);
            }
        }
    }
}
 

Valid XHTML 1.0 TransitionalValid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2008 Melem. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.