Astuces delphi : comment modifier le menu système des fenêtres ?

Vincent CALLIES

En cliquant avec le bouton droit de la souris sur la barre de titre d'une fenêtre apparaît un PopUpMenu (voir figure 1.1, ci dessous). Ce PopUpMenu, appelé menu système, n'est pas directement modifiable par l'utilisation des propriétés de l'Objet Form de Delphi, tout du moins pas totalement (voir le post scriptum de la partie III). Le présent article vous propose de vous donner les moyens de contrôler et modifier ce menu.

- Titre : Astuces delphi : comment modifier le menu système des fenêtres ?

- Auteur : Vincent CALLIES

- Parution : 9 août 2000

- Mise à jour : 9 août 2000

- Licence : Ce document est issu de https://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

Figure 1.1 : le menu système d'une fenêtre
La nécessité de contrôler le menu système d'une fenêtre peut naître de différents types de besoins :

- Le besoin de contrôler le menu système, par exemple pour désactiver la fermeture automatique de la fenêtre par le raccourci clavier Alt-F4, ou interdire la réduction ou le plein écran de l'application, ou même son déplacement...

- Le besoin d'enrichir le menu système, dans le cas où la fenêtre est une barre flottante (ToolWindow) qui ne saurait avoir un menu traditionnel, trop gourmant en place.

La modification du menu système des fenêtres doit être faite avec précaution. En effet, elle se réalise par un recours aux API qui court-circuite votre Objet Pascal Form. Une des conséquences, par exemple, en est que la modification générée par le changement d'état de la propriété FormStyle va détruire les modifications que vous aurez faites dans le menu système. Une astuce consiste à ne pas utiliser cette propriété mais son équivalent par API.

Par ailleurs, il ne faut pas oublier que l'utilisateur a l'habitude de retrouver le menu système de la Form bien évidemment s'il s'agit de la fenêtre principale - dans le menu de l'Application elle-même, lorsqu'elle est réduite en bas de l'écran. Si l'on supprime, modifie ou ajoute des éléments dans le menu système de la fenêtre principale, le menu système de l'application devra s'en faire l'écho.

"Le plus grand danger de vivre est de vivre sans danger " disait Alfred Adler. Commençons donc notre exploration vers ce menu interdit... Pour ce faire, le plus simple est de modifier le menu lors de la création de la fenêtre de l'application.

I°) Premier exercice, créer une fiche que l'on ne puisse plus déplacer et qui ait un article du menu système de la fenêtre moins laconique que " Fermeture "

L'exercice a pour objet de se familiariser avec les API GetSystemMenu, ModifyMenu, DeleteMenu. Il consiste à détruire l'article " Déplacement " du PopUpMenu et modifier l'article " Fermeture " .

Nous allons :

1. Créer une nouvelle application (Fichier, Nouveau, Application).

2. Modifier FormCreate afin de changer le menu système avec les API GetSystemMenu, ModifyMenu, DeleteMenu (Dans l'inspecteur d'objet, aller dans les Evénements de l'objet Form1 et double cliquer dans l'événement OnCreate)

3. Copier la routine ci-dessous...

procedure TForm1.FormCreate(Sender: TObject);
var
SysMenu: hMenu;
Begin
{modifions le menu de la fenêtre :}
{demandons le handle du menu afin de pouvoir nous adresser à lui}
SysMenu := GetSystemMenu(Handle, False);

{modifions un élément du menu}
ModifyMenu(SysMenu, sc_Close, mf_ByCommand, sc_Close, '&Quitter mon application à moi'#9'Alt+F4');

{supprimons un élément du menu : dans le cas présent, « déplacement »}
DeleteMenu(SysMenu, Sc_Move, mf_ByCommand);

{modifions le menu de l'application, en suivant le même principe :}
SysMenu := GetSystemMenu(application.handle,false);

ModifyMenu(SysMenu, sc_Close, mf_ByCommand, sc_Close, '&Quitter mon application à moi'#9'Alt+F4');
end;

Appuyez sur la touche F9 pour lancer votre application.

Note : Vous aurez sans doute remarqué que le simple fait d'avoir retiré l'article " Déplacement " du menu système interdit effectivement à l'utilisateur le déplacement de la fenêtre. Dans le deuxième exercice nous nous familiariserons avec les conséquences de la modification du menu système.

figure 1.2 : observer la différence avec la figure 1.1

II°) Deuxième exercice : pour aller au delà de la simple fermeture de la fenêtre.

L'exercice qui a pour objet de maîtriser les messages du menu système se déroule en deux temps.

1. Il consiste a ajouter à notre projet une nouvelle fenêtre. On se contentera de mettre sa propriété Visible à True et de modifier l'article " Fermeture " du menu système. On constatera que le choix de cet article permet de quitter notre seconde fenêtre mais pas l'application elle même.

2. On va créer un gestionnaire de messages pour permettre de quitter l'application.

Nous allons :

1. Créer une deuxième fenêtre (Fichier, Nouveau, Fiche), décaler cette fenêtre sur la droite ou sur la gauche pour qu'elle ne chevauche pas exactement votre première fenêtre que vous ne pourrez déplacer ;

2. Mettre la propriété de Form2.Visible à True (Dans l'inspecteur d'objet de la nouvelle fenêtre, aller dans l'onglet Propriété, et changer la valeur de la propriété Visible) ;

3. Modifier FormCreate afin d'y mettre la même routine que précédemment mais allégée (Dans l'inspecteur d'objet de la nouvelle fenêtre, aller dans l'onglet Evénements de l'objet Form2 et double cliquer dans l'événement OnCreate) ;

4. Copier la routine ci-dessous.

procedure TForm2.FormCreate(Sender: TObject);
var
SysMenu: hMenu;
Begin
{modifions le menu de cette nouvelle fenêtre :}
SysMenu := GetSystemMenu(Handle, False);
ModifyMenu(SysMenu, sc_Close, mf_ByCommand, sc_Close, '&Quitter mon application à moi'#9'Alt+F4');
end;

Appuyez sur la touche F9 pour lancer votre application.

Si vous avez un problème :

- Vérifier que vous avez bien mis la propriété Visible à True.

- Vérifier que vous avez bien décalé votre seconde fenêtre par rapport à la première.

Note : Vous remarquerez rapidement que le choix de l'article " Quitter mon application à moi " dans la deuxième fenêtre ne permet pas de quitter l'application mais simplement notre nouvelle fenêtre. Cela s'explique par le fait que la première fiche est la fenêtre principale (la fermer équivaut à quitter l'application) alors que la seconde n'est... qu'une fenêtre supplémentaire dont ne dépend pas l'application !

Nous allons :

1. Modifier la déclaration de type Form2 pour y ajouter, dans les déclarations privées, un gestionnaire de messages interceptant les commandes du menu système ;

2. Déclarer dans les déclarations publiques, une procédure " Quitter " ;

3. Ecrire la procédure SysCommand ;

4. Ecrire la procédure Quitter.

Remplacer la déclaration de type Form2 par celle-ci :

type
TForm2 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Déclarations privées }
procedure SysCommand(var Msg: TMessage); message wm_SysCommand; {nous interceptons les messages SysCommand}
public
procedure PMSys_Quitter; {S'il s'agit du message « close », on le gèrera ici}
{ Déclarations publiques }
end;

A la suite de votre procédure TForm2.FormCreate, ajouter ces deux procédures :

procedure TForm2.SysCommand(var Msg: TMessage);
begin
inherited; {instruction pour que les messages soient traités comme d'habitude}
if Msg.wParam = sc_Close then Quitter; {sauf celui qui nous intéresse}
end;

procedure TForm2. PMSys_Quitter;
begin
Showmessage ('au revoir les enfants !');
Application.Terminate ;
end;

Appuyez sur la touche F9 pour lancer votre application.

III°) Troisième exercice : empêcher la fermeture de la fenêtre et la rendre toujours visible.

L'exercice, qui se déroule en deux temps, a pour objet de comprendre, en premier lieu, la différence entre les paramètres mf_ByCommand et Mf_ByPosition puis, en second lieu, le problème du changement de Style de la fenêtre ainsi que l'utilisation des API AppendMenu, CheckMenuItem et SetWindowPos.

Nous allons :

1. Créer une troisième fenêtre (Fichier, Nouveau, Fiche), décaler cette fenêtre sur la droite ou sur la gauche pour qu'elle ne chevauche pas exactement les deux précédentes ;

2. Mettre la propriété de Form3.Visible à True (Dans l'inspecteur d'objet de la nouvelle fenêtre, aller dans l'onglet Propriété, et changer la valeur de la propriété Visible) ;

3. Créer un événement FormCreate (Dans l'inspecteur d'objet, aller dans l'onglet Evénements de l'objet Form3 et double cliquer dans l'événement OnCreate)

4. Copier la routine ci-dessous...

procedure TForm3.FormCreate(Sender: TObject);
var
SysMenu: hMenu;
begin
SysMenu := GetSystemMenu(Handle, False);
{La destruction de l'article de fermeture du menu rend inactif le bouton et le raccourci clavier}
DeleteMenu(SysMenu, sc_Close, mf_ByCommand);
{Destruction de l'article de séparation, devenu inutile}
DeleteMenu(SysMenu,5,Mf_ByPosition);
end;

Note : Vous noterez la différence entre les paramètres utilisés dans les deux appels à l'API DeleteMenu. Le premier paramètre mf_ByCommand demande la destruction de l'article par son identifiant (la constante sc_Close que l'on trouve définie dans l'unité Windows.pas). Le second paramètre Mf_ByPosition demande la destruction de l'article par sa position dans le menu (c'est le 5ème article).

Post Scriptum : La propriété BoderIcons de votre objet Form permet de déterminer quels boutons systèmes seront affectés dans la barre de titre de la fiche durant l'exécution. Les boutons proposés correspondent à la présence active du bouton de réduction ou d'agrandissement, et, dans la cas où ces deux derniers sont désactivés, la présence du bouton d'aide. Il n'est pas possible d'agir sur le bouton de fermeture par cette propriété, c'est pourquoi il vous a été proposé l'exemple ci-dessus utilisant une API.

Nous allons :

1. Modifier la déclaration de type Form3 pour y ajouter, dans les déclarations privées, un gestionnaire de messages interceptant les commandes du menu système ;

2. Déclarer dans les déclarations publiques, une procédure " Visible " ;

3. Ecrire la procédure SysCommand ;

4. Ecrire la procédure Visible ;

5. Modifier la procédure CreateForm.

Remplacer la déclaration de type Form3 par celle-ci :

type
TForm3 = class(TForm)
procedure FormCreate(Sender: TObject);
private
procedure SysCommand(var Msg: TMessage); message wm_SysCommand;
public
procedure PMSys_Visible;
end;

A la suite de votre procédure Tform3.FormCreate, ajouter ces deux procédures :

procedure TForm3.SysCommand(var Msg: TMessage);
begin
inherited; {instruction pour que les messages soient traités comme d'habitude}
if Msg.wParam = 121 then PMSys_Visible;
end;

procedure TForm3.PMSys_Visible;
var
SysMenu: hMenu;
MonFlag:integer;
begin
SysMenu := GetSystemMenu(Handle, False);
MonFlag:= GetMenuState(SysMenu,121,mf_ByCommand);
{Nous sommes obligés de passer par une API pour mettre la fenêtre en premier plan car la propriété
FormStyle := fsStayOnTop redessine la fenêtre et fait disparaître le menu !}
if MonFlag = Mf_Checked then
begin
SetWindowPos(handle, Hwnd_NoTopmost, Left, Top, Width, Height,0);
CheckMenuItem(SysMenu, 121,MF_UnChecked);
end
else
begin
SetWindowPos(handle, Hwnd_Topmost,Left, Top, Width, Height,0);
CheckMenuItem(SysMenu,121,Mf_Checked);
end;
end;

Modifier la procédure Tform3.FormCreate comme il suit :

procedure TForm3.FormCreate(Sender: TObject);
var
SysMenu: hMenu;
begin
SysMenu := GetSystemMenu(Handle, False);
DeleteMenu(SysMenu, sc_Close, mf_ByCommand);
DeleteMenu(SysMenu,5,Mf_ByPosition);
{Ajout d'un article proposant que la fenêtre soit toujours visible !}
AppendMenu(SysMenu, mf_ByCommand+ Mf_Unchecked, 121, 'Tjrs Visible' );
end;

Appuyez sur la touche F9 pour lancer votre application.

Note : Pour bien comprendre le problème de FormStyle := fsStayOnTop, vous pouvez très bien détruire le contenu de la procédure TForm3.PMSys_Visible et le remplacer par cette simple ligne : FormStyle := fsStayOnTop ; vous verrez disparaître le fruit de vos efforts : les modifications du menu système.

IV°) Quatrième et dernier exercice : personnaliser son menu, jusqu'aux icônes.

L'objectif de cet exercice est d'apprendre à maîtriser la difficile API InsertMenuItem.
Nous allons :

1. Créer une quatrième et dernière fenêtre (Fichier, Nouveau, Fiche), décaler cette fenêtre sur la droite ou sur la gauche pour qu'elle ne chevauche pas exactement les trois précédentes ;

2. Mettre la propriété de Form4.Visible à True (Dans l'inspecteur d'objet de la nouvelle fenêtre, aller dans l'onglet Propriété, et changer la valeur de la propriété Visible) ;

3. Créer un événement FormCreate (Dans l'inspecteur d'objet, aller dans l'onglet Evénements de l'objet Form4 et double cliquer dans l'événement OnCreate)

4. Créer un événement FormDestroy (Dans l'inspecteur d'objet, aller dans l'onglet Evénements de l'objet Form4 et double cliquer dans l'événement OnDestroy)

5. Modifier la procédure CreateForm.

Modifier avant et après l'instruction implementation vos déclarations comme il suit :

var
Form4: TForm4;
MonIcone : TBitmap;

implementation
const
SC_MonArticleMenu = WM_USER + 1;

Modifier les procédures FormCreate et FormDestroy ainsi :

var
Form4: TForm4;
MonIcone : TBitmap;

implementation
const
SC_MonArticleMenu = WM_USER + 1;

Modifier les procédures FormCreate et FormDestroy ainsi :

procedure TForm4.FormCreate(Sender: TObject);
var
SysMenu: hMenu;
MenuItemInfo: TMenuItemInfo;
begin
MonIcone := TBitmap.create;
MonIcone.LoadFromFile ('C:\Program Files\Fichiers communs\Borland Shared\Images\Icons\chem16.bmp');
{Note : ce chemin dépend de l'installation que vous avez retenue pour Delphi}
SysMenu := GetSystemMenu(Handle, False);
{Définition de notre article}
with MenuItemInfo do
begin
{taille de la structure en bytes :}
cbSize := 44;
{paramétrages : retrait ou activation}
fMask := Miim_Checkmarks or Miim_ID or
Miim_State or Miim_Type;
{défini le type d'article :}
fType := Mft_String ;
{l'article sera coché :}
fState := Mfs_Checked;
{identification du menu :}
wID := SC_MonArticleMenu;
{on indique que l'article du menu ne mènera pas à un sous menu :}
hSubMenu := 0;
{Handle du bitmap a afficher à côté de l'article quand il est sélectionné (checked, en anglais). Ce sera le cas ici.}
hbmpChecked := MonIcone.Handle ;
{même chose mais lorsqu'il n'est pas sélectionné.}
hbmpUnchecked := 0;
{contenu du menu (dépend du choix effectué dans MenuItemInfo.fType)}
dwTypeData := PChar('La soif de connaître !');
end;
{Ajout - enfin ! - de notre article}
InsertMenuItem(SysMenu, Dword(-1), True, MenuItemInfo);
end;

procedure TForm4.FormDestroy(Sender: TObject);
begin
{il ne faut pas oublier de libérer notre objet !}
MonIcone.Free ;
end;

Note : Vous remarquerez que nous avons défini une constante à partir de WM_USER. Il s'agit d'un moyen de définir des messages privés au sein des applications sans risque de double usage. C'est une méthode préférable à celle de l'allocation d'un identifiant au hasard, comme nous l'avions fait dans l'exemple précédent.

figure 1.3 : une icône sur l'article du menu système

Maintenant que vous connaissez l'utilisation de ces API, le plus simple est de errer dans Forms.pas et de réaliser un descendant de TForm !

Si vous avez des suggestions afin d'améliorer cet article, n'hésitez pas à m'écrire : Vincent CALLIES