Nono40.developpez.com
Le petit coin du web de Nono40
SOURCES TESTS DELPHI WIN32 AUTOMATISMES DELPHI .NET QUICK-REPORT

Gestion de la barre des tâches Aero dans une application MDI

Cet article présente la méthode pour gérer la barre des tâches par document sous Windows 7 avec le thème Aero actif.

Commentez cet article : 1 commentaire Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Depuis l'arrivée de Windows Vista le style Aero permet d'obtenir des icônes des applications lancées en survolant la barre des tâches.
Depuis Windows 7, une interface existe pour gérer ces nouvelles fonctionnalités.

La gestion des icônes est presque automatique pour toutes les applications même celles développées avant l'existence de Windows Vista. Mais dans le cas d'une application MDI, Windows affiche seulement une icône pour l'application entière et non pas une icône par document ouvert.
L'affichage par défaut est le suivant :

Image non disponible

Or, dans le cas d'une application MDI, nous aimerions avoir une icône par document ouvert et pouvoir les survoler comme on peut le faire avec Excel ou Internet Explorer.
Nous allons donc voir les étapes pour arriver à :

Image non disponible

II. Le principe

La solution se découpe en deux parties distinctes :
- création des boutons dans la barre des tâches
- fournir à Windows des icônes et aperçus.

II-A. Préparation du projet

Dans l'exemple suivant nous allons partir du modèle de projet MDI donné dans Delphi. "Sélectionner Fichier->Nouveau->Autre..." Dans la rubrique "projets Delphi" prendre "Application MDI".
Ajouter la variable globale msgTaskbarButtonCreated : cardinal; après la définition de MainForm.
Ajouter en fin de l'unité Main.pas le code pour obtenir le numéro de message reçu après la création du bouton dans la barre des tâches :

 
Sélectionnez

initialization
  // On commence par demander à Windows quel est l'identifiant du message
  // TaskbarButtonCreated. Ce dernier sera envoyé par Windows à l'application
  // lorsque son bouton aura été créé dans la barre des tâches.
  msgTaskbarButtonCreated := RegisterWindowMessage('TaskbarButtonCreated');
end.

Ajouter dans la définition de la fiche la surcharge de la méthode WndProc et la variable TaskBar qui contiendra une instance de l'interface ITaskBarList3 :

 
Sélectionnez

  TMainForm = class(TForm)
  	{...}
  protected
    procedure WndProc(var Message: TMessage); override;
  public
    { Déclarations publiques }
    TaskBar : ITaskBarList3;
  end;
{...}

Les images sont fournies à Windows en réponse aux messages WM_DWMSENDICONICTHUMBNAIL et WM_DWMSENDICONICLIVEPREVIEWBITMAP. L'application doit y répondre en utilisant les fonctions DwmSetIconicThumbnail et DwmSetIconicLivePreviewBitmap réciproquement.

 
Sélectionnez

procedure TMainForm.WndProc(var Message: TMessage);
begin
  // On regarde si le message reçu correspond au message TaskbarButtonCreated
  if Message.Msg = msgTaskbarButtonCreated Then
  begin
    // On a reçu le message qu'on attendait, à présent on peut demander l'interface
    // ITaskBarList3
    TaskBar := CreateComObject(CLSID_TaskbarList) as ITaskBarList3;
  end else
    inherited WndProc(Message);
end;

Dans le modèle de projet donné dans Delphi, le source du projet n'active pas la gestion de la barre des tâches, on ne reçoit alors jamais le message msgTaskbarButtonCreated. Il faut ouvrir le source du projet et ajouter la ligne suivante :

 
Sélectionnez

program Mdiapp;

uses
  Forms,
  MAIN in 'MAIN.PAS' {MainForm},
  CHILDWIN in 'CHILDWIN.PAS' {MDIChild},
  about in 'about.pas' {AboutBox};

{$R *.RES}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;     // Ajouter cette ligne
  Application.CreateForm(TMainForm, MainForm);
  Application.CreateForm(TAboutBox, AboutBox);
  Application.Run;
end.

II-B. Création des boutons

Les boutons sont créés à l'aide de l'interface ITaskBarList3. Vous avez une présentation de cette interface dans l'article de Franck Soriano sur l'utilisation de la barre des tâches de Windows avec Delphi 2010.

Au lancement d'une application, si aucun code spécifique n'est appelé, Windows crée automatiquement un bouton dans la barre des tâches. Ce bouton est lié au handle de la fenêtre principale.
Pour avoir un bouton par document il faut créer spécifiquement un bouton pour chaque document ouvert et le détruire pour chaque document fermé.

L'ajout d'un bouton est effectué par l'appel de la méthode ITaskbarList3.RegisterTab, il est ensuite affiché à la position voulue via la méthode ITaskbarList3.SetTabOrder
Cet appel sera effectué dans l'évènement OnCreate de la fiche MDI fille.

 
Sélectionnez

procedure TMDIChild.FormCreate(Sender: TObject);
begin
  // On demande la création du bouton dans la barre des tâches
  MainForm.TaskBar.RegisterTab(Handle,MainForm.Handle);
  // On le place en fin de liste
  MainForm.TaskBar.SetTabOrder(Handle,0);
end;

Notez que le bouton automatiquement créé par Windows au lancement de l'application est automatiquement supprimé dès le premier appel de RegisterTab et SetTabOrder. Il n'est pas utile de le supprimer explicitement.

La suppression d'un bouton est effectuée par l'appel de la méthode ITaskbarList3.UnregisterTab.
Cet appel sera effectué dans le OnClose. Sachant que dans le cas d'une application MDI le OnClose demande la destruction de la fiche, si vous conservez la fiche en mémoire et que vous voulez conserver le bouton dans la barre des tâches, c'est dans OnDestroy qu'il faudra appeler UnregisterTab.

 
Sélectionnez

procedure TMDIChild.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  // On désenregistre le bouton de la barre des tâches.
  MainForm.TaskBar.UnregisterTab(Handle);

  Action := caFree;
end;

Notez que lorsque le dernier bouton est détruit, Windows recrée automatiquement un bouton lié à la fenêtre principale ; nous n'avons donc pas à nous soucier de ce point.

Il est également possible de sélectionner par défaut l'un des boutons de la barre de tâches représentant le document actif de l'application.
Ceci est effectué par l'appel de ITaskbarList3.SetTabActive, cet appel étant effectué dans l'évènement OnActivate de la fiche MDIChild :

 
Sélectionnez

procedure TMDIChild.FormActivate(Sender: TObject);
begin
  // Quand la fiche fille devient active, l'icône de la fiche dans la barre
  // des tâche devient celle sélectionnée par défaut
  MainForm.TaskBar.SetTabActive(Handle,MainForm.Handle,0);
end;

A l'exécution les boutons sont bien créés... mais vides.

Image non disponible

II-C. Gérer les icônes et prévisualisations

Pour fournir à Windows des icônes et des prévisualisations il faut gérer les messages WM_DWMSENDICONICTHUMBNAIL et WM_DWMSENDICONICLIVEPREVIEWBITMAP. L'application doit y répondre en utilisant les fonctions DwmSetIconicThumbnail et DwmSetIconicLivePreviewBitmap.
Or c'est là que le problème se corse : le DVM (Desktop Windows Manager) ne dialogue qu'avec les fenêtres "Top level", c'est à dire les fenêtres sans parent. Les fenêtres MDIChild ne sont pas concernées car elles ont pour parent commun une fenêtre de classe MDIClient. La notion de parent est ici celle des APIs Windows, la proprité MDIChild.Parent vaut bien Nil. Le handle MDIChild.Handle a lui pour fenêtre parente la zone client MDI de la fiche principale.

II-C-1. Création d'un 'cache'

Pour contourner ce problème nous allons créer une fiche vide pour chaque fiche MDIChild ouverte. Cette fiche "cache" ne sera jamais visible mais permettra de gérer correctement tous les messages Windows liés à la barre des tâches.

Dans Delphi ajouter une nouvelle fiche nommée FormCache dans une unité UFormCache. Une fois la fiche créée, supprimez sa création automatique dans les options de projet et supprimez la ligne FormCache:TFormCache. Nous allons déporter dans cette fiche toute la gestion de la barre des tâches.

En plus de ne pas être une fiche de type MDIChild, une fiche doit avoir les attributs DWMWA_HAS_ICONIC_BITMAP et DWMWA_FORCE_ICONIC_REPRESENTATION afin de recevoir les messages concernés. Nous allons donc ajouter les attributs à la création de la fiche :

 
Sélectionnez

procedure TFormCache.FormCreate(Sender: TObject);
Var
  fForceIconic : Bool;
  fHasIconicBitmap : BOOL;
begin
  // A la création de la fiche on change le style de la fenêtre
  // pour prévenir Windows qu'il doit nous demander une icône
  // et un aperçu.
  // Windows n'est pas capable de le faire de lui même sur une fiche
  // enfant MDI.
  fForceIconic := True;
  fHasIconicBitmap := True;
  DwmSetWindowAttribute(
      Handle,
      DWMWA_HAS_ICONIC_BITMAP,
      @fHasIconicBitmap,
      sizeof(fHasIconicBitmap));

  DwmSetWindowAttribute(
      Handle,
      DWMWA_FORCE_ICONIC_REPRESENTATION,
      @fForceIconic,
      sizeof(fForceIconic));
end;

Pour créer la fiche "cache" nous allons ajouter une méthode de classe permettant de créer une fiche de cache liée à une fiche MDIChild.
Au passage nous créons le bouton lié au document dans la barre des tâches, notez que c'est bien le handle de la fiche "cache" qui est passé à Windows et non pas le Handle de la fiche MDIChild.

 
Sélectionnez

  TFormCache = class(TForm)
    {...}
  public
    { Déclarations publiques }
    Class Function CreationFicheCache(FicheMDIFille:TForm):TFormCache;
  end;

{...}

class function TFormCache.CreationFicheCache(FicheMDIFille: TForm): TFormCache;
begin
  if MainForm.TaskBar=Nil then
  Begin
    Result := Nil;
    Exit;
  End;

  // Création de la fiche "cache" et mise à jour des références
  Result := TFormCache.Create(FicheMDIFille);
  Result.MDIFille := FicheMDIFille;
  Result.Caption := FicheMDIFille.Caption;

  // On demande la création du bouton dans la barre des tâches
  MainForm.TaskBar.RegisterTab(Result.Handle,MainForm.Handle);
  // On le place en fin de liste
  MainForm.TaskBar.SetTabOrder(Result.Handle,0);
end;

Du coup dans le OnCreate de la fiche fille on ne crée que la fiche "cache", le cache se chargera du bouton.

 
Sélectionnez

procedure TMDIChild.FormCreate(Sender: TObject);
begin
  Cache := TFormCache.CreationFicheCache(Self);
end;

Comme la fiche MDIChild est donnée comme propriétaire de la fiche "cache", la destruction de la fiche fille entrainera la destruction automatique de la fiche "Cache".
Le OnClose de la fiche MDIChild redevient classique :

 
Sélectionnez

procedure TMDIChild.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

Tandis que le retrait du bouton est effectué dans le OnDestroy de la fiche "cache" :

 
Sélectionnez

procedure TFormCache.FormDestroy(Sender: TObject);
begin
  // On désenregistre le bouton de la barre des tâches.
  MainForm.TaskBar.UnregisterTab(Handle);
end;

La gestion du bouton actif sera aussi effectué dans la fiche "cache", la fille MDIChild demandant à la fiche "Cache" de devenir le bouton actif.

 
Sélectionnez

  TFormCache = class(TForm)
    {...}
  public
    { Déclarations publiques }
    Class Function CreationFicheCache(FicheMDIFille:TForm):TFormCache;
  end;

{...}

procedure TFormCache.DoActivate;
begin
  // Quand la fiche fille devient active, l'icône de la fiche dans la barre
  // des tâche devient celle sélectionnée par défaut
  MainForm.TaskBar.SetTabActive(Handle,MainForm.Handle,0);
end;
 
Sélectionnez

procedure TMDIChild.FormActivate(Sender: TObject);
begin
  if Cache<>Nil then
    Cache.DoActivate;
end;

Il ne reste plus qu'à répondre aux messages de Windows pour fournir les images. Si on ne le fait pas, Windows affiche une fenêtre vide avec le sablier en attendant une hypothétique réponse de l'application :

Image non disponible

II-C-2. Donner une icône pour la barre des tâches.

Pour ajouter une icône dans la barre des boutons créée pour la barre des tâches, il faut répondre au message WM_DWMSENDICONICTHUMBNAIL avec la fonction DwmSetIconicThumbnail.

Dans la fiche "cache" nous allons donc surcharger la méthode WndProc afin de répondre aux messages nous intéressant.
Le message WM_DWMSENDICONICTHUMBNAIL donne la taille maximale de l'icône qu'il faut renvoyer, si l'une des dimensions est supérieure l'image sera refusée.

Il faut donner à la fonction DwmSetIconicThumbnail un DIB contenant une image. Les dimensions maximum de cette image sont données en paramètre du message : Message.LParamHi donne la largeur maximum et Message.LParamLo donne la hauteur maximum. L'image doit être de 32bis par pixel. Le contenu lui même est libre, vous pouvez y mettre une miniature de la fiche ou une photo de vos vacances ;).
Dans l'exemple nous allons créer une image réduite de la fenêtre, comportement classique sous Windows.
Pour ce faire nous demandons à la fiche de se dessiner dans un bitmap de la même taille.

 
Sélectionnez

    // On reçoit une demande d'icône ou de prévisualisation taille réelle.
    // Dans les deux cas il capturer la copie de la fiche
    BMP := TBitMap.Create;
    Try
      // Le bitmap doit être de type DIB et non DDB le type par défaut
      BMP.HandleType := bmDIB;
      // On ajuste le bitmap à la taille de la zone client
      BMP.Width := MDIFille.ClientWidth;
      BMP.Height := MDIFille.ClientHeight;
      // Et on demande simplement à la fiche de se dessiner dedans
      // Cette méthode à l'avantage de fonctionner même si la fiche
      // est cachée.
      MDIFille.PaintTo(BMP.Canvas,0,0);
      
      {...}
      
    Finally
      Bmp.Free;
    End;

Puis nous allons copier à l'échelle cette image dans une autre. Cette deuxième image sera aux dimensions maximales autorisées afin d'être passée à la fonction DwmSetIconicThumbnail.

 
Sélectionnez

      if Message.Msg = WM_DWMSENDICONICTHUMBNAIL then
      Begin
        // Dans le cas d'un icône il faut d'abord le reduire à
        // la taille maximum acceptée par Windows. Cette taille
        // est donnée en paramètre de message
        ICO := TBitMap.Create;
        Try
          ICO.HandleType := bmDIB;
          // On teste le coeff de réduction pour garder la proportion de l'image
          if (BMP.Width/Message.LParamHi)>(BMP.Height/Message.LParamLo) then
          Begin
            CoeffD := BMP.Width;
            CoeffM := Message.LParamHi;
          End Else
          Begin
            CoeffD := BMP.Height;
            CoeffM := Message.LParamLo;
          End;
          ICO.Width := MulDiv(BMP.Width,CoeffM,CoeffD);
          ICO.Height := MulDiv(BMP.Height,CoeffM,CoeffD);
          ICO.Canvas.StretchDraw(ICO.Canvas.ClipRect,BMP);
          DwmSetIconicThumbnail(Handle,ICO.Handle,DWM_SIT_DISPLAYFRAME);
        Finally
          ICO.Free;
        End;
      End;

II-C-3. Donner un aperçu de la fiche

Quand on survole une des icônes de l'application dans la barre des tâches, Windows affiche une prévisualisation de la fiche. Windows demande cet aperçu via le message WM_DWMSENDICONICLIVEPREVIEWBITMAP et on donne l'image via la fonction DwmSetIconicLivePreviewBitmap

DwmSetIconicLivePreviewBitmap doit recevoir une image de type DIB 32bits et une position d'affichage. Cette position est relative par rapport au coin supérieur gauche de la fiche principale de l'application. Le code de création de l'aperçu est le même que celui du paragraphe précédent. Le calcul de la position est effectué à partir de la position de la zone MDICliente de la fiche principale.

Image non disponible

On part de la position de la zone MDIClient donnée par les coordonnées du handle de fenêtre MainForm.ClientHandle. (flèche bleue)
On soustrait la position absolue de la fiche principale. (flèche rouge)
On ajoute la position relative de la fiche fille. (flèche jaune).
La position obtenue (flèche verte) est celle qu'il faut transmettre à la fonction DwmSetIconicLivePreviewBitmap afin que l'aperçu se superpose exactement à la fiche.

 
Sélectionnez

      if Message.Msg = WM_DWMSENDICONICLIVEPREVIEWBITMAP then
      Begin
        // On cherche la position de la fiche enfant par rapport au coin
        // supérieur gauche de la fiche MDI principale
        // On part de la position absolue de la zone MDICLIENT de la fenêtre principale
        // On ajoute la position relative de la fiche enfant dans cette zone client
        // On soustrait la position absolue de la fiche principale
        GetWindowRect(MainForm.ClientHandle,Rect);
        Point.X := Rect.Left + MDIFille.Left - MainForm.Left;
        Point.Y := Rect.Top  + MDIFille.Top  - MainForm.Top;
        // Dans le cas d'un aperça taille réelle on donne directement le bitmap
        DwmSetIconicLivePreviewBitmap(Handle,bmp.Handle,Point,DWM_SIT_DISPLAYFRAME);
      End Else

II-C-4. Gérer le titre de la fenêtre

Windows se sert du titre de la fenêtre comme titre des icônes. Comme Windows ne connait que la fenêtre 'cache', on doit recopier le titre de la fenêtre fille à la fenêtre cache à chaque changement de celui-ci.

Pour cela nous allons gérer le message CM_TEXTCHANGED de la fiche MDIChild. A chaque changement de titre, le titre de la fenêtre "cache" sera mis à jour.

 
Sélectionnez

  TMDIChild = class(TForm)
	{...}
  private
    { Déclarations privées }
    procedure CMTextChanged (var Message: TMessage); message CM_TEXTCHANGED;
  end;

{...}

procedure TMDIChild.CMTextChanged(var Message: TMessage);
begin
  if Cache<>Nil then
    Cache.Caption := Caption;
  Inherited;
end;

II-C-5. Changer l'icône représentant la fiche

Windows va conserver l'icône de la fiche tant que la fiche existe. Il ne la redemandera pas au prochain affichage de la barre de boutons dans la barre des tâches.
Il est possible de forcer Windows à demander de nouveau l'icône au prochain affichage. Il faut appeler DwmInvalidateIconicBitmaps afin d'invalider l'icône en mémoire.

Dans notre exemple nous le ferons chaque fois que la taille de la fille MDIChild change. Ainsi, au moins, les proportions de l'icône seront exactes à chaque affichage. Dans l'évènement OnResize de la fiche MDIChild, l'icône en cache sera invalidée.

 
Sélectionnez

  TFormCache = class(TForm)
    {...}
  public
    { Déclarations publiques }
    Procedure DoReSize;
  end;

procedure TFormCache.DoReSize;
begin
  // Quand la fiche fille à changer de taille on demande à windows
  // d'effacer son cache afin que la nouvelle icône ait les bonnes
  // proportions.
  DwmInvalidateIconicBitmaps(Handle);
end;
{...}
 
Sélectionnez

procedure TMDIChild.FormResize(Sender: TObject);
begin
  if Cache<>Nil then
    Cache.DoReSize;
end;

II-D. Gérer les actions sur les icônes

Du fait d'utiliser une fiche de cache au lieu des fiches elle mêmes, les fiches MDIChild ne reçoivent plus les messages standards comme ceux pour les activer ou les fermer. Ces messages sont reçus par la fenêtre "cache". Il faut donc intercepter ces messages et les transmettre la fiche MDIChild correspondante.

Nous allons donc compléter le code de WndProc pour gérer au minimum les messages WM_CLOSE et WM_ACTIVATE. WM_CLOSE est envoyé si l'utilisateur clique sur la petite croix rouge située sur l'icône. WM_ACTIVATE est envoyé si l'utilisateur clique sur l'une des icônes.

 
Sélectionnez

procedure TFormCache.WndProc(var Message: TMessage);
begin
  if Message.Msg = WM_CLOSE Then
  begin
    // La fiche a reçu une demande de fermeture venant de la barre de tâche
    // On la transmet à la vrai fenêtre fille
    PostMessage(MDIFille.Handle,WM_CLOSE,0,0);
  end else
  if Message.Msg = WM_ACTIVATE Then
  begin
    // La fiche a reçu une demande de d'activation venant de la barre de tâche
    // (Quand on clique sur l'une des icônes dans Win 7 Aero)
    // On met la vrai fiche fille en avant plan
    MDIFille.Show;
  end else
{...}
End;

III. Conclusion

Nous aurons vu comment gérer la barre des tâches dans une application de type MDI. Il reste des points à améliorer comme la qualité des icônes produites étant donné que la méthode utilisée (ici avec un simple StretchDraw) n'est pas optimum.
Il est possible aussi de gérer l'ordre des documents dans la barre des tâches, ici nous avons positionné chaque nouveau bouton en fin mais il est possible de le positionner où on veut par rapport aux boutons existants.

Les fichiers du projet sont disponibles ici : TestMDI2010.zip ( Miroir )
Remarque : il faut Delphi 2010 minimum pour pouvoir ouvrir ce projet.

Je tiens à remercier Julien Lelong et Franck Soriano pour la relecture et les corrections.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2011 Bruno Guérangé. 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.