可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm attempting to make border-less forms that pop out of a tool bar. I want the user to be able to grab at the bottom-right corner (a "resize handle") and be able to resize the form, but not be able to resize or reposition the form in any other way.
I've heard that I can intercept the WM_NCHITTEST
message sent to the form and set its result to HTBOTTOMRIGHT
which will let the operating system handle the re-sizing of the form, just as if it had a sizable frame. The idea I had was to detect if the mouse pointer had entered a box I defined in the corner and if it did then return the HTBOTTOMRIGHT
result.
This doesn't quite work as I expected it to. I'm able to intercept the message, but it seems the message is only sent when the user positions the mouse cursor on the 1px thick border of the form. That means it works how I want to, if you very precisely position your cursor on the bottom-right edges.
Here is my WndProc
override:
protected override void WndProc(ref Message m)
{
const UInt32 WM_NCHITTEST = 0x0084;
const UInt32 HTBOTTOMRIGHT = 17;
const int RESIZE_HANDLE_SIZE = 40;
bool handled = false;
if (m.Msg == WM_NCHITTEST)
{
Size formSize = this.Size;
Point screenPoint = new Point(m.LParam.ToInt32());
Point clientPoint = this.PointToClient(screenPoint);
Rectangle hitBox = new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE);
if (hitBox.Contains(clientPoint))
{
m.Result = (IntPtr)HTBOTTOMRIGHT;
handled = true;
}
}
if (!handled)
base.WndProc(ref m);
}
Am I doing something wrong or is there a better way to do what I'm trying to do?
Much thanks.
回答1:
I was looking for something similar and Anton's code was a great base. This is what I ended up to have resize work from all sides. I'm unsure a Dictionary
was optimal way to store the hitboxes, but I guess it doesn't matter all that much.
And since my form is filled with controls using Fill as Dock parameters, I just had to add a 5px padding to the Form
for it to work nicely.
protected override void WndProc(ref Message m)
{
const UInt32 WM_NCHITTEST = 0x0084;
const UInt32 WM_MOUSEMOVE = 0x0200;
const UInt32 HTLEFT = 10;
const UInt32 HTRIGHT = 11;
const UInt32 HTBOTTOMRIGHT = 17;
const UInt32 HTBOTTOM = 15;
const UInt32 HTBOTTOMLEFT = 16;
const UInt32 HTTOP = 12;
const UInt32 HTTOPLEFT = 13;
const UInt32 HTTOPRIGHT = 14;
const int RESIZE_HANDLE_SIZE = 10;
bool handled = false;
if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
{
Size formSize = this.Size;
Point screenPoint = new Point(m.LParam.ToInt32());
Point clientPoint = this.PointToClient(screenPoint);
Dictionary<UInt32, Rectangle> boxes = new Dictionary<UInt32, Rectangle>() {
{HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
{HTBOTTOM, new Rectangle(RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
{HTBOTTOMRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
{HTRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE)},
{HTTOPRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
{HTTOP, new Rectangle(RESIZE_HANDLE_SIZE, 0, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
{HTTOPLEFT, new Rectangle(0, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
{HTLEFT, new Rectangle(0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE) }
};
foreach (KeyValuePair<UInt32, Rectangle> hitBox in boxes)
{
if (hitBox.Value.Contains(clientPoint))
{
m.Result = (IntPtr) hitBox.Key;
handled = true;
break;
}
}
}
if (!handled)
base.WndProc(ref m);
}
回答2:
just little modification to your code. I've added WM_MOUSEMOVE
message handling:
protected override void WndProc(ref Message m)
{
const UInt32 WM_NCHITTEST = 0x0084;
const UInt32 WM_MOUSEMOVE = 0x0200;
const UInt32 HTBOTTOMRIGHT = 17;
const int RESIZE_HANDLE_SIZE = 10;
bool handled = false;
if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE )
{
Size formSize = this.Size;
Point screenPoint = new Point(m.LParam.ToInt32());
Point clientPoint = this.PointToClient(screenPoint);
Rectangle hitBox = new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE);
if (hitBox.Contains(clientPoint))
{
m.Result = (IntPtr)HTBOTTOMRIGHT;
handled = true;
}
}
if (!handled)
base.WndProc(ref m);
}
by the way, you can draw system specific window size grip with ControlPaint.DrawSizeGrip Method
http://msdn.microsoft.com/en-us/library/2e1yx2sa.aspx
回答3:
Anton Semenov, i didn't understand your code.
Anyway, i had a problem with the first code of Charles P,
when i maximize the window and then try to change its size - it is being resized.
after that i couldn't fix it again to its normal size, nor maximize it again with the normal max button.
what i did to fix this problem was adding condition inside the 'foreach' loop at the bottom:
protected override void WndProc(ref Message m)
{
const UInt32 WM_NCHITTEST = 0x0084;
const UInt32 WM_MOUSEMOVE = 0x0200;
const UInt32 HTLEFT = 10;
const UInt32 HTRIGHT = 11;
const UInt32 HTBOTTOMRIGHT = 17;
const UInt32 HTBOTTOM = 15;
const UInt32 HTBOTTOMLEFT = 16;
const UInt32 HTTOP = 12;
const UInt32 HTTOPLEFT = 13;
const UInt32 HTTOPRIGHT = 14;
const int RESIZE_HANDLE_SIZE = 10;
bool handled = false;
if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
{
Size formSize = this.Size;
Point screenPoint = new Point(m.LParam.ToInt32());
Point clientPoint = this.PointToClient(screenPoint);
Dictionary<UInt32, Rectangle> boxes = new Dictionary<UInt32, Rectangle>() {
{HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
{HTBOTTOM, new Rectangle(RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
{HTBOTTOMRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, formSize.Height - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE)},
{HTRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE)},
{HTTOPRIGHT, new Rectangle(formSize.Width - RESIZE_HANDLE_SIZE, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
{HTTOP, new Rectangle(RESIZE_HANDLE_SIZE, 0, formSize.Width - 2*RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
{HTTOPLEFT, new Rectangle(0, 0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE) },
{HTLEFT, new Rectangle(0, RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE, formSize.Height - 2*RESIZE_HANDLE_SIZE) }
};
foreach (KeyValuePair<UInt32, Rectangle> hitBox in boxes)
{
if (this.WindowState != FormWindowState.Maximized
&& hitBox.Value.Contains(clientPoint))
{
m.Result = (IntPtr)hitBox.Key;
handled = true;
break;
}
}
}
if (!handled)
base.WndProc(ref m);
}
回答4:
Based on Charles P. solution made some modifications to it, hope it helps others too :)
Small checks and improvements to not declare extra variables every time the windows message is called.
Also checks not painting the grip anchor when windows state is maximized.
I wanted to create a custom control out of it, but i ended up filling the form with this code unfortunately.
Constructor or in the designer file:
this.DoubleBuffered = true;
this.ResizeRedraw = true;
Overriding windows functions:
const uint WM_NCHITTEST = 0x0084, WM_MOUSEMOVE = 0x0200,
HTLEFT = 10, HTRIGHT = 11, HTBOTTOMRIGHT = 17,
HTBOTTOM = 15, HTBOTTOMLEFT = 16, HTTOP = 12,
HTTOPLEFT = 13, HTTOPRIGHT = 14;
Size formSize;
Point screenPoint;
Point clientPoint;
Dictionary<uint, Rectangle> boxes;
const int RHS = 10; // RESIZE_HANDLE_SIZE
bool handled;
protected override void WndProc(ref Message m)
{
if (this.WindowState == FormWindowState.Maximized)
{
base.WndProc(ref m);
return;
}
handled = false;
if (m.Msg == WM_NCHITTEST || m.Msg == WM_MOUSEMOVE)
{
formSize = this.Size;
screenPoint = new Point(m.LParam.ToInt32());
clientPoint = this.PointToClient(screenPoint);
boxes = new Dictionary<uint, Rectangle>() {
{HTBOTTOMLEFT, new Rectangle(0, formSize.Height - RHS, RHS, RHS)},
{HTBOTTOM, new Rectangle(RHS, formSize.Height - RHS, formSize.Width - 2*RHS, RHS)},
{HTBOTTOMRIGHT, new Rectangle(formSize.Width - RHS, formSize.Height - RHS, RHS, RHS)},
{HTRIGHT, new Rectangle(formSize.Width - RHS, RHS, RHS, formSize.Height - 2*RHS)},
{HTTOPRIGHT, new Rectangle(formSize.Width - RHS, 0, RHS, RHS) },
{HTTOP, new Rectangle(RHS, 0, formSize.Width - 2*RHS, RHS) },
{HTTOPLEFT, new Rectangle(0, 0, RHS, RHS) },
{HTLEFT, new Rectangle(0, RHS, RHS, formSize.Height - 2*RHS) }
};
foreach (var hitBox in boxes)
{
if (hitBox.Value.Contains(clientPoint))
{
m.Result = (IntPtr)hitBox.Key;
handled = true;
break;
}
}
}
if (!handled)
base.WndProc(ref m);
}
protected override void OnPaint(PaintEventArgs e)
{
if (this.WindowState != FormWindowState.Maximized)
{
ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor,
this.ClientSize.Width - 16, this.ClientSize.Height - 16, 16, 16);
}
base.OnPaint(e);
}