views:

238

answers:

3

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#?

+3  A: 

Try the ActiveButtons library: http://www.thecodeking.co.uk/2007/09/adding-caption-buttons-to-non-client.html It appears to be a free download with source.

dthorpe
+4  A: 

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));
            }
            int r = 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.

Chris Taylor
+2  A: 

I think a way to do this would be to handle WM_NCPAINT message (non-client paint) to draw the button, and to handle non-client mouse clicks to know someone clicked on the "button".

Axarydax