Tutoriel pour afficher une image dans le fond d'une application MDI

Laurent Berne

Tous ceux qui à présent ont fait des applications MDI se sont rendu compte de la difficulté à mettre une image dans le fond. ♪

- Titre : Tutoriel pour afficher une image dans le fond d'une application MDI

- Auteur : Laurent Berne

- Parution : 7 avril 2001

- Licence : Copyright ® 2002 Laurent Berne. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.

Sommaire

Première précision, c'est une adaptation. Le code original n'est pas de moi. Je me suis contenté de le commenter et de l'expliquer, donc le mérite revient à Damon Chandler, auteur du code original. Je me suis quand même permis de réorienter le code dans une optique un peu plus objet. Voilà donc l'essentiel de ma contribution. Merci à Reynald PIEPZSYK pour l'erreur sur le message WM_PAINT.

La stratégie est la suivante : nous allons détourner la méthode originale qui gère la zone client de la fenêtre MDI pour la remplacer par une méthode de substitution qui mettra en place une mosaïque d'images.

Dans le code qui va suivre, j'ai appelé ma Form principale « Main ». Bien sûr sa propriété FormStyle est à fsMDIForm.

On en profite pour placer l'image de fond dans un TImage que l'on appelle Background (attention, il est impératif que l'image placée dans le fond soit un Bitmap que l'on place dans la propriété Picture->Bitmap du TImage).

Premièrement on va définir deux pointeurs de fonctions que l'on va utiliser pour le traitement des procédures de fenêtres.

FARPROC NewClientWP;
FARPROC OldClientWP;

On va aussi définir la méthode de dessin qui permet d'afficher une mosaïque à partir de l'image :

void __fastcall TileBlt(HDC HDestDC,int DestWidth,int DestHeight,HDC HSourceDC,int SourceWidth,int SourceHeight);

Et enfin la nouvelle procédure de fenêtre :

void __fastcall MDIClientWndProc(TMessage &Msg);

Le fichier Main.h doit contenir ceci :

#ifndef UnitMainH
#define UnitMainH

#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
#include <Graphics.hpp>

class TMain : public TForm
{
__published: // Composants gérés par l'EDI
TImage *Background;
void __fastcall FormCreate(TObject *Sender);

private: // Déclarations utilisateur
FARPROC NewClientWP ;
FARPROC OldClientWP ;
void __fastcall TileBlt(HDC HDestDC,
int DestWidth,
int DestHeight,
HDC HSourceDC,
int SourceWidth,
int SourceHeight);
void __fastcall MDIClientWndProc(TMessage &Msg);

public: // Déclarations utilisateur

__ fastcall TMain(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TMain *Main;
//---------------------------------------------------------------------------
#endif

Bien, maintenant nous allons nous intéresser au contenu du fichier source (*.cpp).
Ensuite, dans le constructeur de la Form on va initialiser les pointeurs de fonctions :

__fastcall TMain::TMain(TComponent* Owner): TForm(Owner)
{
// D'abord pour la nouvelle procédure de fenêtre on crée une instance :
NewClientWP = (FARPROC)MakeObjectInstance(MDIClientWndProc);
// la méthode MDIClientWndProc sera implémentée par la suite..

/* On fait pointer le deuxième pointeur sur l'ancienne WindowProc et
on établit la permutation des WindowProc avec la fonction API
SetWindowLong
On remarque que l'on passe en argument le Handle de la zone Client et pas celui de la fenêtre au complet*/

OldClientWP = (FARPROC)SetWindowLong(ClientHandle, GWL_WNDPROC, (LONG)NewClientWP);
}

Ensuite on va implémenter la nouvelle méthode qui gère la procédure de fenêtre de la zone client.
Procédure pour texture de fond :

void __fastcall TMain::MDIClientWndProc(TMessage Msg)
{
/* Rappel pour ceux qui ne sont pas familiers des API Win32 et du
fonctionnement de base d'une appli Windows
La WindowProc (ainsi nommée sous CBuilder, dans un prog "classique" elle
s'appelle plutôt WndProc) sert à interpréter les messages que Windows
envoie à l'application. Ces messages sont ici contenus dans une structure
TMessage.
Le but du jeu est d'intercepter les messages qui nous intéressent et de
rediriger les autres vers l'ancienne WindowProc (par l'intermédiaire du
pointeur OldWP) */.

switch (Msg.Msg) //traitement classique du message
{

case WM_ERASEBKGND: // message "efface le fond"
{
HDC Hdc = (HDC)Msg.WParam; // récupère le HDC du message
SelectPalette(Hdc, Background->Picture->Bitmap->Palette, true);
/*récupère la palette de l'image que l'on a choisie pour fond*/
RealizePalette(Hdc); // applique la palette
TileBlt(Hdc, Width, Height,
Background->Canvas->Handle,
Background->Picture->Bitmap->Width,
Background->Picture->Bitmap->Height); // appelle la fonction "Mosaique de l'image"

Msg.Result = 0;// renvoie un message nul pour pas que l'ancienne WindowProc "intervienne"
return;

}// fin case WM_ERASEBKGND

case WM_QUERYNEWPALETTE: // message nouvelle pale
{
HDC Hdc = GetDC(ClientHandle); // récupère le Handle de la zone client
SelectPalette(Hdc, Background->Picture->Bitmap->Palette,true);/*récupère
la palette de l'image que l'on a choisie  pour  fond*/
RealizePalette(Hdc);// applique la palette
InvalidateRect(ClientHandle, NULL, true); // provoque le rafraichissement de la zone client
ReleaseDC(ClientHandle, Hdc); // relâche le Handle

Msg.Result = 0;// renvoie un message nul pour pas que l'ancienne WindowProc "intervienne"
return;
}//fin du case WM_QUERYNEWPALETTE

case WM_PALETTECHANGED: // message palette changée
{
if ((HWND)Msg.WParam != ClientHandle) /* si le Handle transmis par le message
est différent du Handle de la zone client*/
{
HDC Hdc = GetDC(ClientHandle); // récupère le Handle de la zone client
SelectPalette(Hdc, Background->Picture->Bitmap->Palette, true);
/*récupère la palette de l'image choisie pour  fond*/
RealizePalette(Hdc); // applique la palette
UpdateColors(Hdc); // rafraichit les couleurs
ReleaseDC(ClientHandle, Hdc); // relâche le Handle de la zone client
}

Msg.Result = 0; // renvoie un message nul pour pas que l'ancienne WindowProc "intervienne"
return;
}//fin du case WM_PALETTECHANGED

case WM_HSCROLL: // message srcoll horizontal
case WM_VSCROLL: // message scroll vertical
case WM_SIZE :   //message de changement de taille
{
InvalidateRect(ClientHandle, NULL, true); // provoque le rafraichissement de la zone client
break;
}// fin du case WM_VSCROLL et WM_VHCROLL

default :
{
/* Si le message ne rentre dans aucun des cas suivants, on appelle l'ancienne
procédure pour le traitement "classique" des messages*/
Msg.Result = CallWindowProc(OldClientWP, ClientHandle, Msg.Msg,
Msg.WParam, Msg.LParam);
}// fin du cas default

}//fin du switch
}// fin de la procédure

Enfin nous allons implémenter la fonction qui affiche la mosaïque :

void __fastcall TMain::TileBlt(HDC HDestDC,
int DestWidth,
int DestHeight,
HDC HSourceDC,
int SourceWidth,
int SourceHeight)
{
for (int y = 0; y < DestHeight; y = y + SourceHeight)
for (int x = 0; x < DestWidth; x = x + SourceWidth)
BitBlt(HDestDC, x, y, SourceWidth, SourceHeight,HSourceDC, 0, 0,SRCCOPY);
// Copie l'image  sur  le "fond" de la zone client
}

Voilà tout est là.
Pour ceux qui ne veulent pas d'une mosaïque, mais une image étirée (voire n'importe quoi d'autre…), il faut bien sûr modifier MDIClientWndProc en conséquence.

Par exemple pour l'image étirée voici la partie de code à modifier :

case WM_ERASEBKGND : // message "efface le fond"
{
HDC Hdc = (HDC)Msg.WParam;
// Récupère le HDC du message

SelectPalette(Hdc, Background->Picture->Bitmap->Palette, true);
// Récupère la palette de l'image que l'on a choisie pour fond

RealizePalette(Hdc); // applique la palette
StretchBlt(Hdc, 0, 0, Width, Height, Background->Canvas->Handle, 0, 0,
Background->Picture->Bitmap->Width,Background->Picture->Bitmap->Height, SRCCOPY);
// appelle la fonction "copie en étirant une image"

Msg.Result = 0;// renvoie un message nul pour pas que l'ancienne WindowProc "intervienne"
return;

}

Bon courage !

Laurent BERNE