Thanks a lot!
In WM_NCCALCSIZE you reduce initial rectangle to exclude your border.
In WM_NCPAINT you draw you border.
In WM_NCHITTEST you can assign certain Windows functionality to your border;
in my case it's just a border.
WM_NCMOUSEMOVE is same for nonclient area as WM_MOUSEMOVE (OnMouseMove) for
client area.
Similarly WM_NCLBUTTONDOWN, WM_NCLBUTTONUP, WM_NCRBUTTONDOWN, etc. -- they
stand for left button down, up, right button down, respectively, for
non-client area.
The point given by WM_NCMOUSEMOVE and WM_NC*BUTTON* is relative to client
area (first point of client area has coordinates (0, 0)) -- thus to get
absolute coordinates, where (0, 0) is in the upper-left corner of your
corner, on the non-client border , you need to offset the point by the width
of the left border and the height of the top border.
When you need to redraw your border (non-client area), you need to call a
special function because Invalidate(), Update() etc. won't do it. I named
the function "invalidateNC." You need to call it after your control's size
has changed (in OnSizeChanged()), too.
I'll show you some code of my control in C#, which has a header at the top
of height headerHeight and border around of width borderWidth:
using System.Runtime.InteropServices;
protected override void WndProc(ref Message m) {
Point pt;
switch ( m.Msg ) {
case (int)WinAPI_WM.WM_NCCALCSIZE :
if ( m.WParam == IntPtr.Zero ) {
WinAPI_NCCALCSIZE_PARAMS csp;
csp = (WinAPI_NCCALCSIZE_PARAMS)Marshal.PtrToStructure( m.LParam,
typeof(WinAPI_NCCALCSIZE_PARAMS));
csp.rgrc0.Top += headerHeight;
csp.rgrc0.Bottom -= borderWidth;
csp.rgrc0.Left += borderWidth;
csp.rgrc0.Right -= borderWidth;
Marshal.StructureToPtr( csp, m.LParam, false );
} else if (m.WParam == new IntPtr(1)) {
WinAPI_NCCALCSIZE_PARAMS csp;
csp = (WinAPI_NCCALCSIZE_PARAMS)Marshal.PtrToStructure( m.LParam,
typeof(WinAPI_NCCALCSIZE_PARAMS));
csp.rgrc0.Top += headerHeight;
csp.rgrc0.Bottom -= borderWidth;
csp.rgrc0.Left += borderWidth;
csp.rgrc0.Right -= borderWidth;
Marshal.StructureToPtr( csp, m.LParam, false );
}
break;
case (int)WinAPI_WM.WM_NCPAINT:
IntPtr hDC = GetWindowDC( m.HWnd );
Graphics g = Graphics.FromHdc( hDC );
// header
using (SolidBrush br = new SolidBrush(textBackColor)) {
g.FillRectangle(br, headerBounds);
}
// border
Rectangle rect = new Rectangle(0, 0, Width, Height);
using (Pen p = new Pen(borderColor, borderWidth)) {
p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
g.DrawRectangle(p, rect);
}
ReleaseDC( m.HWnd, hDC );
break;
case (int)WinAPI_WM.WM_NCHITTEST:
m.Result = (IntPtr)WinAPI_HT.HTBORDER;
return;
case (int)WinAPI_WM.WM_NCMOUSEMOVE:
pt = PointToClient( new Point( m.LParam.ToInt32() ) );
pt.Offset(borderWidth, headerHeight);
// ...
break;
case (int)WinAPI_WM.WM_NCLBUTTONDOWN:
pt = PointToClient( new Point( m.LParam.ToInt32() ) );
pt.Offset(borderWidth, headerHeight);
// ...
break;
}
base.WndProc (ref m);
}
/// <summary>
/// Invalidates non-client area
/// </summary>
private void invalidateNC() {
WinAPI.SetWindowPos(this.Handle, IntPtr.Zero, 0, 0, 0, 0,
WinAPI_SWP.SWP_NOMOVE |
WinAPI_SWP.SWP_NOSIZE |
WinAPI_SWP.SWP_NOZORDER |
WinAPI_SWP.SWP_NOACTIVATE |
WinAPI_SWP.SWP_DRAWFRAME
);
}
///////////// WINAPI functions/structures
[StructLayout(LayoutKind.Sequential)]
public struct WinAPI_NCCALCSIZE_PARAMS {
public WinAPI_RECT rgrc0, rgrc1, rgrc2;
public IntPtr lppos;
}
[DllImport("User32.dll")]
public extern static IntPtr GetWindowDC( IntPtr hWnd );
[DllImport("User32.dll")]
public extern static int ReleaseDC( IntPtr hWnd, IntPtr hDC );
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr
hWndInsertAfter, int X,
int Y, int cx, int cy, uint uFlags);
public struct WinAPI_HT {
public const uint HTERROR = unchecked ((uint)-2);
public const uint HTTRANSPARENT = unchecked ((uint)-1);
public const int HTNOWHERE = 0;
public const int HTCLIENT = 1;
public const int HTCAPTION = 2;
public const int HTSYSMENU = 3;
public const int HTGROWBOX = 4;
public const int HTSIZE = HTGROWBOX;
public const int HTMENU = 5;
public const int HTHSCROLL = 6;
public const int HTVSCROLL = 7;
public const int HTMINBUTTON = 8;
public const int HTMAXBUTTON = 9;
public const int HTLEFT = 10;
public const int HTRIGHT = 11;
public const int HTTOP = 12;
public const int HTTOPLEFT = 13;
public const int HTTOPRIGHT = 14;
public const int HTBOTTOM = 15;
public const int HTBOTTOMLEFT = 16;
public const int HTBOTTOMRIGHT = 17;
public const int HTBORDER = 18;
public const int HTREDUCE = HTMINBUTTON;
public const int HTZOOM = HTMAXBUTTON;
public const int HTSIZEFIRST = HTLEFT;
public const int HTSIZELAST = HTBOTTOMRIGHT;
public const int HTOBJECT = 19;
}
[StructLayout(LayoutKind.Sequential)]
public struct WinAPI_NCCALCSIZE_PARAMS {
public WinAPI_RECT rgrc0, rgrc1, rgrc2;
public IntPtr lppos;
}
public struct WinAPI_SWP {
public const int SWP_NOSIZE = 0x0001;
public const int SWP_NOMOVE = 0x0002;
public const int SWP_NOZORDER = 0x0004;
public const int SWP_NOREDRAW = 0x0008;
public const int SWP_NOACTIVATE = 0x0010;
public const int SWP_FRAMECHANGED = 0x0020; // The frame changed: send
WM_NCCALCSIZE
public const int SWP_DRAWFRAME = SWP_FRAMECHANGED;
public const int SWP_SHOWWINDOW = 0x0040;
public const int SWP_HIDEWINDOW = 0x0080;
public const int SWP_NOCOPYBITS = 0x0100;
public const int SWP_NOOWNERZORDER = 0x0200; // Don't do owner Z ordering
public const int SWP_NOREPOSITION = SWP_NOOWNERZORDER;
public const int SWP_NOSENDCHANGING = 0x0400; // Don't send
WM_WINDOWPOSCHANGING
}
public enum WinAPI_WM {
WM_NCCALCSIZE = 0x0083,
WM_NCHITTEST = 0x0084,
WM_NCLBUTTONDOWN = 0x00A1,
WM_NCLBUTTONUP = 0x00A2,
WM_NCMOUSEMOVE = 0x00A0,
WM_NCPAINT = 0x0085,
WM_LBUTTONDOWN = 0x0201,
WM_MOUSEMOVE = 0x0200
}
lukasz
Użytkownik "Joey Callisay" <hcal...@codex-systems.com> napisał w wiadomości
news:eXTiczVm...@TK2MSFTNGP09.phx.gbl...
Thanks a lot for your response, I'll save this one on my reference project
collection for future use.
"lukasz" <bbl...@op.pl> wrote in message
news:uJ75dLXm...@TK2MSFTNGP14.phx.gbl...
Some questions: What is the definition for WinAPI_RECT, I believe you
forgot to declare this datatype where the Top, Bottom, Right and Left are
set-able unlike System.Drawing.Rectangle structure.
Should I declare it as just a struct based on RECT structure of MFC like
this:
[StructLayout(LayoutKind.Sequential)]
public struct WinAPI_RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
thanks a lot!
"lukasz" <bbl...@op.pl> wrote in message
news:uJ75dLXm...@TK2MSFTNGP14.phx.gbl...
lukasz
Użytkownik "Joey Callisay" <hcal...@codex-systems.com> napisał w wiadomości
news:uIrNwARp...@TK2MSFTNGP15.phx.gbl...
protected override void WndProc(ref Message m)
{
Point pt;
switch(m.Msg)
{
case (int) WinAPI_WM.WM_NCCALCSIZE: // Client Rectangle size manipulation??
//JOEY: Calculate first the border width
this.CalculateBorderWidth();
// The update region is clipped to the window frame. When wParam is 1, the entire window frame needs to be updated
if ( m.WParam == IntPtr.Zero )
{
WinAPI_NCCALCSIZE_PARAMS csp;
csp = (WinAPI_NCCALCSIZE_PARAMS)Marshal.PtrToStructure( m.LParam, typeof(WinAPI_NCCALCSIZE_PARAMS));
csp.rgrc0.Top += nBorderHeight;
csp.rgrc0.Bottom -= nBorderHeight;
csp.rgrc0.Left += nBorderWidth;
csp.rgrc0.Right -= nBorderWidth;
Marshal.StructureToPtr( csp, m.LParam, false );
}
else if (m.WParam == new IntPtr(1))
{
WinAPI_NCCALCSIZE_PARAMS csp;
csp = (WinAPI_NCCALCSIZE_PARAMS)Marshal.PtrToStructure( m.LParam, typeof(WinAPI_NCCALCSIZE_PARAMS));
csp.rgrc0.Top += nBorderHeight;
csp.rgrc0.Bottom -= nBorderHeight;
csp.rgrc0.Left += nBorderWidth;
csp.rgrc0.Right -= nBorderWidth;
Marshal.StructureToPtr( csp, m.LParam, false );
}
break;
case (int) WinAPI_WM.WM_NCPAINT:
this.CalculateBorderWidth();
IntPtr hDC = NativeMethods.GetWindowDC( m.HWnd );
Graphics g = Graphics.FromHdc( hDC );
// border
Border3DStyle enBorderStyle = Border3DStyle.Adjust;
if (m_sdroBackColor == Color.Transparent)
{
m_sdroBackColor = (this.TopLevelControl != null) ? this.TopLevelControl.BackColor : this.BackColor;
}
if (m_enBorderStyle == CFCGlobalResources.CdxEBorderStyles.Custom)
{
// Draw custom border
Rectangle rct = new Rectangle(0, 0, this.Width, this.Height);
ControlPaint.DrawBorder(g, rct,
this.m_sdroOuterBorderColor, m_szOuterBorderSize.Width, ButtonBorderStyle.Solid,
this.m_sdroOuterBorderColor, m_szOuterBorderSize.Height, ButtonBorderStyle.Solid,
this.m_sdroOuterBorderColor, m_szOuterBorderSize.Width, ButtonBorderStyle.Solid,
this.m_sdroOuterBorderColor, m_szOuterBorderSize.Height, ButtonBorderStyle.Solid);
rct.Inflate(this.m_szOuterBorderSize.Width * -1 , this.m_szOuterBorderSize.Height * -1);
ControlPaint.DrawBorder(g, rct,
this.m_sdroInnerBorderColor, m_szInnerBorderSize.Width, ButtonBorderStyle.Solid,
this.m_sdroInnerBorderColor, m_szInnerBorderSize.Height, ButtonBorderStyle.Solid,
this.m_sdroInnerBorderColor, m_szInnerBorderSize.Width, ButtonBorderStyle.Solid,
this.m_sdroInnerBorderColor, m_szInnerBorderSize.Height, ButtonBorderStyle.Solid);
}
else
{
switch (m_enBorderStyle)
{
case CFCGlobalResources.CdxEBorderStyles.Etched:
enBorderStyle = Border3DStyle.Etched;
break;
case CFCGlobalResources.CdxEBorderStyles.Raised:
enBorderStyle = Border3DStyle.RaisedInner;
break;
case CFCGlobalResources.CdxEBorderStyles.Sunken:
enBorderStyle = Border3DStyle.SunkenOuter;
break;
case CFCGlobalResources.CdxEBorderStyles.Flat:
enBorderStyle = Border3DStyle.Flat;
break;
}
Rectangle rct = new Rectangle(0, 0, this.Width, this.Height);
ControlPaint.DrawBorder3D(g, rct, enBorderStyle, Border3DSide.All);
}
NativeMethods.ReleaseDC( m.HWnd, hDC );
this.Invalidate();
break;
case (int)WinAPI_WM.WM_NCHITTEST:
m.Result = (IntPtr)WinAPI_HT.HTBORDER;
return;
case (int)WinAPI_WM.WM_NCMOUSEMOVE:
pt = PointToClient( new Point( m.LParam.ToInt32() ) );
pt.Offset(nBorderWidth, nBorderHeight);
// ...
break;
case (int)WinAPI_WM.WM_NCLBUTTONDOWN:
pt = PointToClient( new Point( m.LParam.ToInt32() ) );
pt.Offset(nBorderWidth, nBorderHeight);
// ...
break;
}
base.WndProc (ref m);
}
Thanx a lot for your help!
Thanks!
"lukasz" <bbl...@op.pl> wrote in message
news:%23nM7iuT...@tk2msftngp13.phx.gbl...
I have same code in both cases because it was debug code that I were to
investigate further.
Użytkownik "Joey Callisay" <hcal...@codex-systems.com> napisał w wiadomości
news:uj5VPcdp...@TK2MSFTNGP14.phx.gbl...
lukasz
Uzytkownik "Joey Callisay" <hcal...@codex-systems.com> napisal w wiadomosci
news:#$5FnRUpE...@TK2MSFTNGP14.phx.gbl...
{
Point pt;
switch(m.Msg)
{
this.CalculateBorderWidth();
{
WinAPI_NCCALCSIZE_PARAMS
csp;
csp.rgrc0.Top +=
nBorderHeight;
csp.rgrc0.Bottom -=
nBorderHeight;
csp.rgrc0.Left +=
nBorderWidth;
csp.rgrc0.Right -=
nBorderWidth;
}
{
WinAPI_NCCALCSIZE_PARAMS
csp;
csp.rgrc0.Top +=
nBorderHeight;
csp.rgrc0.Bottom -=
nBorderHeight;
csp.rgrc0.Left +=
nBorderWidth;
csp.rgrc0.Right -=
nBorderWidth;
}
break;
case (int) WinAPI_WM.WM_NCPAINT:
this.CalculateBorderWidth();
// border
Border3DStyle enBorderStyle =
Border3DStyle.Adjust;
if (m_sdroBackColor ==
Color.Transparent)
{
}
if (m_enBorderStyle ==
CFCGlobalResources.CdxEBorderStyles.Custom)
{
// Draw custom border
ControlPaint.DrawBorder(g,
rct,
this.m_sdroOuterBorderColor, m_szOuterBorderSize.Width,
ButtonBorderStyle.Solid,
this.m_sdroOuterBorderColor, m_szOuterBorderSize.Height,
ButtonBorderStyle.Solid,
this.m_sdroOuterBorderColor, m_szOuterBorderSize.Width,
ButtonBorderStyle.Solid,
this.m_sdroOuterBorderColor, m_szOuterBorderSize.Height,
ButtonBorderStyle.Solid);
ControlPaint.DrawBorder(g,
rct,
this.m_sdroInnerBorderColor, m_szInnerBorderSize.Width,
ButtonBorderStyle.Solid,
this.m_sdroInnerBorderColor, m_szInnerBorderSize.Height,
ButtonBorderStyle.Solid,
this.m_sdroInnerBorderColor, m_szInnerBorderSize.Width,
ButtonBorderStyle.Solid,
this.m_sdroInnerBorderColor, m_szInnerBorderSize.Height, ButtonBorderStyle.S
olid);
}
else
{
switch (m_enBorderStyle)
{
case
CFCGlobalResources.CdxEBorderStyles.Etched:
enBorderStyle = Border3DStyle.Etched;
break;
case
CFCGlobalResources.CdxEBorderStyles.Raised:
enBorderStyle = Border3DStyle.RaisedInner;
break;
case
CFCGlobalResources.CdxEBorderStyles.Sunken:
enBorderStyle = Border3DStyle.SunkenOuter;
break;
case
CFCGlobalResources.CdxEBorderStyles.Flat:
enBorderStyle = Border3DStyle.Flat;
break;
}
ControlPaint.DrawBorder3D(g,
rct, enBorderStyle, Border3DSide.All);
}
NativeMethods.ReleaseDC( m.HWnd, hDC );
this.Invalidate();
break;
case (int)WinAPI_WM.WM_NCHITTEST:
m.Result = (IntPtr)WinAPI_HT.HTBORDER;
return;
case (int)WinAPI_WM.WM_NCMOUSEMOVE:
pt.Offset(nBorderWidth, nBorderHeight);
// ...
break;
case (int)WinAPI_WM.WM_NCLBUTTONDOWN:
pt.Offset(nBorderWidth, nBorderHeight);
// ...
break;
}
base.WndProc (ref m);
}
> Użytkownik "Joey Callisay" <hcal...@codex-systems.com> napisał w
wiadomości
> > > Użytkownik "Joey Callisay" <hcal...@codex-systems.com> napisał w
> > wiadomości
"lukasz" <bbl...@op.pl> wrote in message
news:%23r21u3g...@TK2MSFTNGP10.phx.gbl...
"Joey Callisay" <hcal...@codex-systems.com> wrote in message
news:%237Q7Tzo...@TK2MSFTNGP14.phx.gbl...
Instead of invalidating just the control after painting the custom borders,
I used the overloaded Invalidate(true) method to repaint its child controls
(since it is a container control). It's now working. My final question is,
is it efficient painting?
"Joey Callisay" <hcal...@codex-systems.com> wrote in message
news:OlEQU10q...@TK2MSFTNGP10.phx.gbl...
"Joey Callisay" <hcal...@codex-systems.com> wrote in message
news:en7s1%230qEH...@TK2MSFTNGP12.phx.gbl...
Użytkownik "Joey Callisay" <hcal...@codex-systems.com> napisał w wiadomości
news:u1glVX1q...@TK2MSFTNGP12.phx.gbl...
To make this a bit easier there is another solution: There is a library
available which handles all this for you in a very simple ContainerControl,
named QContainerControlBase. You can adjust the ClientArea and paint on the
Non-ClientArea in the OnPaintNonClientArea event. It even makes it possible
to make the control resizable for the user, so you won’t need a Splitter
control.
The library is called Qios.DevSuite.Components and is developed by a
Microsoft Gold Certified Partner, named QIOS. Next to the possibility to
create ContainerControls it contains a StatusBar, MainMenu, ContextMenu,
DockableWindows and Panels. It supports Windows Themes and the layout and
colors of the controls are fully configurable.
You can download the library from http://developers.qios.nl and give it a try.
Here I give you an example of how you can inherit from QContainerControlBase
and adjust the NonClientAera done:
/// <summary>
/// A container control that inherits from QContainerControlBase so the
ClientArea
/// </summary>
public class MyControl : QContainerControl
{
public MyControl()
{
//Decide whether the user can size the Control. This is a nice feature so
//the user can runtime resize the Control without a Splitter. It updates
//the control immediatly, so the user sees the effect while resizing...
//Because those are true, make sure to override ResizeBorderWidth
this.CanSizeRight = true;
this.CanSizeTop = true;
this.CanSizeBottom = true;
this.CanSizeLeft = true;
}
/// <summary>
/// Override the ClientAreaMargin properties, so the client-area is
adjusted.
/// </summary>
protected override int ClientAreaMarginBottom
{
get { return 10; }
}
protected override int ClientAreaMarginTop
{
get { return 10; }
}
protected override int ClientAreaMarginRight
{
get { return 10; }
}
protected override int ClientAreaMarginLeft
{
get { return 10; }
}
/// <summary>
/// Because the user can resize the Control, give the ResizeBorderWidth.
This is the width
/// of the border that the user can use to resize. Returning 0 means the
user can't resize.
/// </summary>
protected override int ResizeBorderWidth
{
get { return 5; }
}
/// <summary>
/// Because there is a NonClientArea, you can override the
OnPaintNonClientArea to do custom
/// painting. This happens double-buffered, so no flickering!
/// </summary>
protected override void OnPaintNonClientArea(PaintEventArgs e)
{
base.OnPaintNonClientArea (e);
//
//Do you custom drawing here...
//
}
}