Listview avec un watermark

Il existe quelques dossiers spéciaux dans Windows XP qui possèdent un watermark, aussi appelé filigrane en français, en bas à droite. Voici l’exemple du dossier Ma musique:
Listview with a watermark
Le but de cet article est de vous aider à faire la même chose dans un TListview sous C++Builder. Pour que l’effet soit réussi, je vous suggère d’utiliser un fichier de type PNG ou icône car ces deux formats permettent l’utilisation du canal alpha. Si vous décidez d’utiliser le format PNG, je vous conseille de jeter un coup d’œil à mon article qui explique comment charger une image dans les ressources. Parce qu’il est plus simple de faire un code qui charge l’icône de l’application, c’est ce format que nous utiliserons.

La première étape est bien sûr de créer un nouveau projet auquel on ajoute un contrôle TListView. Vous pouvez y ajouter quelques TListColumns et TListItems pour faire un peu plus réel.

Étant donné que j’ai décidé d’utiliser un auto_ptr, il faut inclure l’entête suivant dans votre fichier cpp.

#include <memory>

Dans le constructeur de la Form, ajoutez le code suivant:

    static std::auto_ptr<Graphics::TBitmap> Bitmap(new Graphics::TBitmap);
    Bitmap->Canvas->Brush->Color = ListView1->Color;
    Bitmap->SetSize(32, 32); // Le casque romain est seulement en 32x32 pixels
    Bitmap->Canvas->Draw(0, 0, Application->Icon);

    LVBKIMAGE lv;
    lv.ulFlags = LVBKIF_TYPE_WATERMARK;
    lv.hbm = Bitmap->Handle;
    ListView_SetBkImage(ListView1->Handle, &lv);
    ListView_SetTextBkColor(ListView1->Handle, CLR_NONE);

La première ligne sert à créer une image de type Bitmap. Ce format est nécessaire car la fonction qui va être utilisée plus tard nécessite comme paramètre un HBITMAP. Le seconde ligne va servir à définir la couleur de fond de l’image. Si on effectue cette tâche avant d’établir la grandeur de l’image, l’image sera alors automatiquement de cette couleur. Cela évite d’avoir à dessiner un rectangle de la grandeur du Bitmap par la suite. À la troisième ligne, on utilise une grandeur de 32 par 32 pixels, car c’est la grandeur de l’icône de l’application. L’image par défaut dans C++Builder 2010 est un casque romain. La ligne d’après est utilisée pour dessiner l’icône sur le Bitmap. Maintenant, l’image est prête à être utilisée.

Les lignes 6 à 8 servent à remplir une structure de type LVBKIMAGE qui sera utilisée par la fonction ListView_SetBkImage à la ligne 9.

On pourrait penser que l’on a terminé, eh bien non. Il reste un petit tour de passe nécessaire pour voir notre image dans la listview.

Dans votre fichier d’en-tête, ajoutez les déclarations suivantes dans la section private:

    void __fastcall ListViewWndProc(Messages::TMessage &Message);
    Classes::TWndMethod OldListViewProc;

Ensuite, on retourne dans le constructeur de la Form pour ajouter ce code qui sert à rediriger les appels de Windows à la listview vers la méthode ListViewWndProc:

OldListViewProc = ListView1->WindowProc;
ListView1->WindowProc = ListViewWndProc;

Il faut par la suite ajouter cette méthode:

void __fastcall TForm1::ListViewWndProc(Messages::TMessage &Message)
{
    if(Message.Msg == WM_ERASEBKGND)
    {
        ListView1->DefaultHandler(&Message);
        return;
    }
    OldListViewProc(Message);
}

Si ce code n’est pas utilisé, l’événement WMEraseBkgnd de la classe TWinControl, l’ancêtre de TListView, sera appelé. Cette méthode empêche le message WM_ERASEBKGND de se rendre à la procédure par défaut. C’est pourquoi la méthode DefaultHandler de la listview est appelée et que l’on interdit un appel à OldListViewProc si on reçoit ce message.

À présent, vous avez tout le code nécessaire pour faire votre propre watermark dans un contrôle de type listview.

Texte dans une liste vide

Voici un article qui explique comment ajouter un texte dans la liste vide d’un de vos programmes à l’aide de C++Builder.

Dans certaines applications, quand il y a une liste vide (ce qui pourrait laisser croire à une erreur), on ajoute un message au lieu de la laisser ainsi. C’est le cas d’Outlook Express, on peut le voir sur la capture d’écran qui suit.
Outlook Express avec une liste vide
Il existe plusieurs façons d’imiter ce comportement. Mais tout comme Outlook, je voulais que mon texte soit centré et que le redimensionnement des colonnes n’ait aucune influence sur la position du texte.

La première étape est d’ajouter un TListView à votre fenêtre principale. Utilisez le style vsReport pour que l’on puisse voir des colonnes. Pour tester le redimensionnement de la liste, vous pouvez mettre la propriété Align à alClient. Pour tester le redimensionnement des colonnes, il serait préférable d’en ajouter quelques-unes.

Pour capturer les messages qui sont envoyés au contrôle, il faut rediriger la propriété WindowProc vers votre code. Tous les objets qui héritent de TControl possèdent cette propriété. Dans votre fichier d’en-tête, ajoutez les déclarations suivantes dans la section private:

    void __fastcall ListViewWndProc(Messages::TMessage &Message);
    Classes::TWndMethod OldListViewProc;

Ensuite, on met ce code dans le constructeur de la Form:

    OldListViewProc = ListView1->WindowProc;
    ListView1->WindowProc = ListViewWndProc;

Il ne reste plus que le gros morceau maintenant:

void __fastcall TForm1::ListViewWndProc(Messages::TMessage &Message)
{
    static int PrevWidth = 65535; // On met un gros nombre

    if(Message.Msg == WM_PAINT && ListView1->Items->Count <= 0)
    {
        PAINTSTRUCT PaintStruct;
        BeginPaint(ListView1->Handle, &PaintStruct);

        ListView1->Canvas->Font->Color = clWindowText;
        if(ListView1->Enabled)
            ListView1->Canvas->Brush->Color = clWindow;
        else
            ListView1->Canvas->Brush->Color = clBtnFace;
        ListView1->Canvas->FillRect(ListView1->ClientRect);

        RECT rcH;
        int HeaderHeight;
        if(GetWindowRect(ListView_GetHeader(ListView1->Handle), &rcH))
        {    // On ajoute un petit décalage à la hauteur de l'entête
            HeaderHeight = (rcH.bottom - rcH.top) + 10;
        }

        String Temp = "La liste est vide, faites quelque chose pour la remplir.";
        TRect MyRect = ListView1->ClientRect;
        MyRect.Top = HeaderHeight;
        ListView1->Canvas->TextRect(MyRect, Temp, Graphics::TTextFormat() <<
            tfCenter << tfWordBreak << tfNoPrefix << tfNoClip);

        EndPaint(ListView1->Handle, &PaintStruct);
        Message.Result = 0; // On retourne 0 car WM_PAINT à été traité
        return;
    }
    else if(Message.Msg == WM_SIZE && ListView1->Items->Count <= 0)
    {
        if(Message.LParamLo < PrevWidth)
        {   // Quand la largeur de la liste diminue WM_PAINT n'est pas appelé, donc on le fait
            ListView1->Invalidate();
        }
        PrevWidth = Message.LParamLo; // On enregistre la nouvelle largeur
        Message.Result = 0; // On retourne 0 car WM_SIZE à été traité
    }
    OldListViewProc(Message);
}

La première partie du code sert à intercepter le message WM_PAINT dans le but de dessiner la liste. WParam et LParam ne sont pas utilisés pour ce message.

Le texte sera toujours de la couleur clWindowText et l’arrière plan, un rectangle dessiné par FillRect, sera clWindow ou clBtnFace dépendant si le contrôle est activé ou désactivé. Servez-vous des fonctions GetWindowRect et ListView_GetHeader pour aller chercher la hauteur de l’en-tête de liste à laquelle il est préférable d’ajouter un décalage de 10 pixels pour rendre le texte plus apparent. Ensuite, on dessine le texte avec TextRect qui permet de centrer le texte dans le rectangle qui correspond à la taille de la zone client du contrôle.

Dans la deuxième partie, on intercepte le message WM_SIZE car  il faut redessiner le contrôle lorsqu’il rapetisse. Le mot inférieur de LParam, LParamLo, correspond à la largeur de la zone client du contrôle. LParamHi contient la hauteur mais vous n’en aurez pas besoin.

Vous pouvez tenter de compiler le code sans cette section et diminuer la largeur de la liste. Vous remarquerez que le message WM_PAINT n’est pas appelé et donc le texte ne se recentre pas. Je me sers d’une variable statique nommée PrevWidth pour enregistrer la dernière largeur de la liste. Si on avait voulu centrer le texte de façon verticale, il aurait aussi fallu gérer le redimensionnement de la hauteur.

Une des parties les plus importante du code est la ligne 43 où l’on transmet tous les messages non traités vers la procédure d’origine. Sans cette ligne de code, l’application ne fonctionnera pas.

J’espère que vous avez encore appris quelque chose à l’aide de cet article.