Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add an extra button to the window's title bar?

I've seen that some apps (maybe not .NET apps) that have an extra button on the left from the minimize button on the form's title bar? How can I achieve this in C#?

like image 435
Tony Avatar asked May 15 '10 18:05

Tony


People also ask

How many control buttons are there in title bar of the windows?

Title bars contain at least three small buttons that minimize, maximize or restore, and close the window associated with the title bar. Title bars also contain a context-sensitive Help button.

How many buttons are present on the title bar of Windows 10?

The title bar has just the three buttons to minimize, maximize and restore windows. However, you can add new buttons to the window title bars in Windows 10 with the eXtra Buttons software.

Which are the buttons present on the title bar?

In addition, the Title Bar has the three buttons familiar to Windows ® users: Minimize, Maximize, and Close.


1 Answers

UPDATE: Added a solution that will work with Aero enabled for Windows Vista and Windows 7


***Non-Aero Solution***

The non-client area of a window interaction is managed by a series of non-client specfic messages. For example WM_NCPAINT message is sent to the window procedure to paint the non-client area.

I have never done this from .NET, but I suspect you can overide the WndProc and handle the WM_NC* messages to achieve what you want.

Update: Since I never tried this from .NET I got a few minutes and thought I would give it a quick try.

Trying this on Windows 7, I found that I needed to disable the Themes for the Window if I wanted to OS to do the base rendering of the non-client area. So here is a short test. I used GetWindowDC to get the DC of the entire window rather than GetDCEx, that was just because I could interop that from memory and did not have lookup all the flag constants for GetDcEx. And of course the code could do with more error checking.

using System; using System.Drawing; using System.Windows.Forms; using System.Runtime.InteropServices;  namespace WindowsFormsApplication1 {   public partial class CustomBorderForm : Form   {     const int WM_NCPAINT = 0x85;      [DllImport("user32.dll", SetLastError = true)]     public static extern IntPtr GetWindowDC(IntPtr hwnd);      [DllImport("user32.dll", SetLastError = true)]     public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);      [DllImport("user32.dll", SetLastError = true)]     public static extern void DisableProcessWindowsGhosting();      [DllImport("UxTheme.dll", SetLastError = true, CharSet = CharSet.Unicode)]     public static extern IntPtr SetWindowTheme(IntPtr hwnd, string pszSubAppName, string pszSubIdList);      public CustomBorderForm()     {       // This could be called from main.       DisableProcessWindowsGhosting();        InitializeComponent();     }      protected override void OnHandleCreated(EventArgs e)     {       SetWindowTheme(this.Handle, "", "");       base.OnHandleCreated(e);     }      protected override void WndProc(ref Message m)     {       base.WndProc(ref m);              switch (m.Msg)       {         case WM_NCPAINT:           {             IntPtr hdc = GetWindowDC(m.HWnd);             using (Graphics g = Graphics.FromHdc(hdc))             {               g.FillEllipse(Brushes.Red, new Rectangle((Width-20)/2, 8, 20, 20));             }             ReleaseDC(m.HWnd, hdc);           }           break;       }     }   } } 

Btw. I called DisableProcessWindowsGhosting, this will stop the OS from drawing the non-client area if the application takes too long to respond to windows messages. If you do not do this, then in some situations the border will be renderd but your adornments will not be shown. So that depends on your requirements it that is right for you or not.


***Aero supported solution***

Prompted by the comment from @TheCodeKing, I thought I would take another look at this. It turns out this can be done in a fully documented way while supporting Aero. But it is not for the faint of heart. I will not provide a complete solution here, there are still some kinks to workout, but it does the basics.

This code/solution is based off the Win32 example which can be found at the following location http://msdn.microsoft.com/en-us/library/bb688195(VS.85).aspx

In principal what you need to do is the following.

  • Extend the client area of the window to cover the Frame. This is done by handling the WM_NCCALCSIZE message and returning 0. This gives the Non-Client area a size of 0 and therefore the client area now covers the entire window.
  • Extend the Frame into the client area using DwmExtendFrameIntoClientArea. This gets the OS to render the Frame over the client area.

The above steps will give you a windows with the standard glass frame excluding the system menu (Window Icon) and the title. The minimize, maximize and close buttons will still be drawn and will work. What you will not be able to do is drag or resize the window, this is because the frame is not really there, remember the client area covers the whole window, we have just asked the OS to draw the frame onto the client area.

Now you can draw on the window as normal, even on top of the frame. You can even put controls in the caption area.

Finally, allow the DWM to handle hit-testing for you, by calling DwmDefWindowProc from your WndProc (before you've processed it). It returns a boolean indicating whether the DWM handled the message for you.

like image 120
Chris Taylor Avatar answered Sep 20 '22 14:09

Chris Taylor