C# 如何计算WPF中的非客户端窗口大小?
WPF具有公开大量系统度量的功能。在我的电脑上,我注意到一个普通的窗口有一个30像素高的标题和一个8像素宽的边框。这在启用Aero主题的Windows 7上: 但是,C# 如何计算WPF中的非客户端窗口大小?,c#,.net,wpf,windows,winapi,C#,.net,Wpf,Windows,Winapi,WPF具有公开大量系统度量的功能。在我的电脑上,我注意到一个普通的窗口有一个30像素高的标题和一个8像素宽的边框。这在启用Aero主题的Windows 7上: 但是,SystemParameters返回以下值: SystemParameters.BorderWidth = 5 SystemParameters.CaptionHeight = 21 SystemParameters.BorderWidth = 1 SystemParameters.CaptionHeight = 18 在这里
SystemParameters
返回以下值:
SystemParameters.BorderWidth = 5
SystemParameters.CaptionHeight = 21
SystemParameters.BorderWidth = 1
SystemParameters.CaptionHeight = 18
在这里,我禁用了Aero主题:
现在,SystemParameters
返回以下值:
SystemParameters.BorderWidth = 5
SystemParameters.CaptionHeight = 21
SystemParameters.BorderWidth = 1
SystemParameters.CaptionHeight = 18
如何使用
系统参数计算实际观测值?对于可调整大小的窗口,您需要使用一组不同的参数来计算大小:
var titleHeight = SystemParameters.WindowCaptionHeight
+ SystemParameters.ResizeFrameHorizontalBorderHeight;
var verticalBorderWidth = SystemParameters.ResizeFrameVerticalBorderWidth;
当您修改主题时,这些大小将发生变化。我非常确定(SystemParameters
类使用适当的参数在内部调用的)正在为您的系统返回正确的值,它只是在禁用Aero主题的情况下返回正确的值。通过启用Aero,您可以获得更强大的边框和更高的窗口标题,这些都是图形化的优点
如果您希望获得这些窗口元素的正确大小,而不考虑用户的当前主题(请记住,您可以使用经典主题、Aero Basic主题或完整Aero主题运行Windows Vista及更高版本,所有这些都将具有不同大小的UI元素),您需要使用Vista和更高版本中提供的其他方法
您需要发送窗口a以请求扩展标题栏信息。wParam
未使用,应为零。lParam
包含一个指针,指向将接收所有信息的。调用者负责为此结构分配内存并设置其cbSize
成员
要从.NET应用程序执行所有这些操作,显然需要执行一些p/Invoke。首先定义所需的常量,以及TITLEBARINFOEX
结构:
internal const int WM_GETTITLEBARINFOEX = 0x033F;
internal const int CCHILDREN_TITLEBAR = 5;
[StructLayout(LayoutKind.Sequential)]
internal struct TITLEBARINFOEX
{
public int cbSize;
public Rectangle rcTitleBar;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CCHILDREN_TITLEBAR + 1)]
public int[] rgstate;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CCHILDREN_TITLEBAR + 1)]
public Rectangle[] rgrect;
}
然后相应地定义SendMessage
功能:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(
IntPtr hWnd,
int uMsg,
IntPtr wParam,
ref TITLEBARINFOEX lParam);
最后,您可以使用以下代码调用所有这些混乱:
internal static TITLEBARINFOEX GetTitleBarInfoEx(IntPtr hWnd)
{
// Create and initialize the structure
TITLEBARINFOEX tbi = new TITLEBARINFOEX();
tbi.cbSize = Marshal.SizeOf(typeof(TITLEBARINFOEX));
// Send the WM_GETTITLEBARINFOEX message
SendMessage(hWnd, WM_GETTITLEBARINFOEX, IntPtr.Zero, ref tbi);
// Return the filled-in structure
return tbi;
}
编辑:现在已测试并使用运行Windows 7的笔记本电脑 参考以下内容:
我假设您正在尝试计算您必须使应用程序窗口的大小,以便提供适当数量的客户端区域来充分显示某些WPF内容
如果是这样的话,那么请记住WPF的像素是96dpi,并且您的显示器可能以不同的dpi运行…正如其他答案所提到的,主题会影响您必须调整主窗口的大小以获得所需的客户端区域
或者,您可以在窗口的子控件上使用MinWidth/MinHeight。用于重新调整窗口大小
NON_CLIENT_AREA_HEIGHT = SystemParameters.WindowNonClientFrameThickness.Top +
SystemParameters.WindowNonClientFrameThickness.Bottom +
SystemParameters.WindowResizeBorderThickness.Top +
SystemParameters.WindowResizeBorderThickness.Bottom;
NON_CLIENT_AREA_WIDTH = SystemParameters.WindowNonClientFrameThickness.Left +
SystemParameters.WindowNonClientFrameThickness.Right +
SystemParameters.WindowResizeBorderThickness.Left +
SystemParameters.WindowResizeBorderThickness.Right;
这是一个C++/CLI答案,它没有使用SystemParameters
,但我认为这是解决此问题的更好方法,因为它应该适用于任何窗口
事实上,其他答案仅对可调整大小的窗口有效,并且必须为每个可用窗口创建不同的案例
由于这些计算所需的每个SystemParameters
都有一个记录在案的SM_CX*或SM_CY*值,因此我认为,与其重新发明轮子,不如简单地使用WinAPI函数
bool SetWindowClientArea(System::Windows::Window^ win, int width, int height) {
System::Windows::Interop::WindowInteropHelper^ wi = gcnew System::Windows::Interop::WindowInteropHelper(win);
wi->EnsureHandle();
HWND win_HWND = (HWND)(wi->Handle.ToPointer());
LONG winStyle = GetWindowLong(win_HWND, GWL_STYLE);
LONG winExStyle = GetWindowLong(win_HWND, GWL_EXSTYLE);
RECT r = { 0 };
r.right = width;
r.bottom = height;
BOOL bres = AdjustWindowRectEx(&r, winStyle, FALSE, winExStyle);
if (bres) {
Double w = r.right - r.left;
Double h = r.bottom - r.top;
win->Width = w;
win->Height = h;
}
return bres;
}
您可以使用更多的DllImport
s轻松地将上述代码转换为C#,或者,如果您的项目已经在使用C++/CLI程序集,则可以将其放入C++/CLI程序集。是的,添加8是我以前见过的一种使这些值与Aero主题的预期值匹配的技巧。但这并不是一个万无一失的方法。Windows主题显然是微软非常喜欢重新发明的东西,即使在当前系统下,也会出现问题,除非你特别检查用户当前运行的是Classic、Aero Basic还是Aero。在这种情况下,我并不认为这是一种黑客行为。标题为22个像素,边框四个侧面均为8个像素。这将为您提供正确的值。当您更改主题时,它会起作用-它会为Classic、Aero Basic和Aero返回正确的值。答案似乎是:在Windows 8.1上,ResizeFrameVerticalBorderWidth
给我4,但是真正的边框宽度是7。@kolSystemParameters.ResizeFrameVerticalBorderWidth+SystemParameters.FixedFrameVerticalBorderWidth/+SystemParameters.BorderWidth
给了我正确的值(我用java得到了8,但我不确定它是8还是7,所以idk如果BorderWidth很重要,我认为它是内部的灰线。他明确地问了非客户区。你的答案和链接帖子大部分都是关于客户区的。在指定dim时,确定NC区域仅仅是为了计算客户区的尺寸是非常愚蠢的后者的原理要简单得多。似乎有效。在WPF中使用此代码时有一个小小的警告,因为此代码需要windows窗体Rectangle
,而不是WPFRectangle
——我花了一些时间才弄明白,否则就不会得到结果。此代码在windows 8.1中可用,但在windows 10中不起作用,您知道如何x?我在这里发布了一个新问题-非常感谢你在说明指标方面做得很好。这应该放在MSDN文档中!WindowNonClientFrameThickness
已经在内部包含了WindowResizeOrderThickness
。唯一的区别是前者也得到了标题的高度。