So what is the non client area? Well it is the area of a windows application that you regularly cannot place controls.
Here is a standard windows application form.
Now in this image, the non client area is going to be covered.
First off we will need a list of windows messages. So here are some common windows messages:
- WM_NULL = 0x0000,
WM_CREATE = 0x0001,
WM_DESTROY = 0x0002,
WM_MOVE = 0x0003,
WM_SIZE = 0x0005,
WM_ACTIVATE = 0x0006,
WM_SETFOCUS = 0x0007,
WM_KILLFOCUS = 0x0008,
WM_ENABLE = 0x000A,
WM_SETREDRAW = 0x000B,
WM_SETTEXT = 0x000C,
WM_GETTEXT = 0x000D,
WM_GETTEXTLENGTH = 0x000E,
WM_PAINT = 0x000F,
WM_CLOSE = 0x0010,
WM_QUERYENDSESSION = 0x0011,
WM_QUERYOPEN = 0x0013,
WM_ENDSESSION = 0x0016,
WM_QUIT = 0x0012,
WM_ERASEBKGND = 0x0014,
WM_SYSCOLORCHANGE = 0x0015,
WM_SHOWWINDOW = 0x0018,
WM_WININICHANGE = 0x001A,
WM_DEVMODECHANGE = 0x001B,
WM_ACTIVATEAPP = 0x001C,
WM_FONTCHANGE = 0x001D,
WM_TIMECHANGE = 0x001E,
WM_CANCELMODE = 0x001F,
WM_SETCURSOR = 0x0020,
WM_MOUSEACTIVATE = 0x0021,
WM_CHILDACTIVATE = 0x0022,
WM_QUEUESYNC = 0x0023,
WM_GETMINMAXINFO = 0x0024,
WM_PAINTICON = 0x0026,
WM_ICONERASEBKGND = 0x0027,
WM_NEXTDLGCTL = 0x0028,
WM_SPOOLERSTATUS = 0x002A,
WM_DRAWITEM = 0x002B,
WM_MEASUREITEM = 0x002C,
WM_DELETEITEM = 0x002D,
WM_VKEYTOITEM = 0x002E,
WM_CHARTOITEM = 0x002F,
WM_SETFONT = 0x0030,
WM_GETFONT = 0x0031,
WM_SETHOTKEY = 0x0032,
WM_GETHOTKEY = 0x0033,
WM_QUERYDRAGICON = 0x0037,
WM_COMPAREITEM = 0x0039,
WM_GETOBJECT = 0x003D,
WM_COMPACTING = 0x0041,
WM_COMMNOTIFY = 0x0044,
WM_WINDOWPOSCHANGING = 0x0046,
WM_WINDOWPOSCHANGED = 0x0047,
WM_POWER = 0x0048,
WM_COPYDATA = 0x004A,
WM_CANCELJOURNAL = 0x004B,
WM_NOTIFY = 0x004E,
WM_INPUTLANGCHANGEREQUEST = 0x0050,
WM_INPUTLANGCHANGE = 0x0051,
WM_TCARD = 0x0052,
WM_HELP = 0x0053,
WM_USERCHANGED = 0x0054,
WM_NOTIFYFORMAT = 0x0055,
WM_CONTEXTMENU = 0x007B,
WM_STYLECHANGING = 0x007C,
WM_STYLECHANGED = 0x007D,
WM_DISPLAYCHANGE = 0x007E,
WM_GETICON = 0x007F,
WM_SETICON = 0x0080,
WM_NCCREATE = 0x0081,
WM_NCDESTROY = 0x0082,
WM_NCCALCSIZE = 0x0083,
WM_NCHITTEST = 0x0084,
WM_NCPAINT = 0x0085,
WM_NCACTIVATE = 0x0086,
WM_GETDLGCODE = 0x0087,
WM_SYNCPAINT = 0x0088,
WM_NCMOUSEMOVE = 0x00A0,
WM_NCLBUTTONDOWN = 0x00A1,
WM_NCLBUTTONUP = 0x00A2,
WM_NCLBUTTONDBLCLK = 0x00A3,
WM_NCRBUTTONDOWN = 0x00A4,
WM_NCRBUTTONUP = 0x00A5,
WM_NCRBUTTONDBLCLK = 0x00A6,
WM_NCMBUTTONDOWN = 0x00A7,
WM_NCMBUTTONUP = 0x00A8,
WM_NCMBUTTONDBLCLK = 0x00A9,
WM_NCXBUTTONDOWN = 0x00AB,
WM_NCXBUTTONUP = 0x00AC,
WM_NCXBUTTONDBLCLK = 0x00AD,
WM_INPUT = 0x00FF,
WM_KEYFIRST = 0x0100,
WM_KEYDOWN = 0x0100,
WM_KEYUP = 0x0101,
WM_CHAR = 0x0102,
WM_DEADCHAR = 0x0103,
WM_SYSKEYDOWN = 0x0104,
WM_SYSKEYUP = 0x0105,
WM_SYSCHAR = 0x0106,
WM_SYSDEADCHAR = 0x0107,
WM_UNICHAR = 0x0109,
WM_KEYLAST = 0x0108,
WM_IME_STARTCOMPOSITION = 0x010D,
WM_IME_ENDCOMPOSITION = 0x010E,
WM_IME_COMPOSITION = 0x010F,
WM_IME_KEYLAST = 0x010F,
WM_INITDIALOG = 0x0110,
WM_COMMAND = 0x0111,
WM_SYSCOMMAND = 0x0112,
WM_TIMER = 0x0113,
WM_HSCROLL = 0x0114,
WM_VSCROLL = 0x0115,
WM_INITMENU = 0x0116,
WM_INITMENUPOPUP = 0x0117,
WM_MENUSELECT = 0x011F,
WM_MENUCHAR = 0x0120,
WM_ENTERIDLE = 0x0121,
WM_MENURBUTTONUP = 0x0122,
WM_MENUDRAG = 0x0123,
WM_MENUGETOBJECT = 0x0124,
WM_UNINITMENUPOPUP = 0x0125,
WM_MENUCOMMAND = 0x0126,
WM_CHANGEUISTATE = 0x0127,
WM_UPDATEUISTATE = 0x0128,
WM_QUERYUISTATE = 0x0129,
WM_CTLCOLOR = 0x0019,
WM_CTLCOLORMSGBOX = 0x0132,
WM_CTLCOLOREDIT = 0x0133,
WM_CTLCOLORLISTBOX = 0x0134,
WM_CTLCOLORBTN = 0x0135,
WM_CTLCOLORDLG = 0x0136,
WM_CTLCOLORSCROLLBAR = 0x0137,
WM_CTLCOLORSTATIC = 0x0138,
WM_MOUSEFIRST = 0x0200,
WM_MOUSEMOVE = 0x0200,
WM_LBUTTONDOWN = 0x0201,
WM_LBUTTONUP = 0x0202,
WM_LBUTTONDBLCLK = 0x0203,
WM_RBUTTONDOWN = 0x0204,
WM_RBUTTONUP = 0x0205,
WM_RBUTTONDBLCLK = 0x0206,
WM_MBUTTONDOWN = 0x0207,
WM_MBUTTONUP = 0x0208,
WM_MBUTTONDBLCLK = 0x0209,
WM_MOUSEWHEEL = 0x020A,
WM_XBUTTONDOWN = 0x020B,
WM_XBUTTONUP = 0x020C,
WM_XBUTTONDBLCLK = 0x020D,
WM_MOUSELAST = 0x020D,
WM_PARENTNOTIFY = 0x0210,
WM_ENTERMENULOOP = 0x0211,
WM_EXITMENULOOP = 0x0212,
WM_NEXTMENU = 0x0213,
WM_SIZING = 0x0214,
WM_CAPTURECHANGED = 0x0215,
WM_MOVING = 0x0216,
WM_POWERBROADCAST = 0x0218,
WM_DEVICECHANGE = 0x0219,
WM_MDICREATE = 0x0220,
WM_MDIDESTROY = 0x0221,
WM_MDIACTIVATE = 0x0222,
WM_MDIRESTORE = 0x0223,
WM_MDINEXT = 0x0224,
WM_MDIMAXIMIZE = 0x0225,
WM_MDITILE = 0x0226,
WM_MDICASCADE = 0x0227,
WM_MDIICONARRANGE = 0x0228,
WM_MDIGETACTIVE = 0x0229,
WM_MDISETMENU = 0x0230,
WM_ENTERSIZEMOVE = 0x0231,
WM_EXITSIZEMOVE = 0x0232,
WM_DROPFILES = 0x0233,
WM_MDIREFRESHMENU = 0x0234,
WM_IME_SETCONTEXT = 0x0281,
WM_IME_NOTIFY = 0x0282,
WM_IME_CONTROL = 0x0283,
WM_IME_COMPOSITIONFULL = 0x0284,
WM_IME_SELECT = 0x0285,
WM_IME_CHAR = 0x0286,
WM_IME_REQUEST = 0x0288,
WM_IME_KEYDOWN = 0x0290,
WM_IME_KEYUP = 0x0291,
WM_MOUSEHOVER = 0x02A1,
WM_MOUSELEAVE = 0x02A3,
WM_NCMOUSELEAVE = 0x02A2,
WM_WTSSESSION_CHANGE = 0x02B1,
WM_TABLET_FIRST = 0x02c0,
WM_TABLET_LAST = 0x02df,
WM_CUT = 0x0300,
WM_COPY = 0x0301,
WM_PASTE = 0x0302,
WM_CLEAR = 0x0303,
WM_UNDO = 0x0304,
WM_RENDERFORMAT = 0x0305,
WM_RENDERALLFORMATS = 0x0306,
WM_DESTROYCLIPBOARD = 0x0307,
WM_DRAWCLIPBOARD = 0x0308,
WM_PAINTCLIPBOARD = 0x0309,
WM_VSCROLLCLIPBOARD = 0x030A,
WM_SIZECLIPBOARD = 0x030B,
WM_ASKCBFORMATNAME = 0x030C,
WM_CHANGECBCHAIN = 0x030D,
WM_HSCROLLCLIPBOARD = 0x030E,
WM_QUERYNEWPALETTE = 0x030F,
WM_PALETTEISCHANGING = 0x0310,
WM_PALETTECHANGED = 0x0311,
WM_HOTKEY = 0x0312,
WM_PRINT = 0x0317,
WM_PRINTCLIENT = 0x0318,
WM_APPCOMMAND = 0x0319,
WM_THEMECHANGED = 0x031A,
WM_HANDHELDFIRST = 0x0358,
WM_HANDHELDLAST = 0x035F,
WM_AFXFIRST = 0x0360,
WM_AFXLAST = 0x037F,
WM_PENWINFIRST = 0x0380,
WM_PENWINLAST = 0x038F,
WM_USER = 0x0400,
WM_REFLECT = 0x2000,
WM_APP = 0x8000
0x0085
This message to calculate the new client area space
0x0083
And this message to tell when the user clicks on a specific part of the non client area
0x0084
So with those three hexadecimal numbers, we can control our non client area! Now how do we use it? Well we first need to create a struct for our Non Client area calculator so here is the struct code:
Code: Select all
/// <summary>
/// The NCCALCSIZE_PARAMS structure contains information that an application can use
/// while processing the WM_NCCALCSIZE message to calculate the size, position, and
/// valid contents of the client area of a window.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct NCCALCSIZE_PARAMS
{
public RECT rect0, rect1, rect2;
public IntPtr lppos;
}
Code: Select all
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
First override the WndProc (Windows Processes) void.
Code: Select all
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
}
Code: Select all
if (m.Msg == 0x0085)
{
//Null Message...
m.Msg = 0x0;
}
else
{
base.WndProc(ref m);
}
Woah! Right? Well if you see what we did, it makes sense. We nulled the Windows Paint Client Area message. So it couldn't paint the client area for the form. The controls and stuff work, but you need to find the caption buttons.
Okay, now depending on your parameters depends on the different results.
Let's remove what we have and now change the code, We are now going to change the non client area's size calculations.
Code: Select all
if (m.Msg == 0x0083&& (int)m.WParam == 1)
{
}
else if (m.Msg == 0x0084 && (int)m.Result == 0)
{
}
else
{
base.WndProc(ref m);
}
Code: Select all
NCCALCSIZE_PARAMS nccsp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
// Adjust the client rectangle to create a new 'client area'
nccsp.rect0.Top += 0;
nccsp.rect0.Bottom += 0;
nccsp.rect0.Left += 0;
nccsp.rect0.Right += 0;
Marshal.StructureToPtr(nccsp, m.LParam, false);
m.Result = IntPtr.Zero;
For Example:
Code: Select all
nccsp.rect0.Top += 30;
nccsp.rect0.Bottom += -8;
nccsp.rect0.Left += 8;
nccsp.rect0.Right += -8;
Add these to the form's class so we can get where you clicked.
Code: Select all
/// <summary>
/// Equivalent to the LoWord C Macro
/// </summary>
/// <param name="dwValue"></param>
/// <returns></returns>
private static int LoWord(int dwValue)
{
return dwValue & 0xFFFF;
}
/// <summary>
/// Equivalent to the HiWord C Macro
/// </summary>
/// <param name="dwValue"></param>
/// <returns></returns>
private static int HiWord(int dwValue)
{
return (dwValue >> 16) & 0xFFFF;
}
Code: Select all
m.Result = HitTestNCA(m.HWnd, m.WParam, m.LParam);
Code: Select all
/// <summary>
/// Used to figure out where the user clicked...
/// </summary>
/// <param name="hwnd"></param>
/// <param name="wparam"></param>
/// <param name="lparam"></param>
/// <returns></returns>
private IntPtr HitTestNCA(IntPtr hwnd, IntPtr wparam, IntPtr lparam)
{
int HTNOWHERE = 0;
int HTCLIENT = 1;
int HTCAPTION = 2;
int HTGROWBOX = 4;
int HTSIZE = HTGROWBOX;
int HTMINBUTTON = 8;
int HTMAXBUTTON = 9;
int HTLEFT = 10;
int HTRIGHT = 11;
int HTTOP = 12;
int HTTOPLEFT = 13;
int HTTOPRIGHT = 14;
int HTBOTTOM = 15;
int HTBOTTOMLEFT = 16;
int HTBOTTOMRIGHT = 17;
int HTREDUCE = HTMINBUTTON;
int HTZOOM = HTMAXBUTTON;
int HTSIZEFIRST = HTLEFT;
int HTSIZELAST = HTBOTTOMRIGHT;
//Getting the point where the mouse clicked...
Point p = new Point(LoWord((int)lparam), HiWord((int)lparam));
Rectangle topleft = RectangleToScreen(new Rectangle(0, 0, 8, 8));
if (topleft.Contains(p))
return new IntPtr(HTTOPLEFT);
Rectangle topright = RectangleToScreen(new Rectangle(Width - 8, 0, 8, 8));
if (topright.Contains(p))
return new IntPtr(HTTOPRIGHT);
Rectangle botleft = RectangleToScreen(new Rectangle(0, Height - 8, 8, 8));
if (botleft.Contains(p))
return new IntPtr(HTBOTTOMLEFT);
Rectangle botright = RectangleToScreen(new Rectangle(Width - 8, Height - 8, 8, 8));
if (botright.Contains(p))
return new IntPtr(HTBOTTOMRIGHT);
Rectangle top = RectangleToScreen(new Rectangle(0, 0, Width, 8));
if (top.Contains(p))
return new IntPtr(HTTOP);
Rectangle cap = RectangleToScreen(new Rectangle(0, 8, Width, 38 - 8));
if (cap.Contains(p))
return new IntPtr(HTCAPTION);
Rectangle left = RectangleToScreen(new Rectangle(0, 0, 8, Height));
if (left.Contains(p))
return new IntPtr(HTLEFT);
Rectangle right = RectangleToScreen(new Rectangle(Width - 8, 0, 8, Height));
if (right.Contains(p))
return new IntPtr(HTRIGHT);
Rectangle bottom = RectangleToScreen(new Rectangle(0, Height - 8, Width, 8));
if (bottom.Contains(p))
return new IntPtr(HTBOTTOM);
return new IntPtr(HTCLIENT);
}
Now you can make a few images to mimic the client area and it will respond correctly. Here is a layout I created...
Note: When adding images, you must use the OnPaint method to draw them. If you use panels, they will get on top of the rectangles used to tell when the user is clicking on the "non client area" dedicated rectangles. This means that your re-size and move commands will be eliminated.
There is a simple one I just made.
Okay, now for everyone running Windows 7 and Vista. There is a lot more that can be done with the fake non client area. You can have a non client area with glass, so you create the illusion of placing items on a non client area as opposed to creating a custom non client area.
We will use 2 external methods to do this. The first being DwmExtendFrameIntoClientArea(...) and the second being DwmDefWindowProc(...).
So above your constructor put these three methods:
Code: Select all
/// <summary>
/// Method used to tell if Destop Composition is enabled, or if Windows Aero is enabled.
/// </summary>
/// <returns></returns>
[DllImport("dwmapi.dll")]
private static extern int DwmIsCompositionEnabled(ref bool IsCompositionEnabled);
Code: Select all
[DllImport("dwmapi.dll")]
private static extern int DwmDefWindowProc(IntPtr hwnd, int Message, IntPtr wParam, IntPtr lParam, out IntPtr LResult);
Code: Select all
[DllImport("dwmapi.dll", PreserveSig = false)]
private static extern void DwmExtendFrameIntoClientArea(IntPtr hwnd, ref Margins margins);
Now we need our Margins structure to contain our data for the windows Aero glass extension.
Code: Select all
/// <summary>
/// A structure that controls the form's glass extension.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct Margins
{
public int Left;
public int Right;
public int Top;
public int Bottom;
}
Code: Select all
public bool IsDesktopCompEnabled()
{
if (Environment.OSVersion.Version.Major < 6)
{
return false;
}
bool ReturnValue = false;
DwmIsCompositionEnabled(ref ReturnValue);
return ReturnValue;
}
Code: Select all
if (IsDesktopCompEnabled())
{
base.WndProc(ref m);
}
else
{
}
Now debug and if you run Windows Aero, you should see a standard form, if you are on basic, you will see that custom form shown before.
Okay, now for the fun stuff! Go to the HitTestNCA method and locate the rectangle which represents the caption bar. It will look like this:
Code: Select all
Rectangle cap = RectangleToScreen(new Rectangle(0, 8, Width, 38 - 8));
if (cap.Contains(p))
return new IntPtr(HTCAPTION);
Code: Select all
if (UseMargins)
{
Rectangle cap = RectangleToScreen(new Rectangle(0, 8, Width, MARGINS.Top - 8));
if (cap.Contains(p))
return new IntPtr(HTCAPTION);
}
else
{
Rectangle cap = RectangleToScreen(new Rectangle(0, 8, Width, 38 - 8));
if (cap.Contains(p))
return new IntPtr(HTCAPTION);
}
Now we will be ready to make some glass! Go back to your WndProc method and delete "base.WndProc(ref m);" because we won't need it right now.
Place this into the If snippet:
Code: Select all
IntPtr result;
int dwmHandled = DwmDefWindowProc(m.HWnd, m.Msg, m.WParam, m.LParam, out result);
if (dwmHandled == 1)
{
m.Result = result;
return;
}
Now place the NCCALCSIZE_PARAMS stuff below that.
Code: Select all
if (m.Msg == 0x0083 && (int)m.WParam == 1)
{
NCCALCSIZE_PARAMS nccsp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
// Adjust the client rectangle to create a new 'client area'
nccsp.rect0.Top += 0;
nccsp.rect0.Bottom += 0;
nccsp.rect0.Left += 0;
nccsp.rect0.Right += 0;
Code: Select all
if (!UseMargins)
{
//Create New Reference...
MARGINS = new Margins();
//Set what client area would be for passing to DwmExtendIntoClientArea
MARGINS.Top = nccsp.rect2.Top - nccsp.rect1.Top;
MARGINS.Left = nccsp.rect2.Left - nccsp.rect1.Left;
MARGINS.Bottom = nccsp.rect1.Bottom - nccsp.rect2.Bottom;
MARGINS.Right = nccsp.rect1.Right - nccsp.rect2.Right;
UseMargins = true;
}
Marshal.StructureToPtr(nccsp, m.LParam, false);
m.Result = IntPtr.Zero;
}
Code: Select all
else if (m.Msg == 0x0084 && (int)m.Result == 0)
{
m.Result = HitTestNCA(m.HWnd, m.WParam, m.LParam);
}
else
{
base.WndProc(ref m);
}
Now override the OnActivate method.
Code: Select all
protected override void OnActivated(EventArgs e)
{
if (IsDesktopCompEnabled() && UseMargins)
{
//Extend!
DwmExtendFrameIntoClientArea(this.Handle, ref MARGINS);
}
base.OnActivated(e);
}
Now you need to paint the region where the form border extends in so override the OnPaint method.
Add this code to the method body:
Code: Select all
if (IsDesktopCompEnabled() && UseMargins)
{
//Clear
e.Graphics.Clear(Color.Transparent);
//Fill Glass area...
e.Graphics.FillRectangle(new SolidBrush(BackColor),
Rectangle.FromLTRB(
MARGINS.Left - 0,
MARGINS.Top - 0,
Width - MARGINS.Right - 0,
Height - MARGINS.Bottom - 0));
//OnPaint
base.OnPaint(e);
}
else
{
base.OnPaint(e);
}
Simple fix: First go to the constructor method body and enable DoubleBuffering.
Second: Override the OnResize method and tell it to refresh.
Now you can place items on the Glass region on your form!
You can change the size of the glass region by adjusting the rectangle size of the NCCALCSIZE_PARAMS.
So here is my final result:
I hope that you learned from this tutorial. Please let me know whether you like this or not by leaving a comment.