SetMenuItemEx [Windows seulement]

Partagez votre expérience de PureBasic avec les autres utilisateurs.
Avatar de l’utilisateur
ZapMan
Messages : 460
Inscription : ven. 13/févr./2004 23:14
Localisation : France
Contact :

SetMenuItemEx [Windows seulement]

Message par ZapMan »

Cette bibliothèque offre des fonctions permettant de surmonter les limitations des fonctions actuelles de PureBasic (6.20) dédiées à la gestion des menus.

Voici ce que ça donne :
Image

Le code est trop important pour être posté sur le forum. Vous pouvez le télécharger ici :
https://www.editions-humanis.com/downlo ... olorEx.zip
----> Extraire SetMenuItemEx.pbi du zip.

Limitations de PureBasic :
Il n'est pas possible de changer l'icône d'un élément de menu après l'avoir attribuée. Il faut détruire le menu et le reconstruire entièrement pour pouvoir modifier l'une de ses icônes.
Il n'est pas possible de récupérer l'ImageID d'une icône de menu. Il faut le mémoriser dans une variable au moment de l'attribution pour pouvoir le retrouver plus tard.
Il n'est pas possible d'attribuer une icône à un élément de la barre de menus d'une fenêtre si cet élément possède des sous-éléments.
Il n'est pas possible de modifier la police et les couleurs des éléments de menu.

De nombreuses fonctions de cette bibliothèque, comme SetMenuItemFont(), viennent simplement s'ajouter à l'ensemble des fonctions originales de PureBasic. Certaines fonctions existantes, comme MenuTitle(), sont redéfinies pour offrir plus de capacités et de flexibilité.

Fonctions ajoutées :
SetMenuItemFont() et GetMenuItemFont()
SetMenuTitleFont() et GetMenuTitleFont()
SetMenuItemColor() et GetMenuItemColor()
SetMenuTitleColor() et GetMenuTitleColor()
SetMenuItemImage() et GetMenuItemImage()
SetMenuTitleImage() et GetMenuTitleImage()
CheckMenuItem() et IsMenuItemChecked()
SetMenuColor() : applique une couleur à tous les éléments existants d'un menu
SetMenuFont() : applique une police à tous les éléments existants d'un menu

Fonctions améliorées :
MenuTitle() peut désormais accepter un imageID en second paramètre. Elle renvoie également le handle du sous-menu créé par cette commande.

MenuBar() peut maintenant avoir un ID en paramètre, ce qui permet d'attribuer des couleurs particulières aux barres de menus.

Il est désormais possible d'utiliser une allocation dynamique pour les ID des éléments en utilisant #PB_Any comme d'habitude. Exemple :

Code : Tout sélectionner

MyItem = MenuItem(#PB_Any, "Texte de mon élément")
Les handles de sous-menus peuvent être utilisés exactement comme des ID. Cela signifie que l'on peut modifier un titre de menu ainsi :

Code : Tout sélectionner

MyTitle = MenuTitle("Texte du titre")
SetMenuItemText(MyMenu, MyTitle, "Nouveau texte du titre")
Il n'est donc plus nécessaire d'utiliser les fonctions commençant par SetMenuTitle..., bien qu'elles restent fonctionnelles.

L'ID du menu peut être remplacé par #PB_Default dans toutes les fonctions, sauf celles commençant par SetMenuTitle.... On peut donc écrire :

Code : Tout sélectionner

SetMenuItemText(#PB_Default, MyTitle, "Nouveau texte du titre")
Les ID des éléments et leurs handles sont censés être uniques. Ainsi, les fonctions de cette bibliothèque rechercheront dans quel menu ou PopupMenu se trouve l'ID/handle fourni en paramètre et remplaceront #PB_Default par le bon ID de menu.

Cela est particulièrement utile pour récupérer le texte de la commande activée par l'utilisateur dans la boucle principale du programme :

Code : Tout sélectionner

If WaitWindowEvent() = #PB_Event_Menu
  Define ItemText$ = GetMenuItemText(#PB_Default, EventMenu())
  MessageRequester("Info", "L'utilisateur a choisi : " + ItemText$, 0)
EndIf
Tout obstacle est un point d'appui potentiel.

Bibliothèques PureBasic et autres codes à télécharger :https://www.editions-humanis.com/downlo ... ads_FR.htm
Avatar de l’utilisateur
ZapMan
Messages : 460
Inscription : ven. 13/févr./2004 23:14
Localisation : France
Contact :

Re: SetMenuItemEx [Windows seulement]

Message par ZapMan »

Tutorial à propos des menus:

Pour comprendre ce qui est fait à travers les différentes fonctions de cette bibliothèque (ou bien si vous voulez vous-même bricoler les menus à votre façon), il est important de connaître certains éléments sur la façon dont Windows a organisé cette partie de son interface et comment PureBasic la gère.

À propos de CreateImageMenu()

• L'API Windows offre un ensemble complet de fonctionnalités permettant d'ajouter des menus et des menus contextuels à une application sans avoir à gérer grand-chose. Le texte des éléments de menu peut être stocké avec la fonction API SetMenuItemInfo_() et récupéré avec GetMenuItemInfo_().

• Ces mêmes fonctions permettent également de stocker un pointeur d’image pour un élément de menu, rendant l’existence de la fonction CreateImageMenu() non nécessaire sous Windows. Ces deux fonctions devraient théoriquement être équivalentes sur ce système. Cependant, pour des raisons probablement historiques (Windows ne prenant en charge les icônes de menu que depuis Windows 2000), PureBasic gère les menus très différemment selon qu'ils sont créés avec CreateMenu() (et CreatePopupMenu()) ou avec CreateImageMenu() (et CreatePopupImageMenu()). Dans le premier cas, les menus utilisent les fonctions Windows standard pour l’ensemble de leurs fonctionnalités. Dans le second cas, les éléments du menu sont définis comme "ownerdrawn" et PureBasic se charge de les dessiner individuellement.

• Lorsqu'un élément de menu est défini comme "ownerdrawn", il est courant de l'associer à un ensemble de données définissant, en plus du texte et de l’image, la couleur du texte, la couleur de fond et éventuellement la police utilisée pour l’affichage.

• En tant que développeurs, si nous souhaitons ajouter des options d'affichage aux éléments d'un menu créé avec CreateImageMenu(), nous sommes confrontés à une difficulté : ces éléments sont déjà définis comme ownerdrawn par PureBasic, et un ensemble de données leur est déjà associé. En tentant d’ajouter des données aux données existantes et de gérer l'affichage de ces éléments de manière personnalisée, d'autres problèmes apparaissent : les fonctions GetMenuItemText() et SetMenuItemText() ne sont pas conçues pour de telles opérations et entraînent des dysfonctionnements. Pire encore, si un menu contextuel est créé avec la fonction CreatePopupImageMenu(), sa gestion par PureBasic interfère avec d'autres menus gérés comme ownerdrawn par notre application, ce qui ne devrait absolument pas être le cas.

• En résumé, la fonction CreateImageMenu() et la manière dont PureBasic gère ses fonctionnalités posent des problèmes insolubles dès que l'on souhaite ajouter des fonctionnalités comme SetMenuItemColor(). J’ai donc décidé de redéfinir cette fonction, ainsi que de nombreuses autres liées aux menus, afin de créer une bibliothèque fiable et fonctionnelle. L'un des avantages de ce choix est que les menus, même s'ils incluent des images, ne sont ownerdrawn que lorsque cela est strictement nécessaire. (Comme nous le verrons plus tard, les menus ownerdrawn présentent une série d'inconvénients.)
Remarque sur la petite flèche à droite des éléments possédant un sous-menu :
—> Sans cette bibliothèque, elle n’a pas la même apparence selon que le menu est créé avec CreateMenu() ou CreateImageMenu(). Dans le premier cas, le thème Windows est appliqué, dans le second, non, car le menu est alors ownerdrawn.
—> Avec cette bibliothèque, CreateMenu() et CreateImageMenu() sont strictement équivalents et il est possible d'ajouter des images aux éléments dans les deux cas. Le thème Windows est appliqué jusqu'à ce qu'une couleur ou une police spécifique soit définie pour un élément de menu. Dans ce cas (et uniquement dans ce cas), le menu devient ownerdrawn et le thème Windows ne peut plus être appliqué, bien que le comportement du menu reste aussi proche que possible de l’original.

À propos des menus ownerdrawn
Les commentaires suivants concernent Windows 11 et les versions antérieures.

• Lorsqu’un élément de menu est défini comme ownerdrawn, son affichage doit être géré par une procédure de rappel (callback) associée à la fenêtre de l'application. Cette procédure reçoit le message #WM_INITMENU (ou #WM_INITMENUPOPUP) lorsque le menu est sur le point d’être affiché pour la première fois, puis le message #WM_MEASUREITEM, qui permet de calculer et d'enregistrer les dimensions du rectangle dans lequel l'élément sera dessiné, et enfin le message #WM_DRAWITEM lorsque l'élément doit être affiché.

• Pour calculer la largeur de l'élément de menu lors du traitement de #WM_MEASUREITEM et le dessiner lors du traitement de #WM_DRAWITEM, la procédure de rappel a besoin de données définissant son texte, son image, ses couleurs et sa police. Ces données sont stockées dans une structure, dont l'adresse est associée à l'élément via SetMenuItemInfo_(). Cette adresse peut ensuite être récupérée avec GetMenuItemInfo_(). Elle est aussi accessible via measureItemStruct\itemData lors du message #WM_MEASUREITEM, et via drawItemStruct\itemData lors du message #WM_DRAWITEM.

• Si une image est associée à un élément ownerdrawn via SetMenuItemInfo_() et #MIIM_BITMAP, le message #WM_MEASUREITEM n'est jamais envoyé à la procédure de rappel. Cela est regrettable et peut être considéré comme une erreur de Windows, mais c'est un fait à prendre en compte. Windows se charge alors de calculer la largeur requise pour afficher l'élément, mais il le fait de manière incorrecte, en ne prenant en compte que l'image, sans considérer l’espace nécessaire pour le texte.

• Par conséquent, lorsqu'un élément est ownerdrawn, l'image qui lui est associée ne doit PAS être enregistrée de manière standard avec SetMenuItemInfo_(). Le champ hbmpItem de la structure MenuItemInfo doit être mis à 0 ou #HBMMENU_CALLBACK afin que le message #WM_MEASUREITEM soit envoyé et traité par la procédure de rappel. L'adresse de l’image doit être stockée dans l’ensemble des données associées à l’élément pour être récupérée lors des messages #WM_MEASUREITEM et #WM_DRAWITEM.

• Le mode "ownerdrawn" des éléments de menu sous Windows souffre d'autres bugs (également signalés par d'autres développeurs sur le web) que j'ai dû découvrir, comprendre et contourner :

• Chaque élément de menu pouvant être défini indépendamment comme ownerdrawn ou non, on pourrait s’attendre à ce que tout continue de fonctionner normalement si certains éléments sont ownerdrawn et d’autres non. Mais ce n'est pas le cas. Dès qu’un seul élément d’un menu est ownerdrawn, l'affichage de tous les autres éléments de ce menu rencontre des problèmes : ils ne sont plus affichés en utilisant le thème Windows actuel et sont décalés vers la droite, avec un décalage qui dépend étrangement de la largeur attribuée à l’élément ownerdrawn. En bref, cela ne fonctionne pas, sauf si l'élément ownerdrawn est le dernier du menu. Si un élément d’un menu est défini comme ownerdrawn, alors tous les éléments de ce menu doivent l’être, même si ce n’est pas nécessaire pour eux.

• Lorsqu’on modifie le texte ou l’image d’un élément de menu-barre qui n’est pas ownerdrawn, la barre de menu est automatiquement redessinée. Lorsqu’on modifie les données d’un élément ownerdrawn, il n’est PAS automatiquement redimensionné et redessiné, alors qu'il devrait l’être. Les messages #WM_MEASUREITEM et #WM_DRAWITEM ne lui sont pas envoyés automatiquement. Comme aucune fonction ResizeMenuItem() explicite n'existe, il faut utiliser une sorte de "hack" pour obtenir un effet équivalent : en appelant SetMenuItemInfo_() avec fMask = #MIIM_BITMAP et hbmpItem = 0, #WM_MEASUREITEM est renvoyé à la procédure de rappel, même si hbmpItem était déjà à zéro avant l’appel de SetMenuItemInfo_(). Toute cette mécanique a quelque chose d'absurde.

• Lorsqu’un élément de barre de menu est défini comme ownerdrawn, le message #WM_MEASUREITEM est normalement envoyé à la procédure de rappel, mais #WM_DRAWITEM ne l’est pas si aucun ID n’a été attribué à la barre de menu. N'importe quel nombre positif peut être utilisé, y compris un ID déjà utilisé par un autre élément (ce qui ne devrait pas fonctionner !). Peu importe, le système ne vérifie ni n’utilise réellement cet ID, mais il en a besoin pour envoyer #WM_DRAWITEM. C'est un autre aspect absurde du système ownerdrawn.

• Lorsqu’un élément est une entrée de sous-menu, son ID devrait être -1. Mais la gestion des menus ownerdrawn sous Windows comporte un bug très rare mais très ennuyeux (contrairement aux barres de menus, ce n'est pas systématique) : lorsqu’un élément a un ID égal à -1, environ une fois sur 200, le message #WM_DRAWITEM n’est pas envoyé à la procédure de rappel et l’élément n'est jamais dessiné. Lorsqu’un élément est victime de ce bug, c’est définitif : même en appelant DrawMenuBar_() plusieurs fois, l’élément défaillant ne sera jamais affiché. Quelque chose dans ses données semble être corrompu sans qu’il soit possible de savoir quoi. Après de nombreux tests épuisants, il apparaît qu’attribuer un ID de 1 à tous les éléments qui sont des entrées de sous-menu corrige ce problème. En réalité, cet ID ne sera pas VRAIMENT attribué à l'élément. Si on teste avec GetMenuItemID_(hmenu, ItemPos) après l’opération, on constate que l'élément a toujours un ID égal à -1. Mais cette bidouille empêche le bug de se produire.

• Dans les versions récentes de Windows, les éléments de la barre de menu sont activés lorsque le curseur de la souris les survole, offrant ainsi une expérience utilisateur fluide et intuitive. Malheureusement, ce comportement ne s’applique pas aux éléments ownerdrawn, qui sont gérés à l’ancienne par Windows. Ces éléments ne réagissent pas au survol de la souris et aucun message #WM_DRAWITEM n’est envoyé à la procédure de rappel de la fenêtre dans ce cas. Pour contourner cette limitation, un message #WM_DRAWITEM est explicitement déclenché lorsque le curseur de la souris survole un élément du menu. Cela est géré dans la procédure de rappel via le message #WM_NCMOUSEMOVE.

• Dans les versions récentes de Windows, la barre de menu devient grisée lorsque la fenêtre devient inactive. Cependant, ce comportement ne se produit généralement pas avec les menus ownerdrawn, car Windows n’envoie pas le message #WM_DRAWITEM aux menus ownerdrawn lors de l’activation ou de la désactivation de la fenêtre. Ce problème a été corrigé en ajoutant un code spécifique dans la procédure de rappel de la fenêtre via le message #WM_ACTIVATE.

• Ces deux derniers points expliquent peut-être pourquoi PureBasic ne définit jamais la barre de menu comme ownerdrawn, même lorsque le menu est créé avec CreateImageMenu(). Cela évite les inconvénients mentionnés précédemment, mais introduit un troisième problème : il devient impossible d’afficher une image dans la barre de menu, même lorsqu’elle est créée avec CreateImageMenu(). En réécrivant les fonctions natives de PureBasic, cette bibliothèque résout tous ces problèmes :

La barre de menu peut désormais afficher des images sans être ownerdrawn.
Si la barre de menu est définie comme ownerdrawn pour permettre l'affichage de couleurs spécifiques ou d’une police particulière, elle continuera de se comporter comme une barre de menu non-ownerdrawn. Si un thème est appliqué, les couleurs du thème seront utilisées par défaut.

À propos de la fonction MenuTitle() de PureBasic

• La fonction native MenuTitle() de PureBasic est essentiellement une version déguisée de la fonction OpenSubMenu(). Lorsqu’on l’utilise pour ajouter un titre au menu principal, elle crée en réalité une entrée de sous-menu, dans laquelle on peut ensuite ajouter des éléments avec la fonction MenuItem(). Du point de vue du développeur, MenuItem() ajoute des éléments dans le même menu où le titre a été ajouté avec MenuTitle(). En réalité, pour Windows, il s'agit de deux menus distincts (un menu et un sous-menu) qui sont imbriqués. Windows gère un sous-menu (ou un menu contextuel) de la même manière qu'un menu standard.

• Contrairement à la fonction OpenSubMenu(), qui retourne un handle vers le sous-menu créé, la fonction native MenuTitle() de PureBasic ne retourne aucun handle. C'est pourquoi, lorsque l'on veut modifier le titre d’un menu avec SetMenuTitleText(), on est obligé de désigner l’élément par sa position dans le menu, ce qui n’est pas très pratique. Cette lacune est corrigée par cette bibliothèque. Désormais, MenuTitle() retourne un handle, et on peut utiliser SetMenuItemText() avec ce handle pour modifier le titre du menu. On peut également lui associer une image via un imageID en second paramètre.

• Le fonctionnement de la fonction native OpenSubMenu() peut être amélioré. Si l’utilisateur oublie d’appeler CloseSubmenu() à la fin de la création d’un menu, et si des opérations intensives de création et destruction de menus (FreeMenu()) ont lieu sans fermeture d’un sous-menu, cela peut finir par provoquer une erreur mémoire. Ce problème se produit précisément lors de la 64e itération d'une boucle de création/destruction de menus. Cette bibliothèque corrige ce problème en forçant un appel à CloseSubmenu() avant chaque CreateMenu(), CreatePopupMenu() et FreeMenu().

À propos de la fonction MenuBar() de PureBasic

• La fonction native MenuBar() de PureBasic ne retourne pas de handle et ne permet pas d’attribuer un ID. Avec cette bibliothèque, c'est désormais possible, ce qui permet d'utiliser cet ID pour attribuer une couleur particulière à une barre de menu.
Tout obstacle est un point d'appui potentiel.

Bibliothèques PureBasic et autres codes à télécharger :https://www.editions-humanis.com/downlo ... ads_FR.htm
Répondre