Click here to Skip to main content
15,868,154 members
Articles / Multimedia / GDI+

vtExtender: ToolStrip Extender, Renderer, and Customization Class

Rate me:
Please Sign up or sign in to vote.
4.96/5 (35 votes)
16 Feb 2009CPOL6 min read 103.6K   5.3K   138   38
Create ToolStrips with fading buttons, custom tooltips, and expose renderer styles..

system.jpg

In the Beginning..

There was the toolbar, and the developer said: "The toolbar is good, but.." Why are we stuck with just a few styles? How do I make fading buttons like in IE7? Can I customize the tooltips? How does this Renderer class work, and what can I do with it?

First things first, I would like to thank Phil Wright for his Office 2007 renderer example; without his example, this would have taken far longer to write.

The first goal I had when creating this class proved to be the most difficult, that is, how to create a toolbar with fading buttons. The solution took a lot of experimenting to get right (but more on that later..). I also wanted to make a toolbar with buttons that looked raised when selected, and inset when pushed; for that matter, I wanted a flexibility that is lacking in the pre-fabricated implementations offered to us through the designer, and to expose properties that allowed for ad hoc customization of the toolbar's appearance. I started by researching the Renderer class, (not much help there really), and then looking for more examples of the code here and on other developer sites. The only good example I could find was the Office 2007 renderer, so I took it apart, stepping through it line by line.

As with other aspects of .NET, I have come to like the idea of a renderer class, but was disappointed with the implementation. There are just too many aspects of the drawing process that are not adequately exposed. To do this over again, I would probably write a toolbar from scratch; less work, maybe even less code.

OK, enough preamble..

On with the Show..

This is not just an implementation of a renderer class, it is also several embedded classes that serve to facilitate fading buttons, create custom tooltips, and support custom drawing. At the heart of it all though, is a renderer class, which I think can be thought of as a series of events that are sent during various drawing stages for the toolbar elements. Depending on the state, you can either handle the event, or choose to pass it to the default handler.

C#
protected override void OnRenderImageMargin(ToolStripRenderEventArgs e)
{
    // draw the margin area on a menu
    if ((e.ToolStrip is ContextMenuStrip) || 
        (e.ToolStrip is ToolStripDropDownMenu))
        drawImageMargin(e.Graphics, e.AffectedBounds);
    else
        base.OnRenderImageMargin(e);
}

The event arguments contain information pertaining to the item and the drawing stage; these can include size and co-ordinates, a graphics object, the object owner, and information particular to that event. You can bypass the default handler by simply not including the base portion, or in some cases, a handled flag is included in the event arguments.

The hard part can be in figuring out what some of these events actually do, and what to do with the event parameters. In the above snippet, if the toolstrip owner is a menu, the call is forwarded to a routine that draws the menu's image margin.

Here is an example of how a glass style button is drawn when the OnRenderButtonBackground event is called (through an intermediate routine, drawButton):

C#
private void drawGlassButton(Graphics g, RectangleF bounds, int opacity)
{
    // initial bounds
    bounds.Inflate(-1, -1);

    // draw using anti alias
    using (GraphicsMode mode = new GraphicsMode(g, SmoothingMode.AntiAlias))
    {
        // draw the border around the button
        using (GraphicsPath buttonPath = createRoundRectanglePath(
                g,
                bounds.X, bounds.Y,
                bounds.Width, bounds.Height,
                1f))
        {
            using (LinearGradientBrush borderBrush = new LinearGradientBrush(
                    bounds,
                    Color.FromArgb(opacity * 20, ButtonGradientEnd),
                    Color.FromArgb(opacity * 20, ButtonGradientBegin),
                    90f))
            {
                borderBrush.SetSigmaBellShape(0.5f);
                using (Pen borderPen = new Pen(borderBrush, .5f))
                    g.DrawPath(borderPen, buttonPath);
            }

            // create a clipping region
            RectangleF clipBounds = bounds;
            clipBounds.Inflate(-1, -1);
            using (GraphicsPath clipPath = createRoundRectanglePath(
                    g,
                    clipBounds.X, clipBounds.Y,
                    clipBounds.Width, clipBounds.Height,
                    1f))
            {
                using (Region region = new Region(clipPath))
                    g.SetClip(region, CombineMode.Exclude);
            }

            // fill in the edge accent
            using (LinearGradientBrush edgeBrush = new LinearGradientBrush(
                    bounds,
                    Color.FromArgb(opacity * 15, ButtonBorderColor),
                    Color.FromArgb(opacity * 5, Color.Black),
                    90f))
            {
                edgeBrush.SetBlendTriangularShape(0.1f);
                g.FillPath(edgeBrush, buttonPath);
                g.ResetClip();
                bounds.Inflate(-1, -1);
            }

            // fill the button with a subtle glow
            using (LinearGradientBrush fillBrush = 
                new LinearGradientBrush(
                        bounds,
                        Color.FromArgb(opacity * 10, Color.White),
                        Color.FromArgb(opacity * 5, ButtonGradientBegin),
                        LinearGradientMode.ForwardDiagonal))
            {
                fillBrush.SetBlendTriangularShape(0.4f);
                g.FillPath(fillBrush, buttonPath);
                g.ResetClip();
            }
        }
    }
}

As you can see, most of the code is for handling the drawing events, and using them to create the desired visual style. The above example uses clipping, blending, and gradients to create a button with a mirrored edge.

The Highlights

There are five styles in the example application that covers some of the range of this class. The Carbon and System Plus styles use an image for the toolstrip background, the rest use gradients. I left as many of the properties exposed from the class as I could, giving the user a wide range of style options.

Glass

glass.jpg

This style uses a blended gradient of white and silver. The blend as well as the gradient type is exposed in properties, or can be set with the SetGlobalStyle method, or SetStyle method for the type of toolbar (e.g., SetStatusbarStyle()). This example also uses the glass button style from the code above.

Chrome

chrome.jpg

This style uses a centered vertical gradient with Flat style menus, and the 'Glow' button style. The glow effect is achieved by insetting frames with differing transparencies into a rounded graphics path.

Carbon

carbon.jpg

Carbon uses a background image, with glass style buttons and the Office style connected menu.

System Plus

Pictured at the top of the page. This also uses a background image to mimic the IE style, though it could also be done with gradients. I prefer images though.. less processing. Did you know that much of the visual styles on controls (like this form, or a button..) are done by blitting an image over the control?

BlackOut

blackout.jpg

Another style example using a vertical gradient with a custom blend.

Menu Styles

menu.jpg

I put four different menu style options into the class; Vista, Flat, Office, and Custom. The style also determines the menu bar button style. The custom option will draw the button with whatever the button style option is for that toolstrip.

ToolTips

tooltip.jpg

The ToolTip class is not really a tooltip at all, but rather a static window with a fade timer.

C#
public ToolTip(IntPtr hParentWnd)
{
    Type t = typeof(ToolTip);
    Module m = t.Module;
    _hInstance = Marshal.GetHINSTANCE(m);
    _hParentWnd = hParentWnd;

    // create window
    _hTipWnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
                "STATIC", "",
                SS_OWNERDRAW | WS_CHILD | WS_CLIPSIBLINGS | WS_OVERLAPPED,
                0, 0,
                0, 0,
                GetDesktopWindow(),
                IntPtr.Zero, _hInstance, IntPtr.Zero);
    // set starting position
    SetWindowPos(_hTipWnd, HWND_TOP,
                0, 0,
                0, 0,
                SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
    createFonts();
    this.AssignHandle(_hTipWnd);
}

Note that you can create transparent tooltips simply by changing the alpha on the colors used. This works because the static window is itself transparent.

Button Fader

buttonstyle.jpg

As I mentioned near the beginning of the article, I really wanted fading buttons.. gives the toolbar some panache, you know? I started by writing a timer class and creating an instance of the class for each tool item that would fade. The timer class is kept thread safe by synchronizing the timer with the parent so that the calls out from the timer thread exit on the parent thread.

C#
_aTimer.SynchronizingObject = (ISynchronizeInvoke)sender;

The class houses a timer that counts up or down depending on the leave state, but it also contains a reference to the owner item, and an image dc of that item. This is because you can not simply erase the button between cycles as it goes from transparent to opaque, or it would flicker like a bad fluorescent bulb. Instead, you have to first blit an image of the clean button, then draw the semi-transparent button mask. This gives a flicker free illusion of fading. The painting is initiated via events fired from the timer class; events also manage resetting the timer and invalidating the tool item once the fade timer is spent.

What I found though, was that the buttons were invalidating themselves whenever the mouse moved off the item, causing a heavy flicker. I tried a number of ways to get around this, including subclassing the Paint and Invalidate methods, but no luck.. This is one of the problems with some of these subclassed methods, they just don't seem capable of doing certain tasks if those tasks are in any way outside the expected norm. You are forced to go to unmanaged code and dream up slimy hacks. Such is the case here.. after exhausting every inbuilt method, I went to API to find the solution. First, I override the message handler and intercept WM_PAINT:

C#
protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        // bypasses an invalidation thrown by parent when mouse leaves
        // a button, this causes a slight flicker when drawing fader
        // if someone knows a better way, post a message..
        case WM_PAINT:
            if (!bypassPaint())
                base.WndProc(ref m);
            else
                m.Result = new IntPtr(1);
            break;
        default:
            base.WndProc(ref m);
            break;
    }
}

The bypassPaint method first gets the update rectangle, then if it intersects an item that has an active timer, it validates the item bounds and bypasses the paint call.

C#
internal Rectangle updateRegion()
{
    RECT updateRect;
    GetUpdateRect(ToolStrip.Handle, out updateRect, false);
    return new Rectangle(updateRect.Left, updateRect.Top, 
           updateRect.Right - updateRect.Left, 
           updateRect.Bottom - updateRect.Top);
}

internal bool bypassPaint()
{
    Rectangle updateRect = updateRegion();

    foreach (ToolStripItem item in ToolStrip.Items)
    {
        if ((_fader.ContainsKey(item)) && 
            (_fader[item].TickCount > 0) && 
            (!item.IsOnOverflow) && 
            (!_fader[item].Invalidating) && 
            (updateRect.IntersectsWith(item.Bounds)))
        {
            if (((_fader[item].FadeStyle == FadeType.FadeOut) || 
                (_fader[item].FadeStyle == FadeType.FadeFast)))
            {
                RECT validRect = new RECT(updateRect.Left, updateRect.Top, 
                                          updateRect.Right, updateRect.Bottom);
                ValidateRect(ToolStrip.Handle, ref validRect);
                return true;
            }
        }
    }
    return false;
}

Well.. that's about it, enjoy the code, and stay out of trouble..

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Network Administrator vtdev.com
Canada Canada
Network and programming specialist. Started in C, and have learned about 14 languages since then. Cisco programmer, and lately writing a lot of C# and WPF code, (learning Java too). If I can dream it up, I can probably put it to code. My software company, (VTDev), is on the verge of releasing a couple of very cool things.. keep you posted.

Comments and Discussions

 
QuestionI need the class translated to VB.NET Pin
ElektroStudios22-Mar-13 8:26
ElektroStudios22-Mar-13 8:26 
Questionexcellent friend Pin
Member 871442126-Nov-12 3:06
Member 871442126-Nov-12 3:06 
BugBug : Mouse hover fore color of the text Pin
karthikvaduganathan22-Jul-12 20:37
karthikvaduganathan22-Jul-12 20:37 
GeneralMy vote of 5 Pin
Bill SerGio, The Infomercial King4-May-11 20:30
Bill SerGio, The Infomercial King4-May-11 20:30 
Really cool stuff. I used your class in my article on this website at: http://www.codeproject.com/KB/cs/YouTubeVideoScriptor.aspx
GeneralMy vote of 5 Pin
tgis.top17-Dec-10 1:03
tgis.top17-Dec-10 1:03 
GeneralHi Pin
Infinity82730-Nov-10 11:26
Infinity82730-Nov-10 11:26 
RantDamn! You did it again! Pin
Menthol1-Apr-10 14:27
Menthol1-Apr-10 14:27 
GeneralRe: Damn! You did it again! Pin
John Underhill3-Apr-10 3:42
John Underhill3-Apr-10 3:42 
GeneralThank you Pin
Pouriya Ghamary22-Mar-10 1:16
Pouriya Ghamary22-Mar-10 1:16 
GeneralVS2005 version Pin
crunchster26-Feb-10 9:09
crunchster26-Feb-10 9:09 
GeneralRe: VS2005 version Pin
John Underhill26-Feb-10 10:08
John Underhill26-Feb-10 10:08 
QuestionHow to change ForeColor of the StatusStrip [modified] Pin
kazaam24-Aug-09 18:00
kazaam24-Aug-09 18:00 
AnswerRe: How to change ForeColor of the StatusStrip Pin
BenjoD27-Jan-10 3:25
BenjoD27-Jan-10 3:25 
QuestionNeed it in Visual Basic Pin
moy1-Aug-09 2:26
moy1-Aug-09 2:26 
GeneralSome bug fixations! Pin
sameh_serag9-Apr-09 4:31
sameh_serag9-Apr-09 4:31 
GeneralRe: Some bug fixations! Pin
John Underhill11-Apr-09 13:14
John Underhill11-Apr-09 13:14 
GeneralBEYOND AWESOME Pin
sailorturkey28-Mar-09 10:51
professionalsailorturkey28-Mar-09 10:51 
GeneralA bug fix... Pin
sameh_serag11-Mar-09 23:00
sameh_serag11-Mar-09 23:00 
GeneralRe: A bug fix... Pin
John Underhill13-Mar-09 2:38
John Underhill13-Mar-09 2:38 
QuestionI need more clarification about the license Pin
sameh_serag11-Mar-09 5:28
sameh_serag11-Mar-09 5:28 
AnswerRe: I need more clarification about the license Pin
John Underhill11-Mar-09 6:04
John Underhill11-Mar-09 6:04 
BugRe: I need more clarification about the license Pin
Member 871442126-Nov-12 3:04
Member 871442126-Nov-12 3:04 
RantGreat job! Pin
Manuel Quelhas19-Feb-09 6:39
Manuel Quelhas19-Feb-09 6:39 
GeneralRe: Great job! Pin
John Underhill19-Feb-09 8:21
John Underhill19-Feb-09 8:21 
GeneralBTW Pin
John Underhill17-Feb-09 4:10
John Underhill17-Feb-09 4:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.