WindowChrome ResizeBorderThickness проблема
Я оформляю окно, но я заметил это странное поведение WindowChrome (в.NET FW 4.0, из внешней библиотеки Microsoft.Windows.Shell dll).
Я установил WindowChrome с AllowTransparency = true и WindowStyle = None.
Если я установлю ResizeBorderThickness <= 7 WindowChrome все работает отлично, но если я делаю
ResizeBorderThickness="8"
или больше, когда окно развернуто, я не могу перетащить его из последнего верхнего пикселя около верхнего края экрана, и для каждого +1, превышающего 7, я должен начать перетаскивать еще 1 пиксель вниз от края.
Это раздражает, потому что отключает обычное поведение при закрытии окна, заставляя меня установить его на 7 или меньше.
Может кто-нибудь объяснить мне это поведение?
Спасибо!
2 ответа
У окна нет странного поведения. Вместо этого у окна есть два странных поведения.
- (A) Первое странное поведение:
"[...] когда окно развернуто, я не могу перетащить его из последнего верхнего пикселя около верхнего края экрана [...]"
Такое поведение связано с тем, что край изменения размера все еще активен, когда окно переходит в максимизированное состояние. Действительно, этот край всегда активен. Устанавливая свойство ResizeBorderThickness, WindowChrome резервирует это количество пикселей для управления поведением изменения размера окна. Учитывая, что в режиме максимизации события изменения размера не разрешены, вы заметите, что эти пиксели не допускают какого-либо поведения. Это именно потому, что WindowChrome резервирует только те пиксели, которые управляют поведением изменения размера.
Каково решение? Вам необходимо уведомить WindowChrome об изменении, чтобы установить свойство ResizeBorderThickness равным 0, когда окно развернуто. Это можно сделать, просто установив WindowChrome снова с помощью триггера в xaml:
<Trigger Property="WindowState" Value="Maximized">
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome ResizeBorderThickness="0" [...] />
</Setter.Value>
</Setter>
</Trigger>
Примечание: это также можно сделать в коде времени выполнения
- (Б) Второе странное поведение:
"[...] Если я установлю ResizeBorderThickness WindowChrome <= 7, все будет отлично работать [...], и для каждого +1, превышающего 7, я должен начать перетаскивать еще 1 пиксель вниз от края. [...]"
Береги себя. На самом деле это не из-за значения, заданного в ResizeBorderThickness, но из-за установки свойства WindowStyle = None. Когда это свойство установлено, окно принимает странное поведение при максимизации:
Верхний левый край окна не позиционируется в точке (0,0) текущего экрана, а скорее беспорядочно становится отрицательным (в вашем случае, по оси Y значение кажется -7).
Размер окна принимает размер текущего экрана, когда нормальным поведением должно быть то, что размер окна принимает размер текущей рабочей области (текущий экран, кроме панели задач и т. Д.) Текущего экрана.
Это странное поведение окна приводит к тому, что 7 пикселей, зарезервированных для 'WindowChrome', не видны на текущем экране (очевидно, с ResizeBorderThickness="7"), и поэтому создается ощущение, что свойство ResizeBorderThickness="7" работает правильно, когда это не так. Фактически, это оправдывает поведение, когда ResizeBorderThickness принимает значение 8 или более.
Каково решение? Нужно принудительно заставить окно при максимизации размера и положения в рабочей области текущего экрана. Предупреждение: если вы делаете это только для основного экрана, событие максимизации не работает должным образом для нескольких экранов.
Код, который решает эту проблему, я решил, вызвав внешний API:
[DllImport("user32")]
internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
[DllImport("user32")]
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
Определение классов и структур:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
public class MONITORINFO
{
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
public RECT rcMonitor = new RECT();
public RECT rcWork = new RECT();
public int dwFlags = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
public POINT(int x, int y) { this.x = x; this.y = y; }
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
}
И, наконец, определение функций, которые добавляют хук WndProc к окну:
public static void CompatibilityMaximizedNoneWindow(Window window)
{
WindowInteropHelper wiHelper = new WindowInteropHelper(window);
System.IntPtr handle = wiHelper.Handle;
HwndSource.FromHwnd(handle).AddHook(
new HwndSourceHook(CompatibilityMaximizedNoneWindowProc));
}
private static System.IntPtr CompatibilityMaximizedNoneWindowProc(
System.IntPtr hwnd,
int msg,
System.IntPtr wParam,
System.IntPtr lParam,
ref bool handled)
{
switch (msg)
{
case 0x0024: // WM_GETMINMAXINFO
MINMAXINFO mmi =
(MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
// Adjust the maximized size and position
// to fit the work area of the correct monitor
// int MONITOR_DEFAULTTONEAREST = 0x00000002;
System.IntPtr monitor = MonitorFromWindow(hwnd, 0x00000002);
if (monitor != System.IntPtr.Zero)
{
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo(monitor, monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.x =
Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y =
Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x =
Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y =
Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
}
Marshal.StructureToPtr(mmi, lParam, true);
handled = true;
break;
}
return (System.IntPtr)0;
}
С помощью CompatibilityMaximizedNoneWindow API вы просто вызываете API в конструкторе окна, что-то вроде этого:
public MyWindow
{
[...]
MyNamespace.CompatibilityMaximizedNoneWindow(this);
}
И второе странное поведение должно быть разрешено. Вы заметите, что для работы кода необходимо добавить ссылку PresentationFramework и пространство имен System.Windows.Interop.
Если у вас полноэкранное приложение (WindowStyle
установите значение «Нет» иAllowTransparency
установлен вtrue
) вам нужно внести некоторые изменения в отличный ответ Нуара :
Вместо использования рабочей области для определения максимальных границ используйтеrcMonitor
:
mmi.ptMaxPosition.x = 0;
mmi.ptMaxPosition.y = 0;
mmi.ptMaxSize.x = Math.Abs(rcMonitorArea.left - rcMonitorArea.right);
mmi.ptMaxSize.y = Math.Abs(rcMonitorArea.bottom - rcMonitorArea.top);
Чтобы полноэкранный режим работал, из окна необходимо удалитьWindowChrome
полностью в развернутом режиме:
// Run this whenever the window state changes (maximize, restore, ...)
WindowChrome chrome ;
if (WindowState == WindowState.Maximized)
chrome = null;
else
chrome = new WindowChrome() { ... }
WindowChrome.SetWindowChrome(this, chrome);
Обернув логику в класс, который может сохранять состояние, мы можем даже заставить наше окно переходить в полноэкранный режим и выходить из него по своему желанию:
if (IsFullScreen)
{
// Tell Windows that we want to occupy the entire monitor
mmi.ptMaxPosition.x = 0;
mmi.ptMaxPosition.y = 0;
mmi.ptMaxSize.x = Math.Abs(rcMonitorArea.left - rcMonitorArea.right);
mmi.ptMaxSize.y = Math.Abs(rcMonitorArea.bottom - rcMonitorArea.top);
}
else
{
// Tell Windows that we want to occupy the entire work area of the
// current monitor (leaves the task bar visible)
mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
}
Полный пример с использованием окна WPF доступен как github gist.