Подтвердить что ты не робот

Как сделать окно WPF подвижным, перетащив расширенный оконный кадр?

В таких приложениях, как Проводник Windows и Internet Explorer, можно захватить расширенные области фреймов под заголовком и перетаскивать окна вокруг.

Для приложений WinForms формы и элементы управления так же близки к собственным API-интерфейсам Win32, которые они могут получить; можно просто переопределить обработчик WndProc() в своей форме, обработать окно WM_NCHITTEST и обмануть систему, подумав о щелчке по рамка была действительно нажатием на строку заголовка, возвращая HTCAPTION. Я сделал это в своих приложениях WinForms для восхитительного эффекта.

В WPF я также могу реализовать аналогичный метод WndProc() и привязать его к моему дескриптору окна WPF, расширяя рамку окна в клиентской области, например:

// In MainWindow
// For use with window frame extensions
private IntPtr hwnd;
private HwndSource hsource;

private void Window_SourceInitialized(object sender, EventArgs e)
{
    try
    {
        if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero)
        {
            throw new InvalidOperationException("Could not get window handle for the main window.");
        }

        hsource = HwndSource.FromHwnd(hwnd);
        hsource.AddHook(WndProc);

        AdjustWindowFrame();
    }
    catch (InvalidOperationException)
    {
        FallbackPaint();
    }
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case DwmApiInterop.WM_NCHITTEST:
            handled = true;
            return new IntPtr(DwmApiInterop.HTCAPTION);

        default:
            return IntPtr.Zero;
    }
}

Проблема заключается в том, что, поскольку я слепо устанавливаю handled = true и возвращаю HTCAPTION, щелчок в любом месте, кроме значка окна или кнопок управления, вызывает перетаскивание окна. То есть все, выделенное красным цветом, вызывает перетаскивание. Это даже включает в себя ручки изменения размера по бокам окна (неклиентская область). Мои элементы управления WPF, а именно текстовые поля и элемент управления вкладкой, также прекращают получать клики в результате:

Я хочу только

  • строка заголовка и
  • области клиентской области...
  • ... которые не заняты моими элементами управления

можно перетаскивать. То есть, я хочу, чтобы эти красные области были перетаскиваемыми (клиентская область + строка заголовка):

Как мне изменить мой метод WndProc() и остальную часть моего окна XAML/code-behind, чтобы определить, какие области должны возвращать HTCAPTION, а какие нет? Я думаю что-то вроде использования Point для проверки местоположения клика относительно местоположений моих элементов управления, но я не уверен, как это сделать на WPF-земле.

РЕДАКТИРОВАТЬ [4/24]: один простой способ: иметь невидимый элемент управления или даже само окно, ответить на MouseLeftButtonDown, вызвав DragMove() в окне (см. Ответ Росса). Проблема в том, что по какой-то причине DragMove() не работает, если окно максимизировано, поэтому оно не играет хорошо с Windows 7 Aero Snap. Поскольку я собираюсь для интеграции с Windows 7, это не приемлемое решение в моем случае.

4b9b3361

Ответ 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;
    }
}

Ответ 2

Здесь вы можете попробовать:

Получите местоположение щелчка мыши (из wParam, lParam, возможно?), и используйте его для создания Point (возможно, с каким-то преобразованием координат?).

Затем вызовите статический метод VisualTreeHelper.HitTest(Visual,Point), передав его this и Point, которые вы только что создали. Возвращаемое значение будет указывать на элемент управления с наивысшим Z-ордером. Если это ваше окно, то сделайте свой HTCAPTION вуду. Если это какой-то другой элемент управления, то... не делайте этого.

Удачи!

Ответ 3

Глядя на то же самое (сделайте мое расширенное стекло Aero перетаскиваемым в моем приложении WPF), я просто наткнулся на это сообщение через Google. Я прочитал ваш ответ, но решил продолжить поиски, чтобы увидеть, нет ли чего-то более простого.

Я нашел гораздо менее интенсивное решение.

Просто создайте прозрачный элемент за своими элементами управления и дайте ему обработчик события левой кнопки мыши, который вызывает окно DragMove().

Вот раздел моего XAML, который появляется над моим расширенным стеклом Aero:

<Grid DockPanel.Dock="Top">
    <Border MouseLeftButtonDown="Border_MouseLeftButtonDown" Background="Transparent" />
    <Grid><!-- My controls are in here --></Grid>
</Grid>

И код-позади (это находится в классе Window, поэтому DragMove() доступен для прямого вызова):

private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    DragMove();
}

И это! Для вашего решения вам нужно будет добавить более одного из них для достижения своей непрямоугольной области перетаскивания.

Ответ 4

простой способ создать стекную панель или все, что вам нужно для заголовка XAML

 <StackPanel Name="titleBar" Background="Gray" MouseLeftButtonDown="titleBar_MouseLeftButtonDown" Grid.ColumnSpan="2"></StackPanel>

код

  private void titleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
     {
         DragMove();
     }