Создание поведения WPF для перемещения окна без рамки
Пишу простенькое приложение WPF, решил убрать границы у окошек в оном, но столкнулся с проблемой перемещения окошка. Немного погуглив имеющиеся вопросы, я нашёл довольно простое решение:
bool isKeyPressed = false;
public MainWindow()
{
InitializeComponent();
this.PreviewKeyDown += (s1, e1) => { if (e1.Key == Key.LeftCtrl) isKeyPressed = true; };
this.PreviewKeyUp += (s2, e2) => { if (e2.Key == Key.LeftCtrl) isKeyPressed = false; };
this.PreviewMouseLeftButtonDown += (s, e) => { if (isKeyPressed) DragMove(); };
}
Однако, так как у меня будет приложение с несколькими окнами, было бы удобнее использовать Behavior. Я пока не понял, как сделать проверку на нажатие LeftCtrl и само перетягиваение окна с помощью Behavior.
Нужна помощь! Желательно, с пояснениями, чтобы на будущее мог сам. Заранее спасибо.
Ответы (1 шт):
Не очень понятно, зачем здесь Behavior, это бесполезный механизм, который тянет за собой стороннюю библиотеку, но при этом ничего особо не дает. Скорее я бы делал тут свой класс, производный от Window. Но сделать это через поведение можно как-то так:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Height="350" Width="525" WindowStyle="SingleBorderWindow" >
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
<Grid x:Name="grid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Black">
<Button x:Name="bMin" Content="_" HorizontalAlignment="Right" Height="40" Margin="0,0,80,00" VerticalAlignment="Top"
Background="LightBlue" Width="40" Click="bMin_Click" />
<Button x:Name="bMax" Content="□" HorizontalAlignment="Right" Height="40" Margin="0,0,40,00" VerticalAlignment="Top"
Background="LightBlue" Width="40" Click="bMax_Click" />
<Button Content="X" HorizontalAlignment="Right" Height="40" Margin="0,0,0,0" VerticalAlignment="Top"
Background="Red" Width="40" Click="ButtonClose_Click" />
<Button Content="Button" HorizontalAlignment="Left" Margin="49,45,0,0"
VerticalAlignment="Top" Width="75" Background="Green"
Foreground="White" Click="Button_Click" >
</Button>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Documents;
using System.Windows.Interactivity;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
private void bMin_Click(object sender, RoutedEventArgs e)
{
this.WindowState = WindowState.Minimized;
}
private void bMax_Click(object sender, RoutedEventArgs e)
{
if (this.WindowState == System.Windows.WindowState.Normal) this.WindowState = System.Windows.WindowState.Maximized;
else this.WindowState = System.Windows.WindowState.Normal;
}
private void ButtonClose_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
public class MyBehavior : Behavior<Window>
{
IntPtr Handle;
int yborder = 20; //высота области для перемещения
bool loaded = false;
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left, top, right, bottom;
}
[StructLayout(LayoutKind.Sequential)]
struct NCCALCSIZE_PARAMS
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public RECT[] rgrc;
public IntPtr lppos;
}
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll")]
static extern bool AdjustWindowRectEx(ref RECT lpRect, uint dwStyle,
bool bMenu, uint dwExStyle);
[DllImport("dwmapi.dll")]
static extern bool DwmDefWindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref IntPtr plResult);
static int GET_X_LPARAM(IntPtr lp)
{
short loword = (short)((ulong)lp & 0xffff);
return loword;
}
static int GET_Y_LPARAM(IntPtr lp)
{
short hiword = (short)((((ulong)lp) >> 16) & 0xffff);
return hiword;
}
const uint WM_NCCALCSIZE = 0x0083;
const uint WM_NCHITTEST = 0x0084;
const uint WM_ACTIVATE = 0x0006;
const uint WM_NCACTIVATE = 0x0086;
const uint WM_NCPAINT = 0x85;
const uint WS_OVERLAPPED = 0x00000000;
const uint WS_CAPTION = 0x00C00000;
const uint WS_SYSMENU = 0x00080000;
const uint WS_THICKFRAME = 0x00040000;
const uint WS_MINIMIZEBOX = 0x00020000;
const uint WS_MAXIMIZEBOX = 0x00010000;
const uint WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
const uint HTTOPLEFT = 13;
const uint HTTOPRIGHT = 14;
const uint HTTOP = 12;
const uint HTCAPTION = 2;
const uint HTLEFT = 10;
const uint HTNOWHERE = 0;
const uint HTRIGHT = 11;
const uint HTBOTTOM = 15;
const uint HTBOTTOMLEFT = 16;
const uint HTBOTTOMRIGHT = 17;
const uint SM_CXSIZEFRAME = 32;
const uint SM_CYSIZEFRAME = 33;
//обработка координат мыши для неклиентской области
IntPtr HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam)
{
// Get the point coordinates for the hit test.
var ptMouse = new Point(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
// Get the window rectangle.
RECT rcWindow;
GetWindowRect(hWnd, out rcWindow);
// Get the frame rectangle, adjusted for the style without a caption.
RECT rcFrame = new RECT();
AdjustWindowRectEx(ref rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, false, 0);
// Determine if the hit test is for resizing. Default middle (1,1).
ushort uRow = 1;
ushort uCol = 1;
bool fOnResizeBorder = false;
// Determine if the point is at the top or bottom of the window.
if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + yborder)
{
uRow = 0;
}
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
IntPtr[,] hitTests = new IntPtr[,]
{
{ (IntPtr)HTTOPLEFT, fOnResizeBorder? (IntPtr)HTTOP : (IntPtr)HTCAPTION, (IntPtr)HTTOPRIGHT },
{ (IntPtr)HTLEFT, (IntPtr)HTNOWHERE, (IntPtr)HTRIGHT},
{ (IntPtr)HTBOTTOMLEFT, (IntPtr)HTBOTTOM, (IntPtr)HTBOTTOMRIGHT },
};
return hitTests[uRow, uCol];
}
//обработчик сообщений для окна
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
bool fCallDWP = true;
IntPtr lRet = IntPtr.Zero;
if (msg == WM_NCCALCSIZE)
{
if (wParam != (IntPtr)0)
{
//убираем стандартную рамку
lRet = IntPtr.Zero;
handled = true;
return lRet;
}
}
if (msg == WM_NCACTIVATE)
{
lRet = (IntPtr)1;
handled = true;
return lRet;
}
fCallDWP = !DwmDefWindowProc(hwnd, msg, wParam, lParam, ref lRet);
if (msg == WM_NCHITTEST && lRet == IntPtr.Zero)
{
//обработка нажатий мыши
lRet = HitTestNCA(hwnd, wParam, lParam);
if (lRet != (IntPtr)HTNOWHERE)
{
fCallDWP = false;
}
}
//если сообщение не обработано, передаем базовой процедуре
if (fCallDWP) handled = false;
else handled = true;
return lRet;
}
void Load(HwndSource source)
{
if (loaded) return;
Handle = source.Handle;
source.AddHook(new HwndSourceHook(WndProc));//регистрируем обработчик сообщений
loaded = true;
}
protected override void OnAttached()
{
base.OnAttached();
HwndSource hwndSource = (HwndSource)HwndSource.FromVisual(AssociatedObject);
if (hwndSource == null)
{
AssociatedObject.SourceInitialized += OnSourceInitialized;
return;
}
Load(hwndSource);
}
void OnSourceInitialized(object sender, EventArgs e)
{
HwndSource hwndSource = (HwndSource)HwndSource.FromVisual(AssociatedObject);
if (hwndSource == null)
{
WindowInteropHelper h = new WindowInteropHelper(AssociatedObject);
hwndSource = HwndSource.FromHwnd(h.Handle);
}
Load(hwndSource);
}
}
}
Здесь убирается стандартная рамка с помощью обработки WM_NCCALCSIZE и восстанавливается возможность перемещения окна перетаскиванием за область высотой в 20 пикселей в верхней части (модификация способа из https://ru.stackoverflow.com/a/1130986/240512).