我正在尝试使用DWM为我的窗体制作一个自定义窗口框架。平台是C#WinForms,Pinvoking DWM。
在MSDN上关于用DWM制作自定义窗口框架的文章之后,主要步骤如下:
- 删除标准帧(非客户端区域),在回答WM_NCCALCSIZE消息时返回0
- 使用DwmExtendFrameIntoClientArea函数将帧扩展到客户端区域
我以下一种方式处理WM_NCCALCSIZE消息:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCCALCSIZE:
if (isDwmWindowFramePaintEnabled() && m.WParam != IntPtr.Zero)
{
m.Result = IntPtr.Zero;
}
else
{
base.WndProc(ref m);
}
return;
}
}
根据WM_NCCALCSIZE上的MSDN文档,
当wParam为TRUE时,只需返回0而不处理NCCALCSIZE_PARAMS矩形将导致客户端区域的大小调整为窗口的大小,包括窗框。这将删除窗口中的窗口框架和标题项目,只留下显示客户端区域。
除了一个问题,一切都很好,对我来说都很好。当我最大化/恢复窗口时,当它被恢复时,它总是会增长一点。我认为,问题是这样的:
- 当窗口恢复时,它只包含客户端区域
- Windows尝试为窗口提供一些非客户端区域
- 在WM_NCCALCSIZE中,客户端区域增长为包含非客户端区域
所以,就像这个窗口每次我最大化/恢复它时都会增长一点一样。我需要删除非客户端区域来使用DWM绘制自定义表单框架。我不能简单地将窗口边框样式设置为无,因为DWM将不会绘制窗口标题和边框。
请帮助解决这个问题,并愉快地拥有一个自定义的窗框。
这实际上是Windows窗体中的一个错误,有一个解决方法。在函数Form.SizeFromClientSize(int, int)
中,AdjustWindowRectEx
函数用于转换大小,它始终使用默认测量值,并且不能被覆盖。此函数从两个位置调用:
- WM_WINDOWPOSCHANGED窗口消息处理程序中的
RestoreWindowBoundsIfNecessary
SetClientSizeCore
解决方法如下:
-
覆盖表单中的CreateParams:
private bool createParamsHack; protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; // Remove styles that affect the border size if (createParamsHack) cp.Style &= ~(int)(WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_THICKFRAME); return cp; } }
-
重写WndProc并插入以下代码来处理WM_WINDOWOSCHANGED:
if (m.Msg == WM_WINDOWPOSCHANGED) { createParamsHack = true; base.WndProc(ref m); createParamsHack = false; }
-
覆盖SetClientSizeCore:
protected override void SetClientSizeCore(int x, int y) { createParamsHack = true; base.SetClientSizeCore(x, y); createParamsHack = false; }
覆盖SizeFromClientSize(Size)
以返回正确的测量值可能也是一个好主意,但这并不是绝对必要的。