Ключ к решению этого - связать оконный хук с обработчиком для сообщений WM_NCCALCSIZE (0x83)
и WM_NCPAINT (0x85)
.
WM_NCPAINT
позволит вам удалить нижнюю границу в один пиксель, вызвав DwmExtendFrameIntoClientArea
. В приведенном ниже коде я заключил этот вызов в метод под названием RemoveFrame
.
WM_NCCALCSIZE
позволит вам изменить размер клиентской области окна, восстанавливая дополнительное пространство, которое WindowChrome
установил используя GlassFrameThickness="0,0,0,1"
и NonClientFrameEdges="Bottom"
.
Я объединил эту функциональность в поведение XAML.
Вот последний код, который решит вашу проблему:
WindowChromeLoadedBehavior
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Shell;
using Microsoft.Xaml.Behaviors;
namespace WpfApp1
{
public class WindowChromeLoadedBehavior : Behavior<FrameworkElement>
{
private Window window;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnLoaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
window = Window.GetWindow(AssociatedObject);
if (window == null) return;
Task.Delay(5).ContinueWith(_ =>
{
Dispatcher.Invoke(() =>
{
var oldWindowChrome = WindowChrome.GetWindowChrome(window);
if (oldWindowChrome == null) return;
var newWindowChrome = new WindowChrome
{
CaptionHeight = oldWindowChrome.CaptionHeight,
CornerRadius = oldWindowChrome.CornerRadius,
GlassFrameThickness = new Thickness(0, 0, 0, 1),
NonClientFrameEdges = NonClientFrameEdges.Bottom,
ResizeBorderThickness = oldWindowChrome.ResizeBorderThickness,
UseAeroCaptionButtons = oldWindowChrome.UseAeroCaptionButtons
};
WindowChrome.SetWindowChrome(window, newWindowChrome);
});
});
var hWnd = new WindowInteropHelper(window).Handle;
HwndSource.FromHwnd(hWnd)?.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case NativeMethods.WM_NCPAINT:
RemoveFrame();
handled = false;
break;
case NativeMethods.WM_NCCALCSIZE:
handled = false;
var rcClientArea = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
rcClientArea.Bottom += (int)(WindowChromeHelper.WindowResizeBorderThickness.Bottom / 2);
Marshal.StructureToPtr(rcClientArea, lParam, false);
var retVal = IntPtr.Zero;
if (wParam == new IntPtr(1))
{
retVal = new IntPtr((int)NativeMethods.WVR.REDRAW);
}
return retVal;
}
return IntPtr.Zero;
}
private void RemoveFrame()
{
if (Environment.OSVersion.Version.Major >= 6 && NativeMethods.IsDwmAvailable())
{
if (NativeMethods.DwmIsCompositionEnabled() && SystemParameters.DropShadow)
{
NativeMethods.MARGINS margins;
margins.bottomHeight = -1;
margins.leftWidth = 0;
margins.rightWidth = 0;
margins.topHeight = 0;
var helper = new WindowInteropHelper(window);
NativeMethods.DwmExtendFrameIntoClientArea(helper.Handle, ref margins);
}
}
}
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public static RECT Empty;
public int Width => Math.Abs(Right - Left);
public int Height => (Bottom - Top);
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public RECT(RECT rcSrc)
{
Left = rcSrc.Left;
Top = rcSrc.Top;
Right = rcSrc.Right;
Bottom = rcSrc.Bottom;
}
public RECT(Rectangle rectangle) : this(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom)
{
}
public bool IsEmpty
{
get
{
if (Left < Right)
{
return (Top >= Bottom);
}
return true;
}
}
public override string ToString()
{
if (this == Empty)
{
return "RECT {Empty}";
}
return string.Concat("RECT { left : ", Left, " / top : ", Top, " / right : ", Right, " / bottom : ", Bottom, " }");
}
public override bool Equals(object obj)
{
return ((obj is Rect) && (this == ((RECT)obj)));
}
public override int GetHashCode()
{
return ((Left.GetHashCode() + Top.GetHashCode()) + Right.GetHashCode()) + Bottom.GetHashCode();
}
public static bool operator ==(RECT rect1, RECT rect2)
{
return ((((rect1.Left == rect2.Left) && (rect1.Top == rect2.Top)) && (rect1.Right == rect2.Right)) && (rect1.Bottom == rect2.Bottom));
}
public static bool operator !=(RECT rect1, RECT rect2)
{
return !(rect1 == rect2);
}
static RECT()
{
Empty = new RECT();
}
}
}
}
WindowChromeHelper
using System;
using System.Runtime.InteropServices;
using System.Windows;
namespace WpfApp1
{
public static class WindowChromeHelper
{
public static Thickness LayoutOffsetThickness => new Thickness(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);
/// <summary>
/// Gets the properly adjusted window resize border thickness from system parameters.
/// </summary>
public static Thickness WindowResizeBorderThickness
{
get
{
var dpix = GetDpi(GetDeviceCapsIndex.LOGPIXELSX);
var dpiy = GetDpi(GetDeviceCapsIndex.LOGPIXELSY);
var dx = GetSystemMetrics(GetSystemMetricsIndex.CXFRAME);
var dy = GetSystemMetrics(GetSystemMetricsIndex.CYFRAME);
// This adjustment is needed since .NET 4.5
var d = GetSystemMetrics(GetSystemMetricsIndex.SM_CXPADDEDBORDER);
dx += d;
dy += d;
var leftBorder = dx / dpix;
var topBorder = dy / dpiy;
return new Thickness(leftBorder, topBorder, leftBorder, topBorder);
}
}
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
private static float GetDpi(GetDeviceCapsIndex index)
{
var desktopWnd = IntPtr.Zero;
var dc = GetDC(desktopWnd);
float dpi;
try
{
dpi = GetDeviceCaps(dc, (int)index);
}
finally
{
ReleaseDC(desktopWnd, dc);
}
return dpi / 96f;
}
private enum GetDeviceCapsIndex
{
LOGPIXELSX = 88,
LOGPIXELSY = 90
}
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(GetSystemMetricsIndex nIndex);
private enum GetSystemMetricsIndex
{
CXFRAME = 32,
CYFRAME = 33,
SM_CXPADDEDBORDER = 92
}
}
}
NativeMethods
using System;
using System.Runtime.InteropServices;
namespace WpfApp1
{
public static class NativeMethods
{
public const int WM_NCCALCSIZE = 0x83;
public const int WM_NCPAINT = 0x85;
[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("dwmapi.dll", PreserveSig = false)]
public static extern bool DwmIsCompositionEnabled();
[DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
public int leftWidth;
public int rightWidth;
public int topHeight;
public int bottomHeight;
}
private delegate int DwmExtendFrameIntoClientAreaDelegate(IntPtr hwnd, ref MARGINS margins);
public static int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins)
{
var hModule = LoadLibrary("dwmapi");
if (hModule == IntPtr.Zero)
{
return 0;
}
var procAddress = GetProcAddress(hModule, "DwmExtendFrameIntoClientArea");
if (procAddress == IntPtr.Zero)
{
return 0;
}
var delegateForFunctionPointer = (DwmExtendFrameIntoClientAreaDelegate)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(DwmExtendFrameIntoClientAreaDelegate));
return delegateForFunctionPointer(hwnd, ref margins);
}
public static bool IsDwmAvailable()
{
if (LoadLibrary("dwmapi") == IntPtr.Zero)
{
return false;
}
return true;
}
internal enum WVR
{
ALIGNTOP = 0x0010,
ALIGNLEFT = 0x0020,
ALIGNBOTTOM = 0x0040,
ALIGNRIGHT = 0x0080,
HREDRAW = 0x0100,
VREDRAW = 0x0200,
VALIDRECTS = 0x0400,
REDRAW = HREDRAW | VREDRAW
}
}
}