menu
  Home  ==>  articles  ==>  vcl_rtl  ==>  delphi_vcl_styles   

Syles VCL Delphi - John COLIBRI.

  • résumé : présentation des Styles Vcl : séléction de styles pré-définis, utilisation de Vcl Style Designer, fichiers de style .VSF, chargement de styles à l'éxécution, écriture de tStyleHooks, points à surveiller pour nos propres composants, styles et thèmes Delphi 7
  • mots clé : Delphi Vcl Styles, tStyleManager, SetStyle, tStyleHook, StyleServices, GetElementDetails, RegisterStyleHook, UnregisterStyleHook, UpdateColor
  • logiciel utilisé : Windows XP personnel, Delphi Xe2
  • matériel utilisé : Pentium 2.800 Mhz, 512 Meg de mémoire, 250 Giga disque dur
  • champ d'application : Delphi Xe2 et suivants sous Windows
  • niveau : développeur Delphi
  • plan :


1 - Syles Vcl Delphi Xe2

Les styles permettent de modifier les couleurs et l'apparence des contrôles de toutes les fenêtres d'une application.

Ces systèmes d'apparences, appelés "thèmes", "skins", "styles" permettent par exemple de doter notre fenêtre d'un cadre doré Louis XVI, ou de dessiner nos contrôles avec une apparence noire "graphite".

Il existait déjà, dérivé semble-t-il de tVirtualTreeview de Mike LISCHKE plusieurs systèmes de changement d'apparence, ayant culminé avec les thèmes, y compris l'unité UXTHEME.PAS (2003).




2 - Principe

Les grandes lignes tout d'abord :
  • la couleur, les traits, les images de la forme et des contrôles visuels sont par défaut définis par Windows. Windows permet aussi de définir des familles de styles "Panneau de configuration | Apparences thèmes" et "Panneau de configuration | Apparences | Apparences"

  • nous pouvons demander à Delphi de dessiner en utilisons un style que nous pouvons définir. C'est l'objet de cet article.

    Dans ce cas:

    • les propriétés telles que mon_controle.Color seront ignorées et remplacées par les propriétés que vous avez sélectionnées
    • la mécanique des styles de la VCL place des Hooks qui se chargent d'effectuer le dessin. En gros les méthodes de dessin sont détournées vers la mécanique de Styles Delphi.

      Comme les Hooks sont niché au plus bas niveau Delphi possible, les contrôles standard de la Vcl, la plupart des contrôles des produits tiers, nos propres composants graphiques, utiliseront le style choisi.

  • Cette mécanique n'est pas parfaite :
    • il peu y avoir du scintillement ou quelques irrégularités (stries verticales, par exemple) lors de l'affichage
    • si nous chargeons (ou laissons l'utilisateur choisir) les styles à l'exécution, le résultat peut être un peu inattendu. Les styles sélectionnés à la conception ont le mérite de pouvoir être soigneusement vérifiés
    • certaines zones sont connues pour ne pas bénéficier des styles (OwnerDraw, surbrillance etc)



3 - Définition de Styles par les Options

3.1 - Sélection et Prévisualisation

Voici comment utiliser les options d'un projet pour modifier les styles:
   lancez Delphi (Xe2 ou supérieur)
   créez une application "File | New | Vcl Forms Application - Delphi"
   une application vierge est présentée
   cliquez "Project | Options" (ou par "Project Manager |
   les options sont présentées
   dans le TreeView, sélectionnez "Application | Appearance"
   la page des styles est présentée

delphi_options_styles

Notez que par défaut, le bouton "Preview" (en bas à droite) est Disabled

   sélectionnez un des styles, par exemple "Golden Graphite" (la checkbox doit être en vidéo bleu, pas nécessaire de la cocher)
   le bouton "Preview" est Enabled

delphi_styles_options_hilite_a_style

   cliquez "Preview"
   un exemple de page est présenté avec ce style :

delphi_styles_preview



3.2 - Sélection d'un style

Pour que le style soit utilisé par notre application
   cochez le style
   le style est ajouté dans la liste de la ComboBox "Default Style"
   sélectionnez ce style et fermez le dialogue par "Ok"
   la Forme dans l'IDE garde son apparence
   compilez et exécutez
   la forme prend le style sélectionné

delphi_styles_result




4 - Changement de style à l'exécution

4.1 - Chargement de la liste des styles

Nous pouvons aussi laisser l'utilisateur sélectionner un style. Pour cela nous utilisons le tStyleManager, en appelant SetStyle

Class Procedure SetStyle(Const NameString); overload;



Le nom fourni doit naturellement être un des noms connus de Delphi.

Pour cela, le mieux est de charger tous les noms dans une tListBox, laisser l'utilisateur sélectionner un nom, puis appeler SetStyle.



Les styles sont définis dans des fichiers .VSF (Vcl Style File), situé (sur notre PC) dans :

    C:\Documents and Settings\All Users\Documents\RAD Studio\9.0\Styles

Voici le contenu de nos fichiers .VSF:

delphi_vsf_files

Ce chemin est défini par la variable d'environnement BDSCOMMONDIR "Tools | Options | Environnment Options | Environment Variables"

bdscommondir



4.2 - Projet de changement dynamique de style

Voici comment charger les noms des fichiers .VSF :
   lancez Delphi (Xe2 ou supérieur)
   créez une application "File | New | Vcl Forms Application - Delphi", sauvegardez la sous p_09_vcl_styles
   créez une Procedure qui charge les noms des fichiers de style Vcl:

Uses System.IOUtils// tDirectory

Const k_BDSUSERDIR'C:\Documents and Settings\All Users\Documents\RAD Studio\9.0\'

Procedure list_BDSUSERDIR_vsf_files(p_c_stringstStrings);
    // -- fill ListBoxDiskStyles with the names of styles from our style folder
  Var l_styles_dirl_file_nameString;
  Begin
    p_c_strings.Clear;
    l_styles_dir:= k_BDSUSERDIR'Styles\';
    For l_file_name In TDirectory.GetFiles(l_styles_dir,
        '*'TStyleEngine.FileExtensionDo
      p_c_strings.Add(ExtractFileName(l_file_name));
  End// list_BDSUSERDIR_vsf_files

Modifiez éventuellement la constante définissant le chemin des .VSF

   posez une tListBox, nommez-la vsf_style_listbox_
   posez un tButton qui charge les styles dans ListBox1:

Procedure TForm1.list_BDSUSERDIR_vsf_styles_Click(SenderTObject);                                                                    //
  Begin
    list_BDSUSERDIR_vsf_files(vsf_style_listbox_.Items);
  End// list_registered_styles

   compilez et exécutez
   voici le résultat:

display_vsf_styles

C'est donc actuellement "Windows" le seul style utilisable



Les styles disponibles (chargés et accessible par notre projet) peuvent être affichés par
   tapez la procédure qui liste les styles

Procedure list_registered_styles(p_c_stringstStrings);
    // -- fill ListBoxLoadedStyles with the names of styles
  Var l_style_nameString;
  Begin
    p_c_strings.Clear;

    For l_style_name In TStyleManager.StyleNames Do
      If p_c_strings.IndexOf(l_style_name)= - 1
        Then p_c_strings.Add(l_style_name);
  End// list_registered_styles

   posez une tListBox, nommez-la registered_styles_listbox_, posez un tButton qui la remplit:

Procedure TForm1.list_registered_styles_Click(SenderTObject);
  Begin
    list_registered_styles(registered_styles_listbox_.Items);
  End// list_registered_styles_Click

   compilez et exécutez
   voici le résultat:

display_registered_styles_



Voici comment charger un style à l'exécution :
   écrivez la procédure qui utilise le tStyleManager pour charger un style en mémoire:

Uses Vcl.StylesVcl.Themes;

Procedure load_BDSUSERDIR_vsf_style(Const pk_vsf_style_filenameString);
  Begin
    If FileExists(pk_vsf_style_filename)
      Then TStyleManager.LoadFromFile(pk_vsf_style_filename);
  End// load_BDSUSERDIR_vsf_style

   créez l'événement vsf_style_listbox_.OnClick et chargez le style correspondant, puis réaffiche les styles enregistrés:

Procedure TForm1.vsf_style_listbox_Click(SenderTObject);
  Var l_vsf_filenameString;
  Begin
    With vsf_style_listbox_ Do
    Begin
      l_vsf_filename:= Items[ItemIndex];

      load_BDSUSERDIR_vsf_style(k_BDSUSERDIR'styles\'l_vsf_filename);

      // -- remove from vsf name list
      Items.Delete(ItemIndex);
    End;

    // -- redisplay the registered styles
    list_registered_styles_Click(Nil);
  End// vsf_style_listbox_Click

   compilez et exécutez, cliquez "Golden Graphite"
   voici le résultat:

load_golden_graphite



A présent nous pouvons sélectionner l'un des styles chargés en mémoire:
   créez registered_styles_listbox_.OnClick qui charge le style sélectionné:

Procedure TForm1.registered_styles_listbox_Click(SenderTObject);
  Var l_style_nameString;
  Begin
    With registered_styles_listbox_ Do
      l_style_name:= Items[ItemIndex];

    TStyleManager.SetStyle(l_style_name);
  End// registered_styles_listbox_Click

   posez quelques contrôles "représentatifs" sur Form1 (un tEdit, un tPageControl, tTreeView, un tPopupMenu etc)

   compilez et exécutez, cliquez "Golden Graphite"
   voici le résultat:

set_golden_graphite_style

   ouvrez le popup
   ni la ligne sélectionnée dans registered_styles_listbox_ ni PopupMenu1 ne sont "stylés" (sélection en bleu, menu en gris):

selection_menu_not_styled




5 - Vcl Style Designer

5.1 - VclStyleDesigner.exe

Cet outil permet de créer de nouveaux styles, en général en partant d'un style existant.

Cet .EXE

  • se trouve dans C:\Program Files\Embarcadero\RAD Studio\9.0\bin
  • peut être invoqué depuis "Tools | Vcl Style Designer"
  • est redistribuable: nous pouvons le fournir à des graphistes externes pour qu'ils définissent de nouveaux styles que nous pouvons ensuite utiliser pour nos applications
Notez que certaines parties de la doc wiki mentionne cet utilitaire sous le nom de "Bitmap" Style Designer"



5.2 - Modification d'une image globale

Le Style Designer peut présenter dans un seul .PNG les éléments graphiques utilisés par un style. Pour modifier globalement les couleurs d'un style, il suffit ainsi
  • de charger une ce ces images, de modifier les couleurs du .PNG (avec PaintBrush, Gimp, PaintShopPro ou autre)
  • de sauvegarder cette image, puis la recharger dans le Style Designer
  • de prévisualiser, éventuellement le rendu
  • de sauvegarder le résultat dans un nouveau fichier .VSF que nous pouvons ensuite utiliser comme n'importe quel style pré-défini par Delphi


Voici un exemple de modification globale de Golden Graphite
   lancez le Style Designer par "Tools | Vcl Style Designer"
   voici le résultat

vcl_style_designer

   chargez un style existant, par exemple "Golden Graphite", en cliquant "File | Open" et cherchez un style dans

    C:\Documents and Settings\All Users\Documents\RAD Studio\9.0\Styles

   dans le TreeView de gauche, cliquez "Images | Style.png"

   voci les éléments de style:

golden_graphite_style_png

   sauvegardez l'image .PNG pour pouvoir la modifier. Nous avons utilisé un répertoire \my_png_styles et un nom "my_style.png"

Donc cliquez l'icône "export" (ou par "Image | Export", fournissez le répertoire et sélectionnez "Export"

   voici le répertoire

export_style_png

   cliquez "export"

   voici l'image rechargée dans PaintBrush:

export_style_png_in_paintbrush



Nous pouvons alors modifier les couleurs de ce .png avec n'importe quel utilitaire graphique.
   inversez les couleurs
   voici l'image inversée par Paintbrush :

export_style_png_inverted_by_paintbrush

   sauvegardez sous MY_BLUE_STYLE.PNG


Importons l'image dans le Style Designer:
   cliquez "Update"
   sélectionnez le fichier MY_BLUE_STYLE.PNG
   un dialogue affiche l'image, avec des possibilités de masquage (que nous n'allons pas utiliser)

update_style_png

   cliquez "Ok"

   l'image est affichée dans le Style Designer

   donnez un nom unique à ce style en cliquant "TreeView | Objects | Inspecteur d'Objet | Name"
   voici le nom choisi

set_style_name

   sauvegardez sous forme d'un fichier .VFS par "File | Save As | my_blue_style"
   copiez le fichier dans

    C:\Documents and Settings\All Users\Documents\RAD Studio\9.0\Styles

   lancez l'exécutable de démo précédent et utilisez le style dans notre application Delphi:

setstyle_my_blue_style



5.3 - Changement d'autres éléments de style

Nous avons simplement modifié globalement toutes les couleurs de l'image globale.

Nous pouvons aussi modifier chaque élément individuellement en utilisant les autres éléments du TreeView du Style Designer

Pour faire court

  • en développant "Object", nous pouvons sélectionner chaque type d'objet (un tButton, tCheckBox, tRadioButton) et modifier ses éléments de style

    Ici nous avons modifié la couleur du texte d'un tRadioButton

    style_elements_objects

  • en sélectionnant Fonts nous modifions les polices

  • "Colors" et "SysColors" permettent de changer les couleurs

    syscolor_change



Nous pouvons vérifier le résultat en sélectionnant "Style | Test" (ou l'icône verte) :

style_test



Pour tout dire, à part changer

  • l'image d'un objet ("Objects | RadioButton | Checked | Bitmap" puis clic droit et clic gauche pour définir l'image à utiliser)
  • la couleur "Colors | EditDisabled"
  • la couleur système
nous ne sommes pas arrivés à modifier les autres éléments (dans "Objects | RadioButton etc).

Vous aurez certainement plus de patience pour lire la doc

  http://docwiki.embarcadero.com/RADStudio/XE3/en/Creating_a_Style_using_the_Bitmap_Style_Designer




6 - Vcl tStyleHook

Pour modifier les éléments de style de façon plus fine, nous pouvons écrire une Classe dérivée de tStyleHook en modifiant les éléments dessinés.

Il existe déjà toute une hiérarchie de tStyleHook présente dans Vcl.StdCtrls:

  • tEditStyleHook
  • tMemoStyleHook


6.1 - tEditStyleHook

Le mieux pour comprendre ces tStyleHook est de désosser tEditStyleHook. Voici une partie du code :

Type TEditStyleHook=
         Class(TStyleHook)
           Strict Private
             Procedure UpdateColors;
           Strict Protected
             Procedure PaintNC(CanvasTCanvas); Override;
             Procedure WndProc(Var MessageTMessage); Override;
           Public
             Constructor Create(AControlTWinControl); Override;
         End// TEditStyleHook

// -- TEditStyleHook

Constructor TEditStyleHook.Create(AControlTWinControl);
  Begin
    Inherited;

    OverridePaintNC:= True;
    OverrideEraseBkgnd:= True;
    UpdateColors;
  End// Create

Procedure TEditStyleHook.UpdateColors;
  Const k_style_color_arrayArray[BooleanOf TStyleColor=
            (scEditDisabledscEdit);
        k_style_font_arrayArray[BooleanOf TStyleFont=
            (sfEditBoxTextDisabledsfEditBoxTextNormal);
  Var l_c_custom_style_servicesTCustomStyleServices;
  Begin
    l_c_custom_style_services:= StyleServices;

    Brush.Color:= l_c_custom_style_services.GetStyleColor(
        k_style_color_array[Control.Enabled]);
    FontColor:= l_c_custom_style_services.GetStyleFontColor(
        k_style_font_array[Control.Enabled]);
  End// UpdateColors

Procedure TEditStyleHook.PaintNC(CanvasTCanvas);
  Var l_c_themed_element_detailsTThemedElementDetails;
      l_rectangleTRect;
  Begin
    If StyleServices.Available
      Then
        Begin
          l_c_themed_element_details:= StyleServices.GetElementDetails(
              teEditBorderNoScrollNormal);
          l_rectangle:= Rect(0, 0, Control.WidthControl.Height);
          InflateRect(l_rectangle, - 2, - 2);
          ExcludeClipRect(Canvas.Handlel_rectangle.Leftl_rectangle.Top,
              l_rectangle.Rightl_rectangle.Bottom);

          StyleServices.DrawElement(Canvas.Handlel_c_themed_element_details,
              Rect(0, 0, Control.WidthControl.Height));
        End;
  End// PaintNC

Procedure TEditStyleHook.WndProc(Var MessageTMessage);
  Begin
    Case Message.Msg Of
      CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
          Begin
            SetTextColor(Message.WParamColorToRGB(FontColor));
            SetBkColor(Message.WParamColorToRGB(Brush.Color));
            Message.Result:= LRESULT(Brush.Handle);
            Handled:= True;
          End;

      CM_ENABLEDCHANGED:
          Begin
            UpdateColors;

            Handled:= False// Allow control to handle message
          End
      Else Inherited WndProc(Message);
    End;
  End// WndProc

// -- class constructor registration

Class Constructor TCustomEdit.Create;
  Begin
    TCustomStyleEngine.RegisterStyleHook(TCustomEditTEditStyleHook);
  End;

et:

  • UpdateColors a pour vocation de lire les valeurs des couleurs et polices correspondant à certains états du contrôle.

    tCustomStyleServices est une Classe qui nous permet de récupérer les valeurs provenant du style (éventuellement modifiées par le Style Designer).

    Pour savoir quelle couleur devra être utilisée si le tEdit est Enabled ou non, nous utilisons

    If Control.Enabled
      Then Brush.Color:= l_c_custom_style_services.GetStyleColor(scEditDisabled)
      Else Brush.Color:= l_c_custom_style_services.GetStyleColor(scEdit);

    "UpdateColor" veut donc dire "mettez à jour la valeur tEditStyleHook.Brush.Color à partir de la table des couleurs de ce style.

    Ces couleurs ainsi mises à jour pourront alors être utilisées pour dessiner le contrôle par la suite

  • PaintNC dessine les bordures en utilisant StyleServices.DrawElement

  • la procédure WndProc analyse le type de message
    • si Enabled a changé, les valeurs de Brush.Color ou FontColor sont lues dans la table de style
    • si le message concerne le dessin (la couleur est la couleur par défaut ou une couleur spécifiée par l'utilisateur) les couleurs initialisées par UpdateColor sont utilisées pour effectuer le dessin
  • ce tEditStyleHook est utilisée par un Class Constructor (cf Allen BAUER)


6.2 - Utilisation de tStyleHook

Le principe semble donc être le suivant
  • nous modifions les valeurs de polices et couleurs dans le Style Designer, pour pouvoir les charger dans UpdateColor de notre tStyleHook
  • nous créons notre Classe t_xxx_style_hook dérivée de tStyleHook, et même en général d'un tStyleHook existant pour n'avoir à modifier que quelques propriétés marginales
  • dans le programme qui utilise ce tStyleHook
    • nous l'enregistrons notre tStyleHook

      tStyleManager.Engine.RegisterStyleHook(t_yyyt_xxx_style_hook);

    • si nous chargeons un style (comme montré précédemment) ce style utilisera nos affichages

    • nous pouvons éventuellement retirer le hook par UnRegisterStyleHook


Notez qu'il existe aussi un message cm_StyleChanged. Il peut être utilisé pour récupérer les valeurs du style, puis changer les couleurs du composant pour éviter le scintillement. Important si change de forme, ou utilise des tPageControl : si la couleur original était clWindow ou noir, si affiche avec un style coloré, aura des scintillements (le composant est d'abord peint par Windows avec les couleurs originales, puis les styles sont surajoutés)



6.3 - Premier essai

Pour comprendre ce fonctionnement, nous avons simplement
  • changé quelques polices et couleurs de notre style bleu:

    50_set_focused_edit_font

    51_set_disabled_edit_color

    en le sauvegardant sous "My Edit Style"

  • défini un tStyleHook dérivé de tEditStyleHook
    • en affichant ce qui se passe ici ou là
    • en forçant la couleur clFuchsia pour l'affichage du fond :

      Constructor tMyEditStyleHook.Create(AControlTWinControl);
        Begin
          Inherited;
          OverrideEraseBkgnd:= True;
          UpdateColors;
        End// Create

      Procedure tMyEditStyleHook.do_display(p_textString);
        Begin
          display(Format('%-6s %s', [Control.Namep_text]));
        End// do_display

      Procedure tMyEditStyleHook.UpdateColors;
        Const k_style_color_arrayArray[BooleanOf TStyleColor=
                  (scEditDisabledscEdit);
              k_style_font_arrayArray[BooleanOf TStyleFont=
                  (sfEditBoxTextDisabledsfEditBoxTextNormal);
        Var l_c_custom_style_servicesTCustomStyleServices;
        Begin
          l_c_custom_style_services:= StyleServices;

          Brush.Color:= l_c_custom_style_services.GetStyleColor(
              k_style_color_array[Control.Enabled]);
          FontColor:= l_c_custom_style_services.GetStyleFontColor(
              k_style_font_array[Control.Enabled]);
          do_display(Format('UpdateColors brush %8x  font %8x',
              [Brush.ColorFontColor]));
        End// UpdateColors

      Procedure tMyEditStyleHook.WndProc(Var MessageTMessage);
        Begin
          Case Message.Msg Of
            CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
                Begin
                  do_display(Format('cn %s', [f_display_cn(Message.Msg)]));
                  SetTextColor(Message.WParamColorToRGB(FontColor));
                  SetBkColor(Message.WParamclFuchsia); // <============

                  Message.Result:= LRESULT(Brush.Handle);
                  Handled:= True;
                End;

            CM_ENABLEDCHANGED:
                Begin
                  do_display('cm_enabledchanged');
                  UpdateColors;

                  Handled:= False// Allow control to handle message
                End
            Else Inherited WndProc(Message);
          End;
        End// WndProc


  • et le programme principal
    • utilise notre chargement dynamique de style
    • contient
      • un tEdit avec les couleurs par défaut ("static")
      • un tEdit avec une tEdit.Color clAqua
    • un bouton permet l'enregistrement du style, un bouton permet le désenregistrement

      Type TWinControlClassClass(TWinControl);

      Constructor TEditStyleHookColor.Create(AControlTWinControl);
        Begin
          Inherited;
          UpdateColors;
        End// Create

      Procedure TEditStyleHookColor.do_display(p_textString);
        Begin
          display(Format('%-6s %s', [Control.Namep_text]));
        End// do_display

      Procedure TEditStyleHookColor.UpdateColors;
        Var LStyleTCustomStyleServices;
        Begin
          If Control.Enabled
            Then
              Begin
                Brush.Color:= TWinControlClass(Control).Color;
                FontColor:= TWinControlClass(Control).Font.Color;
              End
            Else
              Begin
                LStyle:= StyleServices;

                Brush.Color:= LStyle.GetStyleColor(scEditDisabled);
                FontColor:= LStyle.GetStyleFontColor(sfEditBoxTextDisabled);
              End;
        End// UpdateColors

      Procedure TEditStyleHookColor.WndProc(Var MessageTMessage);
        Begin
          Case Message.Msg Of
            CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
                Begin
                  UpdateColors;

                  SetTextColor(Message.WParamColorToRGB(FontColor));
                  SetBkColor(Message.WParamColorToRGB(Brush.Color));
                  Message.Result:= LRESULT(Brush.Handle);
                  Handled:= True;
                End;
            CM_ENABLEDCHANGED:
                Begin
                  UpdateColors;

                  Handled:= False;
                End
            Else Inherited WndProc(Message);
          End;
        End// WndProc

    • une tCheckBox permet de basculer Edit1.Enabled et Edit2.Enabled
  • voici la forme après enregistrement du hook et chargement du style "Windows" : rien de particulier

    52_tedistylehook_back_start

  • nous sélectionnons le style "My Edit Style", et cochons "Edit1.Enabled" :

    53_tedistylehook_back_load_style



Notez que
  • naturellement nous voyons le violet du fond, et l'orange pour l'Edit inhibé. Mais nous ne voyons pas notre police italique, et l'affichage n'est pas parfait


6.4 - tStyleHook pour changer le fond sélectionnée

Nous avons emprunté à Rodrigo RUZ, le grand gourou des styles Vcl, un exemple qui colorie la couleur du fond d'un tEdit :

Type TWinControlClassClass(TWinControl);

Constructor TEditStyleHookColor.Create(AControlTWinControl);
  Begin
    Inherited;
    UpdateColors;
  End// Create

Procedure TEditStyleHookColor.do_display(p_textString);
  Begin
    display(Format('%-6s %s', [Control.Namep_text]));
  End// do_display

Procedure TEditStyleHookColor.UpdateColors;
  Var LStyleTCustomStyleServices;
  Begin
    If Control.Enabled
      Then
        Begin
          Brush.Color:= TWinControlClass(Control).Color;
          FontColor:= TWinControlClass(Control).Font.Color;
        End
      Else
        Begin
          LStyle:= StyleServices;

          Brush.Color:= LStyle.GetStyleColor(scEditDisabled);
          FontColor:= LStyle.GetStyleFontColor(sfEditBoxTextDisabled);
        End;
  End// UpdateColors

Procedure TEditStyleHookColor.WndProc(Var MessageTMessage);
  Begin
    Case Message.Msg Of
      CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
          Begin
            UpdateColors;

            SetTextColor(Message.WParamColorToRGB(FontColor));
            SetBkColor(Message.WParamColorToRGB(Brush.Color));
            Message.Result:= LRESULT(Brush.Handle);
            Handled:= True;
          End;
      CM_ENABLEDCHANGED:
          Begin
            UpdateColors;

            Handled:= False;
          End
      Else Inherited WndProc(Message);
    End;
  End// WndProc

Et voici le résultat après enregistrement de ce hook, avec notre style

54_tedistylehook_back_background



6.5 - StyleServices

StyleServices a quelques méthodes que nous pouvons utiliser
  • IsSystemStyle : faux si utilise un style custom (Aqua Graphite etc)
  • pour récupérer les "programmatic color" qui ont été ajustées dans le Style Designer, nous utilisons GetStyleColor

    Les valeurs possibles sont:

      TStyleColor = (scBorderscButtonDisabledscButtonFocusedscButtonHot,
        scButtonNormalscButtonPressedscCategoryButtonsscCategoryButtonsGradientBase,
        scCategoryButtonsGradientEndscCategoryPanelGroupscComboBox,
        scComboBoxDisabledscEditscEditDisabledscGridscGenericBackground,
        scGenericGradientBasescGenericGradientEndscHintGradientBase,
        scHintGradientEndscListBoxscListBoxDisabledscListViewscPanelscPanelDisabled,
        scSplitterscToolBarGradientBasescToolBarGradientEndscTreeViewscWindow);

    Ces constantes sont utilisées pour récupérer la couleur de fond d'un élément de style, puis pour coordonner les dessins d'autres éléments d'un composant custom

    Pour un tButton, nous pouvons utiliser, par exemple scButtonNormal pour le bouton relâché, et scButtonPressed pour le bouton enfoncé

    Notez que tous les contrôles ne sont pas dans ce énuméré. Nous n'avons rien, par exemple, pour un tMemo. Il faudra donc utiliser les énumérés d'un tEdit

  • GetStyleFontColor, qui a beaucoup plus de valeurs :

    tStyleFont = (
        sfButtonTextDisabledsfButtonTextFocusedsfButtonTextHotsfButtonTextNormalsfButtonTextPressed,
        sfCaptionTextInactivesfCaptionTextNormal,
        sfCategoryPanelGroupHeaderHotsfCategoryPanelGroupHeaderNormalsfCatgeoryButtonsCategoryNormal
        sfCatgeoryButtonsCategorySelected,
        sfCatgeoryButtonsHotsfCatgeoryButtonsNormalsfCatgeoryButtonsSelected,
        sfCheckBoxTextDisabledsfCheckBoxTextFocusedsfCheckBoxTextHotsfCheckBoxTextNormal,
        sfCheckBoxTextPressed,
        sfComboBoxItemDisabledsfComboBoxItemFocusedsfComboBoxItemHotsfComboBoxItemNormal
        sfComboBoxItemSelected,
        sfEditBoxTextDisabledsfEditBoxTextFocusedsfEditBoxTextHotsfEditBoxTextNormal
        sfEditBoxTextSelected,
        sfGridItemFixedHotsfGridItemFixedNormalsfGridItemFixedPressedsfGridItemNormal
        sfGridItemSelected,
        sfGroupBoxTextDisabledsfGroupBoxTextNormal,
        sfHeaderSectionTextDisabledsfHeaderSectionTextHotsfHeaderSectionTextNormal
        sfHeaderSectionTextPressed,
        sfListItemTextDisabledsfListItemTextFocusedsfListItemTextHotsfListItemTextNormal
        sfListItemTextSelected,
        sfMenuItemTextDisabledsfMenuItemTextHotsfMenuItemTextNormalsfMenuItemTextSelected,
        sfPanelTextDisabledsfPanelTextNormal,
        sfPopupMenuItemTextDisabledsfPopupMenuItemTextHotsfPopupMenuItemTextNormal
        sfPopupMenuItemTextSelected,
        sfRadioButtonTextDisabledsfRadioButtonTextFocusedsfRadioButtonTextHotsfRadioButtonTextNormal
        sfRadioButtonTextPressed,
        sfSmCaptionTextInactivesfSmCaptionTextNormal,
        sfStatusPanelTextDisabledsfStatusPanelTextNormal,
        sfTabTextActiveDisabledsfTabTextActiveHotsfTabTextActiveNormalsfTabTextInactiveDisabled
        sfTabTextInactiveHotsfTabTextInactiveNormal,
        sfTextLabelDisabledsfTextLabelFocusedsfTextLabelHotsfTextLabelNormal,
        sfToolItemTextDisabledsfToolItemTextHotsfToolItemTextNormalsfToolItemTextSelected,
        sfTreeItemTextDisabledsfTreeItemTextFocusedsfTreeItemTextHotsfTreeItemTextNormal
        sfTreeItemTextSelected,
        sfWindowTextDisabledsfWindowTextNormal
      );

  • GetStyleSystemFont est utilisé pour récupérer une couleur qui correspond à une couleur système

    Pour utiliser ces couleurs, fournit comme paramètre une couleur connue et la fonction retourne la couleur utilisée par le style actuellement chargé

    my_actual_color:= StyleServices.GetStyleSystemColor(clBtnFace



6.6 - Parties non stylées

Comme indiqué auparavant
  • les tMainMenu sont stylés
  • less tPopupMenu le sont pas
  • toutes les parties d'un contrôle ne sont pas toujours stylés, comme, par exemple Toolbar.BorderWidth


Mentionnons aussi que toutes les formes utilisent le même style



6.7 - Les Styles et vos propres composants

Quelques informations supplémentaires :
  • concernant les éléments que les styles ne prennent pas toujours en compte :
    • couleur tBrush par défaut
      • chaque fois qu'un contrôle est créé, Delphi crée une nouvelle Handle, avec une tBrush par défaut. Toutefois cette brosse ne sera pas synchronisée avec nos styles
      • les styles actuellement ne changent pas tControl.Color. Même si nous utilisons le style "graphite", la valeur de tEdit.Color est toujours clWhite (c'est pourquoi il faut utiliser UpdateColor pour récupérer les couleurs du style)
    • contrôles "OwnerDraw"
      • tout dépend d'où nos couleurs sont récupérées : si nous dessinons sur un tCanvas en utilisant clBtnFace, la couleur sera celle de Windows (et pas celle de la face des tButton de ce style)
    • la couleur des textes sélectionnés (clHighLigth) ne sera pas modifié par les Styles Vcl. Il n'y a pas d'élément de style qui permet de changer cette couleur. Quel que soit le style sélectionné, aura le même style bleu marine de Windows pour la sélection
    • certains contrôles pourraient avoir des parties pour lesquelles Delphi n'a pas prévu l'élément de style. Il faudra donc coder manuellement l'affichage de ces parties


6.8 - Compatibilité arrière

Pour les personnes utilisant déjà les Thèmes depuis Delphi 7 :
  • StyleServices seulement disponibles depuis Xe2
  • pourrait utiliser des $IfDef, mais devient rapidement ingérable
  • le mieux est de définir des fonctions:
    • UsingSystemStyle : retourne True si <Xe2, sinon retourne False
    • ActiveStyleColor : retourne la valeur
    • ActiveSyleFontCOlor
    • ActiveSyleSystemFontCOlor


6.9 - La Classe Vcl.Styles.tStyleManager

Ce diagramme de Classe UML résume les propriétés et méthodes de tStyleManager que nous avons utilisées:

tstylemanager

et pour les tStyleHook

tstylehook




7 - Remarques

Quelques commentaires
  • le choix de styles prédéfinis, ou même le changement global de la bitmap .PNG qui représente tous les styles sont faciles

  • pour les modifications détaillées, c'est une autre histoire :
    • il faut connaître les éléments de style utilisé par les contrôles usuels (scrollbars, pagecontrol etc)
    • je ne suis pas arrivé à modifier les "Objects" simples, mais soit j'ai oublié une manipulation, soit ma version (Xe2, update 1) ne le permet pas
    • la modification de l'image d'un Objet est assez délicate, et peut-être est-elle prévue pour sélectionner des images autres que la .PNG globale chargées pour modifier un composant particulier.
    • les autres items de treeview (fonts, colors, syscolors) semble avoir un double rôle
      • elles sont appliquées aux contrôles usuels (l'orange que nous avons mis dans les syscolor)
      • elles sont destinées à être appelées depuis des tStyleHooks. Dans ce cas, il faut comprendre les correspondances entre
        • à quels éléments Windows associe chaque image ou police
        • quel est le nom Delphi correspondant
        afin de pouvoir les lire et les appliquer à nos propres composants graphiques

    En fait ces tStyleHooks semblent concerner surtout
    • Embarcadero
    • les sociétés spécialisées dans la vente de composants (Raize, Tms etc)
    • les développeurs commr RRUZ qui a passé le temps suffisant pour comprendre ces associations

  • les blogs mentionnent aussi que les contrôles non-windows (tPanel) doivent être traités différemment que les contrôles standards (tButton)

  • dans le Style Designer, le test (F9, la "flèche verte" ou "Style | Test" correspond à VclStyleTest.exe situé dans \BIN. Il aurait été utile d'avoir les sources pour pouvoir y inséerer les composants que nous développons pour tester leur comportement par rapport aux composants standards inclus dans ce test
  • nul doute que la doc générale et les constantes des polices et couleurs se bonifient dans le temps



8 - Télécharger le code source Delphi

Vous pouvez télécharger:

Comme d'habitude:
  • nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
  • tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
  • plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
    Nom :
    E-mail :
    Commentaires * :
     

  • et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.



9 - Références

Le spécialiste incontesté des styles Vcl est Rodrigo RUZ (RRUZ dans les forums) :

Autre tutoriels

Et pour les constructeurs de Classe

L'article original a été écrit en Français (indentation du code pascal original)




10 - L'auteur

John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Created: jan-04. Last updated: mar-2020 - 250 articles, 620 .ZIP sources, 3303 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2020
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
    + web_internet_sockets
    + services_web_
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
      – gestes_delphi
      – delphi_vcl_styles
      – anonymous_methods
      – ecran_tactile_delphi
    + colibri_helpers
    + colibri_skelettons
    + admin
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Migration Delphi migration de versions Delphi, migration Unicode, migration BDE / base de données, migration Internet - Tél 01.42.83.69.36
Formation Programmation Objet Delphi La programmation objet: les types, l'encapsulation, l'héritage, le polymorphisme - 3 jours
Formation Threads Delphi et Multi Tâche Les Threads Delphi : création, synchronisation, communication entre threads - 1 jour