How do I compute the non-client window size in WPF?
WPF has the SystemParameters
class that exposes a great number of system metrics. On my computer I have noticed that a normal window has a title that is 30 pixels high and a border that is 8 pixels wide. This is on Windows 7 with the Aero theme enabled:
How开发者_Go百科ever, SystemParameters
return the following values:
SystemParameters.BorderWidth = 5
SystemParameters.CaptionHeight = 21
Here I have disabled the Aero theme:
Now, SystemParameters
return the following values:
SystemParameters.BorderWidth = 1
SystemParameters.CaptionHeight = 18
How do I compute the actual observed values by using SystemParameters
?
For a resizable window you need to use a different set of parameters to compute the size:
var titleHeight = SystemParameters.WindowCaptionHeight
+ SystemParameters.ResizeFrameHorizontalBorderHeight;
var verticalBorderWidth = SystemParameters.ResizeFrameVerticalBorderWidth;
These sizes will change when you modify the theme.
I'm pretty sure that the GetSystemMetrics
function (which the SystemParameters
class calls internally with the appropriate arguments) is returning the correct values for your system, it's just returning the correct values in the case whether the Aero theme is disabled. By turning on Aero, you get beefier borders and taller window captions, all the name of juicy graphical goodness.
If you want to get the correct size of these window elements, regardless of the user's current theme (remember, you can run Windows Vista and beyond with the Classic theme, the Aero Basic theme, or the full Aero theme, all of which are going to have different-sized UI elements), you need to use a different method available in Vista and later.
You need to send the window a WM_GETTITLEBARINFOEX
message in order to request extended title bar information. The wParam
is unused and should be zero. The lParam
contains a pointer to a TITLEBARINFOEX
structure that will receive all of the information. The caller is responsible for allocating memory for this structure and setting its cbSize
member.
To do all of this from a .NET application, you'll obviously need to do some P/Invoke. Start by defining the constants you need, as well as the TITLEBARINFOEX
structure:
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;
}
Then define the SendMessage
function accordingly:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(
IntPtr hWnd,
int uMsg,
IntPtr wParam,
ref TITLEBARINFOEX lParam);
And finally, you can call all of that mess using something like the following code:
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;
}
EDIT: Now tested and working on my notebook running Windows 7.
This is a C++/CLI answer, and it is NOT using SystemParameters
, but I think this is a better approach to this problem, since it should be correct for any window.
In fact, the other answers are only valid for a resizable window, and one must create different cases for each of the available WindowStyle
s.
Since for every SystemParameters
needed for these calculations there is a documented SM_CX* or SM_CY* value, I thought that, instead of re-inventing the wheel, one could simply use the WinAPI AdjustWindowRectEx
function.
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;
}
One can easily convert the above code to C# using some more DllImport
s, or this can be dropped into a C++/CLI assembly if your project is already using one.
Refer to the following:
http://blogs.microsoft.co.il/blogs/alex_golesh/archive/2009/09/20/wpf-quick-tip-how-to-get-wpf-window-client-area-size.aspx
I presume you are trying to calculate the size you must make the Applications Window in order to give the right amount of client area to fully show some WPF content?
If so, then just remember that WPF's pixels are at 96dpi, and your display may be running at a different dpi...also as mentioned by other answers the theme affects how big you'd have to size your main Window to get your desired client area.
Alternatively, you might be able to use MinWidth/MinHeight on the child control of the Window.
For re-sizable window
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;
精彩评论