My Photo

Technical Architect with over 15 years experience in a wide range of technologies.

@TheCodeKing

rss feed

Adding caption buttons to the Non-client area on Vista | Saturday, September 22, 2007

Introduction

This article demonstrates a quick and easy-to-use .Net solution for attaching buttons to the non-client area of the window title bar. This uses the ActiveButtons code library, which is able to preserve the composition and transparency effects in Windows Vista.

Background

The Windows Vista operating system fundamentally changes the way the non-client area is rendered, making it almost impossible (if not actually impossible) to paint onto this area without adversely affecting the look and feel. This is because the new Windows Vista graphics engine renders the non-client area outside of the GDI using the new Desktop Windows Manager (DWM).

The DWM is able to render visual effects such as Aero glass by drawing directly to video memory. In doing this it allows the system to perform complex blending of content from multiple applications without adversely effecting performance.

The DWM does provide an API for customising the way it renders specific windows forms through the use of window attributes and Win32 calls. This provides limited control over the rendering of the non-client areas. For example in a previous article I discussed using the DWM API to extend the non-client into a windows form to increase the glass surface area. To date however there doesn't appear to be a solution for rendering a button cleanly onto the non-client area without loosing the visual effects.

In some ways not being able to draw in the non-client area is a good thing. It leads to cleaner more consistent interfaces and adheres to the recommended Microsoft standards for interface design. That said, sometimes there is a need for more flexibility in application design and a valid argument for making use of the non-client area without loosing the standard look & feel. The library discussed in this article provides one possible solution. The alternative would be to paint the entire title bar yourself including the system buttons, or disable composition effects in the application and use the traditional Win32 calls to draw within the NC area.

The Solution

The ActiveButtons implementation overcomes the limitations of NC rendering in Vista with a unique approach that is not reliant on the DWM API. The solution supports Windows Vista, Windows XP and Windows 2000 from a single codebase, and allows the attachment of buttons to any .Net Windows form along-side the standard minimise, maximise and close buttons.

Example: adding button to title bar

    // hook the windows form load event
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        // get an instance of IActiveMenu for the current form
        IActiveMenu menu = ActiveMenu.GetInstance(this);
        // create a new button instance
        ActiveButton button = new ActiveButton();
        // set some properties
        button.BackColor = Color.LightBlue;
        button.Text = "One";
        // attach a click handler
        button.Click += new EventHandler(button_Click);
        // add the button to the menu instance
        menu.Items.Add(button);
    }

How it works

To achieve the desired effect the library uses some trickery to overlay the buttons onto the windows NC area. This means that the rendering is still preformed within the GDI and the current process, but the DWM still renders the standard title bar complete with any glass and transparency effects where applicable.

To accomplish this the ActiveMenu instance creates an additional transparent Windows Form behind the scenes, which is then attached to the original window. This can done done from within the constructor as follows.

    private ActiveMenu(Form form)
    {
       ...

       // the following line attaches the menu form
       // as a child window of the original

       this.Show(form);

       ...
    }
This new Form is then floated over the title bar area allowing .Net controls to be attached in the usual way. As the DWM treats this as another Window, it still blends the buttons and renders them perfectly with the glass background in situ. Once this is achieved it's just a case of positioning and sizing the buttons correctly. To do this the menu hooks into the parent Form's Resize and Move events, and keeps the child menu correctly positioned.

    ...

    // event handlers are attached in constructor routine

    parentForm.SizeChanged += new EventHandler(parent_Refresh);
    parentForm.Move += new EventHandler(parent_Refresh);

    ...

    // on change of parent form, the menu is repositioned
    protected void parent_Refresh(object sender, EventArgs e)
    {
        this.Top = parentForm.Top;
        this.Left = parentForm.Left + 
                    parentForm.Width - this.Width -
                   (SystemInformation.CaptionButtonSize.Width*3) - 2;
    }

The approach is fairly radical, so feedback and bug reports are greatly appreciated. Feel free to experiment with the ActiveButtons.dll library in your own applications.

Update: The full source code is now available from the download above.

History

  • Initial release, Sept 2007
  • Additional detail added on how it works, Sept 2007
  • Improved demo application and minor bug fixes, Sept 2007
  • Added ActiveButtonsBasic source code, Sept 2007
  • Added full source code, Dec 2009

Further reading


21 comments:

Jonathan Fleury said...

Exactly what I needed. Thanks !

Mohammad said...

Excellent work.
Thank you.

shahid_hazoor said...

I want to add buttons on every (Win 32) window when user opens.
So can you suggest me any Hook or any idea?
please help me out?

Shahid Hazoor
shahid_hazoor@hotmail.com

Anonymous said...

It is definitely possible to draw on the titlebar, as Office 2007 does this to achieve the ribbon quick access toolbar...

Just have a look at:
DwmSetWindowAttribute & DWMWA_ALLOW_NCPAINT

Anonymous said...

Maybe this will help:

http://www.menendezpoo.com/a.php?h=a4971f920f120b

Harry-D said...

Hello,
is this function also available for Visual C++??

YassirYasser said...

THANK YOU MAN MY ALLAH BLESS YOU,
I have looking for this for most a year.

I GIVE YOU ONCE AGAIN ANOTHER THANKS

nicegirl_caro_sex said...

I want add a additional Button to the Explorer.exe to make special Actions. In the end, every user should get no rights and needs to klick on the additional button on next to minimize button and there they can "rename/delete/create files" with special rights.

Should be possible , shouldn't? Teamspeak can do it either!

CrossIron said...

Is there a way of actually granting glass (like the minimize button) to the buttons?

TheCodeKing said...

@nicegirl_caro_sex this library is designed to allow developers to add buttons to a .Net Windows Forms application. It doesn't allow buttons to be attached to an external window such as explorer.exe (although a similar technique could be used to achieve this). It would be difficult to do what you suggest, but probably not impossible. I'd suggest looking at extending the context menu instead with a custom shell extension.

TheCodeKing said...

@CrossIron do you mean to achieve the rollover glow? It's not possible with this library. The standard min/max/close buttons are actually rendered by the DWM to achieve the glow effect. I don't think it's possible to replicate this.

Charles Sicotte said...

Hi,

I had a bug where the ActiveButton would remain shown if I programmatically .Hide() the parent form.

In the file ActiveMenuImpl.cs, in the OnPosition() mehtod, I added the following check:

if (Items.Count > 0)
{
if (parentForm.Visible)
{
this.Opacity = parentForm.Opacity;
}
else
{
this.Visible = false;
}
}

It resolved the problem. I am using Windows Vista Business.

Besides this minor issue, the library is awesome.
Thanks for sharing!

Cheers,
Charles

TheCodeKing said...

@Charles, thanks for the feedback. I'll incorporate this into a new release.

Syddino said...

Hi, there is a bug when I attach ActiveButton to an MDI child form. The button is painted outside the caption area and also it remains visible when I close the form.
Thank you.

TheCodeKing said...

I'm unable to reproduce the MDI issue. Try the latest version on Github and let me know if you still have an issue.

Syddino said...

Hi, I've downloaded version 1.0.1.0 from Github and there is the same problem in MDI child form.
This is a capture of my form:
http://imageshack.us/photo/my-images/526/mdif.jpg/

and this the code for button creation:
Public Sub CreaMenu(objForm As Windows.Forms.Form)
Dim menu As TheCodeKing.ActiveButtons.Controls.IActiveMenu = TheCodeKing.ActiveButtons.Controls.ActiveMenu.GetInstance(objForm)
Dim button As New TheCodeKing.ActiveButtons.Controls.ActiveButton()
button.Text = "..."
menu.ToolTip.SetToolTip(button, "Apri azioni aggiuntive...")
AddHandler button.Click, AddressOf Button1_Click
menu.Items.Add(button)
End Sub

The form is called as a MDI child form:

Dim objForm As New frmTestGiuseppe
objForm.MdiParent = Me
objForm.Show()

Matt Elliot said...

When you make the form topmost (always on top) it is on top of the activebutton. How can I push the button back to the top while keeping the form on top of other windows.

Matt Elliot said...

Syddino has this problem because of the type of form he is using. Activebuttons only seems to work when the formborderstyle is set to sizable.

TheCodeKing said...

Hi, I've checked in a fix to support TopMost. Turns out it's a pretty simple change. Try the latest and let me know if you have any probs.

TheCodeKing said...

BTW what do you mean by "Activebuttons only seems to work when the formborderstyle is set to sizeable". It works in all modes in the demo app. DO you mean with MDI specifically?

Jim said...

I have the same problem as Syddino: The button works fine for the main window, but does not work for MDI child windows. The button is drawn way out of the title bar, several hundred pixels above it.

This is because of the ActiveMenuImpl.CreateParams override setting the window as a child of the desktop, even when the window is a child of it's MDI parent.

Could not find an easy fix, sorry.

Post a Comment