Ответ 1
Пример кода
Благодаря электронному письму, которое я получил сегодня утром, мне было предложено создать пример рабочего приложения, демонстрирующий эту самую функциональность. Я сделал это сейчас; вы можете найти его на http://wpfdraggableframe.codeplex.com. Просто загрузите последнюю версию с вкладки Исходный код, откройте ее в Visual Studio и создайте и запустите ее.
Полное приложение полностью лицензировано MIT, но вы, вероятно, разделите его на части и добавьте фрагменты своего кода, а не используете код приложения в полном объеме - не то, что лицензия не позволяет вам делать это или. Кроме того, хотя я знаю, что дизайн главного окна приложения нигде не похож на каркасы выше, идея такая же, как и в вопросе.
Надеюсь, это поможет кому-то!
Пошаговое решение
Я, наконец, решил. Благодаря Jeffrey L Whitledge для указания меня в правильном направлении! Его ответ был принят, потому что, если бы не он, мне не удалось бы выработать решение. ИЗМЕНИТЬ [9/8]: этот ответ теперь принят, поскольку он больше завершения; Я даю Джеффри отличную большую щедрость вместо его помощи.
Для потомков, вот как я это сделал (цитируя Джеффри, где это уместно, когда я иду):
Получите местоположение щелчка мыши (из wParam, lParam, возможно?), и используйте его для создания
Point
(возможно, с каким-то преобразованием координат?).
Эта информация может быть получена из сообщения lParam
сообщения WM_NCHITTEST
. X-координата курсора - это младшее слово, а y-координата курсора - это слово высокого порядка, поскольку MSDN описывает.
Поскольку координаты относятся ко всему экрану, мне нужно вызвать Visual.PointFromScreen()
в моем окне, чтобы преобразовать координаты относительно окна.
Затем вызовите статический метод
VisualTreeHelper.HitTest(Visual,Point)
, передав егоthis
иPoint
, которые вы только что создали. Возвращаемое значение будет указывать на элемент управления с наивысшим Z-порядком.
Мне пришлось пройти в верхнем уровне Grid
вместо this
в качестве визуального для тестирования точки. Аналогично мне пришлось проверить, был ли результат нулевым, вместо того, чтобы проверять, было ли это окно. Если он равен нулю, курсор не ударил ни одного из дочерних элементов сетки - другими словами, он попал в незанятую область окна окна. Во всяком случае, ключ должен был использовать метод VisualTreeHelper.HitTest()
.
Теперь, сказав это, есть два оговорки, которые могут применяться к вам, если вы выполните следующие шаги:
-
Если вы не закрываете все окно и вместо этого только частично расширяете оконный фрейм, вы должны поместить элемент управления над прямоугольником, который не заполнен рамкой окна, как наполнитель клиентской области.
В моем случае область содержимого моего элемента управления вкладками идеально подходит для прямоугольной области, как показано на диаграммах. В вашем приложении вам может потребоваться установить элемент
Rectangle
илиPanel
и нарисовать соответствующий цвет. Таким образом, управление будет удалено.Эта проблема о наполнителях клиентской области приводит к следующему:
-
Если ваша сетка или другой элемент управления верхнего уровня имеет текстуру фона или градиент над расширенным оконным кадром, вся область сетки будет реагировать на попадание даже на любые полностью прозрачные области фона (см. Хит-тестирование в визуальном слое). В этом случае вы захотите игнорировать удары по самой сетке и только обратите внимание на элементы управления внутри нее.
Следовательно:
// In MainWindow
private bool IsOnExtendedFrame(int lParam)
{
int x = lParam << 16 >> 16, y = lParam >> 16;
var point = PointFromScreen(new Point(x, y));
// In XAML: <Grid x:Name="windowGrid">...</Grid>
var result = VisualTreeHelper.HitTest(windowGrid, point);
if (result != null)
{
// A control was hit - it may be the grid if it has a background
// texture or gradient over the extended window frame
return result.VisualHit == windowGrid;
}
// Nothing was hit - assume that this area is covered by frame extensions anyway
return true;
}
Теперь окно можно перемещать, щелкая и перетаскивая только незанятые области окна.
Но это не все. Вспомните в первом примере, что область без клиента, содержащая границы окна, также была затронута HTCAPTION
, поэтому окно больше не изменялось.
Чтобы исправить это, я должен был проверить, попал ли курсор в область клиента или область без клиента. Чтобы проверить это, мне нужно было использовать функцию DefWindowProc()
и посмотреть, вернула ли она HTCLIENT
:
// In my managed DWM API wrapper class, DwmApiInterop
public static bool IsOnClientArea(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam)
{
if (uMsg == WM_NCHITTEST)
{
if (DefWindowProc(hWnd, uMsg, wParam, lParam).ToInt32() == HTCLIENT)
{
return true;
}
}
return false;
}
// In NativeMethods
[DllImport("user32.dll")]
private static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);
Наконец, здесь мой последний метод процедуры окна:
// In MainWindow
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case DwmApiInterop.WM_NCHITTEST:
if (DwmApiInterop.IsOnClientArea(hwnd, msg, wParam, lParam)
&& IsOnExtendedFrame(lParam.ToInt32()))
{
handled = true;
return new IntPtr(DwmApiInterop.HTCAPTION);
}
return IntPtr.Zero;
default:
return IntPtr.Zero;
}
}