Michel Perfetti
Introduction aux ToolStripRenderers
Le framework .net 2.0 fournit un mécanisme qui permet de dessiner soi-même les barres d'outils et de menu
Par Michel Perfetti publié le 25/08/2005 à 08:09, lu 7023 fois, 4 pages
 3 | Démonstration avec un exemple
Téléchargez le code source - 97 Kb
Démonstration avec un exemple
Détail du design
Ceci est un exemple simple d'utilisation d'un ToolStripRenderer personnalisé :



Pour ne pas alourdir le code du ToolStripRenderer, et pour qu'il reste facilement modifiable pour faire vos propres essais, cet exemple ne permet pas une utilisation complète des fonctionnalités des ToolStrips. C'est-à-dire :
  • Les barres d'outils verticales ne sont pas gérées
  • Certains objets qui ne sont pas essentiels à la compréhension du fonctionnement des ToolStripRenderers utilisent les fonctions par défaut du ToolStripRenderer hérité.
Dessin du menu
Le fond du menu est un dégradé de 2 couleurs réalisé avec la classe LinearGradiantBrush:
protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e)
{
    Brush b;
    RectangleF rect = new RectangleF(0,
                                    0,
                                    e.ToolStrip.Bounds.Right - e.ToolStrip.Bounds.Left,
                                    e.ToolStrip.Bounds.Bottom - e.ToolStrip.Bounds.Top);
    if (e.ToolStrip is MenuStrip)
    {            
        b = new LinearGradientBrush(rect, MenuBackgroundColor1, MenuBackgroundColor2, 90, false);        
        e.Graphics.FillRectangle(b, e.Graphics.ClipBounds);
    }

    ...
    
}
OnRenderToolStripBackground est la méthode générique pour dessiner les ToolStrips, c'est pour cela qu'il faut vérifier le type avant de dessiner.

La méthode OnRenderMenuItemBackground permet de dessiner les entrées de menu en fonction :
  • De leur état : normal, sélectionné, pressé
  • De leur position : dans la barre d'outil ou dans un menu déroulant
L'état est déterminé par les propriété Pressed et Selected du ToolStripItem de l'évenement. La position dans le menu est, quant à elle, indiquée par la propriété IsOnDropDown :
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
{
    if (!e.Item.IsOnDropDown)
    {
        if (e.Item.Pressed)
        {
            RenderMenuItemPressed(e);
        }
        else if (e.Item.Selected)
        {
            RenderMenuItemSelected(e);
        }
    }
    else
    {
        if (e.Item.Pressed)
        {
            RenderMenuItemOnDropDownPressed(e);
        }
        else if (e.Item.Selected)
        {

            RenderMenuItemOnDropDownSelected(e);
        }
    }
}
// Item slectionn dans un menu droulant
private void RenderMenuItemOnDropDownSelected(ToolStripItemRenderEventArgs e)
{
    Rectangle rect = new Rectangle(2, 0, e.Item.Width - 4, e.Item.Height - 1);
    e.Graphics.FillRectangle(new SolidBrush(MenuBackgroundColor1), rect);
    e.Graphics.DrawRectangle(new Pen(new SolidBrush(DropDownBorderColor)), rect);
}

// Item click dans un menu droulant
private void RenderMenuItemOnDropDownPressed(ToolStripItemRenderEventArgs e)
{
    Rectangle rect = new Rectangle(2, 0, e.Item.Width - 4, e.Item.Height - 1);
    e.Graphics.FillRectangle(new SolidBrush(DropDownBorderColor), rect);
    e.Graphics.DrawLines(new Pen(DropDownBorderColor), 
        new Point[4] { 
            new Point(rect.Left, rect.Bottom),
            new Point(rect.Left, rect.Top),
            new Point(rect.Right, rect.Top),
            new Point(rect.Right, rect.Bottom) });
}
// Item slectionn dans la barre de menu
private static void RenderMenuItemSelected(ToolStripItemRenderEventArgs e)
{
    Rectangle rect = new Rectangle(0, 0, e.Item.Width - 1, e.Item.Height - 1);
    e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(0xb7, 0xbf, 0xb4)), rect);
    e.Graphics.DrawRectangle(new Pen(new SolidBrush(Color.FromArgb(0xa7, 0xac, 0xa5))), rect);
}
// Item click dans la barre de menu
private void RenderMenuItemPressed(ToolStripItemRenderEventArgs e)
{
    Rectangle rect = new Rectangle(0, 0, e.Item.Width - 1, e.Item.Height - 1);
    e.Graphics.FillRectangle(new SolidBrush(DropDownBackgroundColor), rect);
    e.Graphics.DrawLines(new Pen(DropDownBorderColor), 
        new Point[4] { 
            new Point(rect.Left, rect.Bottom), 
            new Point(rect.Left, rect.Top), 
            new Point(rect.Right, rect.Top), 
            new Point(rect.Right, rect.Bottom) });
}        
Dessin de la barre d'outils
La barre d'outils est un peu plus compliquée à dessiner car au lieu de simplement dessiner un rectangle autour d'un ToolStripItem de la barre, nous allons y afficher des bitmaps :
  • Une image pour la partie gauche de l'item
  • Une pour la partie centrale qui sera étalée sur la largueur du bouton
  • Et une pour la partie gauche
Il y a 3 jeux de boutons, un pour chaque état :
protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e)
{
    Bitmap bouton_g;
    Bitmap bouton_bg;
    Bitmap bouton_d;
    if (!e.Item.IsOnDropDown)
    {
        SelectBoutonImagesForBackground(e, out bouton_g, out bouton_bg, out bouton_d);

        int Y = (e.Item.Height - bouton_g.Height) / 2;
        e.Graphics.DrawImage(bouton_g, 0, Y, bouton_g.Width, bouton_g.Height);
        e.Graphics.DrawImage(bouton_d, e.Item.Width - bouton_d.Width, Y, bouton_d.Width, bouton_d.Height);


        Rectangle fillRect = new Rectangle(bouton_g.Width, 
                                            Y, 
                                            e.Item.Width - bouton_g.Width - bouton_d.Width - 1, 
                                            bouton_bg.Height);
        for (int i = fillRect.Left; i <= fillRect.Right; i++)
        {
            e.Graphics.DrawImage(bouton_bg, i, fillRect.Top, 1, bouton_bg.Height);
        };
    }

}

private static void SelectBoutonImagesForBackground(ToolStripItemRenderEventArgs e, 
                                                    out Bitmap bouton_g, 
                                                    out Bitmap bouton_bg, 
                                                    out Bitmap bouton_d)
{
    if (e.Item.Pressed)
    {
        bouton_g = Properties.Resources.bouton_g_3;
        bouton_d = Properties.Resources.bouton_d_3;
        bouton_bg = Properties.Resources.bouton_bg_3;
    }
    else if (e.Item.Selected)
    {
        bouton_g = Properties.Resources.bouton_g_2;
        bouton_d = Properties.Resources.bouton_d_2;
        bouton_bg = Properties.Resources.bouton_bg_2;
    }
    else
    {
        bouton_g = Properties.Resources.bouton_g_1;
        bouton_d = Properties.Resources.bouton_d_1;
        bouton_bg = Properties.Resources.bouton_bg_1;
    }
}
La partie délicate concerne la position des différents bitmaps par rapport à la taille du bouton et celles des images. Comme je le disais un peu plus haut, il n'y a pas de rendu de fond pour les boutons de la barre d'outils quand la propriété IsOnDropDown est à true : c'est-à-dire quand les boutons sont dans le menu déroulant à droite de la barre d'outils, quand celle-ci est pas assez large pour les contenir tous.

Pour que l'icône ou le texte d'un ToolStripItem ne « touchent » pas les « rebords » de l'item, j'ai du augmenter les marges à droite et à gauche, c'est-à-dire pour toutes les entrées des barres d'outils :
protected override void InitializeItem(ToolStripItem item)
{
    if (!item.IsOnDropDown)
    {
        item.Padding = new Padding(5, 0, 5, 0);                
    }
}
Cette méthode évite de devoir le faire directement dans le designer de la fiche, ce qui est gênant si l'on veut pouvoir changer les designs des ToolStrips.
Remarques générales
La déclaration du ToolStripRenderer est très simple :
    private void Form1_Load(object sender, EventArgs e)
    {
        myRenderer = new MyRenderer();
        menuStrip.Renderer = myRenderer;
        toolStripContainer.TopToolStripPanel.Renderer = myRenderer;
        toolStripBar.Renderer = myRenderer;
        
    }
Par contre il ne faut pas oublier de le déclarer dans chaque panel lorsque l'on utilise un toolStripContainer !

L'ordre des vérifications des types de ToolStrips est important car certaines classes (comme les menus déroulant) héritent d'autres. Il faut donc toujours d'abord travailler sur les classes filles.

L'un des grands avantages des ToolStripRenderer est la possibilité de l'embarquer dans un assemblage avec ses éléments de design et de le réutiliser ensuite pour tous les projets qui en ont besoin. De ce fait, L'application et le rendu de l'interface peuvent être développés quasiment en parallèle.
 
» Démarrer une discussion