Зробіть перетягування вікна WPF, незалежно від того, який елемент натиснуто


111

Моє запитання вдвічі, і я сподіваюся, що є більш прості рішення як для WPF, а не для стандартних рішень від WinForms (які надав Крістоф Гірс, перш ніж я зробив це уточнення).

По-перше, чи є спосіб зробити Window перетягуванням без захоплення та обробки подій миші + перетягування? Я маю на увазі, що вікно перетягується за допомогою рядка заголовка, але якщо я встановив вікно, щоб воно не було, і я все ще хочу мати можливість перетягнути його, чи є спосіб просто перенаправити події якось на те, що обробляє перетягування рядка заголовка ?

По-друге, чи є спосіб застосувати обробник подій до всіх елементів у вікні? Як і в, зробіть перетягування вікна незалежно від того, який елемент користувач натискає + перетягує. Очевидно, не додаючи обробник вручну, до кожного окремого елемента. Просто зроби це колись десь?

Відповіді:


284

Звичайно, застосуйте наступну MouseDownподію вашогоWindow

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.ChangedButton == MouseButton.Left)
        this.DragMove();
}

Це дозволить користувачам перетягувати Вікно, коли вони клацають / перетягують будь-який елемент управління, ВИКЛЮЧЕНО для елементів керування, які їдять подію MouseDown ( e.Handled = true)

Ви можете використовувати PreviewMouseDownзамість MouseDown, але подія перетягування з'їдає Clickподію, тому ваше вікно перестає реагувати на події клацання лівою мишкою. Якщо ви дійсно хотіли мати можливість натискати та перетягувати форму з будь-якого елемента управління, ви, ймовірно, можете скористатися PreviewMouseDown, запустити таймер, щоб почати операцію перетягування, і скасувати операцію, якщо MouseUpподія запуститься протягом X мілісекунд.


+1. Набагато краще дозволити менеджеру вікон керувати ходом, а не підробляти його, запам'ятовуючи положення та переміщуючи вікно. (Останній метод у будь-якому випадку також має тенденцію помилятися в певних випадках)
Joey

Чому б просто не встановити MouseLeftButtonDownподію, а не перевірити в .cs?

1
@Drowin Ви, напевно, можете скористатись цією подією, але будьте впевнені, ви спершу протестуєте її, оскільки MouseLeftButtonDownмає пряму стратегію маршрутизації, а стратегія маршрутизації MouseDown- барботажна. Дивіться розділ Зауваження сторінки MSDN для MouseLeftButtonDown для отримання додаткової інформації, а також для деяких додаткових речей , щоб бути в курсі , якщо ви збираєтеся використовувати MouseLeftButtonDownбільш MouseDown.
Рейчел

@Rachel Так, я використовую це, і він працює, але дякую за пояснення!

2
@Rahul Перетягування UserControl набагато складніше ... вам потрібно буде розмістити його на батьківській панелі, як Canvas, і вручну встановити властивості X / Y (або Canvas.Top і Canvas.Left), коли користувач рухає мишу. Я використовував події миші минулого разу, коли я це робив, тому позицію захоплення OnMouseDown та реєструвати подію переміщення, OnMouseMove змінити X / Y та OnMouseUp видалити подію переміщення. Ось основна ідея цього :)
Рейчел

9

якщо форма wpf потребує перетягування незалежно від того, де було натиснено, легка робота навколо - це використання делегата для запуску методу DragMove () або події завантаження Windows або події завантаження сітки

private void Grid_Loaded(object sender, RoutedEventArgs 
{
      this.MouseDown += delegate{DragMove();};
}

2
Я додав це до конструктора. Працює шарм.
Джо Джонстон

1
Це викине виняток, якщо ви клацнете правою кнопкою миші десь у формі, тому що її DragMoveможна викликати лише тоді, коли основна кнопка миші вниз.
Степан Бакрач

4

Іноді ми не маємо доступу до них Window, наприклад, якщо ми використовуємо DevExpress, все, що є, - це UIElement.

Крок 1: Додати додане властивість

Рішення полягає в:

  1. Гак у MouseMoveподії;
  2. Шукайте візуальне дерево, поки ми не знайдемо першого батьків Window;
  3. Зателефонуйте .DragMove()до нашого нещодавно відкритого Window.

Код:

using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace DXApplication1.AttachedProperty
{
    public class EnableDragHelper
    {
        public static readonly DependencyProperty EnableDragProperty = DependencyProperty.RegisterAttached(
            "EnableDrag",
            typeof (bool),
            typeof (EnableDragHelper),
            new PropertyMetadata(default(bool), OnLoaded));

        private static void OnLoaded(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var uiElement = dependencyObject as UIElement;
            if (uiElement == null || (dependencyPropertyChangedEventArgs.NewValue is bool) == false)
            {
                return;
            }
            if ((bool)dependencyPropertyChangedEventArgs.NewValue  == true)
            {
                uiElement.MouseMove += UIElementOnMouseMove;
            }
            else
            {
                uiElement.MouseMove -= UIElementOnMouseMove;
            }

        }

        private static void UIElementOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            var uiElement = sender as UIElement;
            if (uiElement != null)
            {
                if (mouseEventArgs.LeftButton == MouseButtonState.Pressed)
                {
                    DependencyObject parent = uiElement;
                    int avoidInfiniteLoop = 0;
                    // Search up the visual tree to find the first parent window.
                    while ((parent is Window) == false)
                    {
                        parent = VisualTreeHelper.GetParent(parent);
                        avoidInfiniteLoop++;
                        if (avoidInfiniteLoop == 1000)
                        {
                            // Something is wrong - we could not find the parent window.
                            return;
                        }
                    }
                    var window = parent as Window;
                    window.DragMove();
                }
            }
        }

        public static void SetEnableDrag(DependencyObject element, bool value)
        {
            element.SetValue(EnableDragProperty, value);
        }

        public static bool GetEnableDrag(DependencyObject element)
        {
            return (bool)element.GetValue(EnableDragProperty);
        }
    }
}

Крок 2: Додайте властивість, що додається, до будь-якого елемента, щоб перетягнути вікно

Користувач може перетягнути все вікно, натиснувши на певний елемент, якщо ми додамо це додане властивість:

<Border local:EnableDragHelper.EnableDrag="True">
    <TextBlock Text="Click me to drag this entire window"/>
</Border>

Додаток A: Необов'язковий додатковий приклад

У цьому прикладі від DevExpress ми заміняємо рядок заголовка стикувального вікна на власний сірий прямокутник, а потім переконуємося, що якщо користувач натисне та перетягне згаданий сірий прямокутник, вікно перетягнеться нормально:

<dx:DXWindow x:Class="DXApplication1.MainWindow" Title="MainWindow" Height="464" Width="765" 
    xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking" 
    xmlns:local="clr-namespace:DXApplication1.AttachedProperty"
    xmlns:dxdove="http://schemas.devexpress.com/winfx/2008/xaml/docking/visualelements"
    xmlns:themeKeys="http://schemas.devexpress.com/winfx/2008/xaml/docking/themekeys">

    <dxdo:DockLayoutManager FloatingMode="Desktop">
        <dxdo:DockLayoutManager.FloatGroups>
            <dxdo:FloatGroup FloatLocation="0, 0" FloatSize="179,204" MaxHeight="300" MaxWidth="400" 
                             local:TopmostFloatingGroupHelper.IsTopmostFloatingGroup="True"                             
                             >
                <dxdo:LayoutPanel ShowBorder="True" ShowMaximizeButton="False" ShowCaption="False" ShowCaptionImage="True" 
                                  ShowControlBox="True" ShowExpandButton="True" ShowInDocumentSelector="True" Caption="TradePad General" 
                                  AllowDock="False" AllowHide="False" AllowDrag="True" AllowClose="False"
                                  >
                    <Grid Margin="0">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Border Grid.Row="0" MinHeight="15" Background="#FF515151" Margin="0 0 0 0"
                                                                  local:EnableDragHelper.EnableDrag="True">
                            <TextBlock Margin="4" Text="General" FontWeight="Bold"/>
                        </Border>
                        <TextBlock Margin="5" Grid.Row="1" Text="Hello, world!" />
                    </Grid>
                </dxdo:LayoutPanel>
            </dxdo:FloatGroup>
        </dxdo:DockLayoutManager.FloatGroups>
    </dxdo:DockLayoutManager>
</dx:DXWindow>

Відмова: Я не пов'язаний з DevExpress . Ця методика буде працювати з будь-яким користувальницьким елементом, включаючи стандартний WPF або Telerik (ще один прекрасний постачальник бібліотеки WPF).


1
Це саме те, що я хотів. IMHO весь код WPF позаду має бути записаний як поведінка, що додається.
fjch1997

3
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
    this.DragMove();
}

Кидає виняток у деяких випадках (наприклад, якщо у вікні також є зображення, яке можна натиснути, яке при натисканні відкриває вікно повідомлення. Коли ви виходите з поля повідомлення, ви отримаєте помилку) Це безпечніше використовувати

private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
            this.DragMove();
}

Тож ви впевнені, що в цей момент натиснута ліва кнопка.


Я використовую e.LeftButtonзамість того, Mouse.LeftButtonщоб спеціально використовувати кнопку, пов’язану з аргументами події, хоча це, мабуть, ніколи не матиме значення.
Fls'Zen

2

Перетягувати форму можна, клацнувши в будь-якому місці форми, а не лише заголовку. Це зручно, якщо у вас є форма без кордону.

Ця стаття про CodeProject демонструє одне можливе рішення для її реалізації:

http://www.codeproject.com/KB/cs/DraggableForm.aspx

В основному створюється нащадок типу Форма, в якому обробляються події миші, вгору та переміщення.

  • Вказівник миші: запам'ятайте положення
  • Рух миші: зберігайте нове місце
  • Наведення миші: форму позиції на нове місце

І ось подібне рішення пояснено у відеоуроці:

http://www.youtube.com/watch?v=tJlY9aX73Vs

Я не дозволяю перетягувати форму, коли користувач натискає на елемент керування у зазначеній формі. Користувачі очікують різних результатів, коли натискають на різні елементи керування. Коли моя форма раптово починає переміщатися, тому що я натиснув список, кнопку, мітку ... тощо. це було б заплутано.


Впевнений, що він не переміститься, натиснувши будь-який елемент керування, але якщо ви натиснули та перетягнули, чи не очікуєте, що форма переміститься. Я маю на увазі, що ви не очікуєте переміщення кнопки чи списку, наприклад, якщо натиснути + перетягнути її, рух форми - це природне очікування, якщо ви спробували натиснути та перетягнути кнопку у форму, я думаю.
Алекс К

Здогадайтесь, це лише особистий смак. У будь-якому випадку .... елементи управління повинні обробляти одні й ті ж події миші. Вам слід повідомити батьківську форму про ці події, оскільки вони не спливають.
Крістоф Гірс

Крім того, хоча я знав про рішення WinForms для цього, я сподівався на простіший спосіб існування в WPF, я думаю, я повинен зробити це зрозумілішим у питанні (зараз це лише тег).
Алекс К

Вибач, моя погана. Не помітив тег WPF. Не було згадано в первинному питанні. Я просто припустив, що WinForms за замовчуванням переглянув тег.
Крістоф Гірс

2

Як уже згадував @ fjch1997, це зручно реалізувати поведінку. Ось вона, основна логіка така ж , як в @ loi.efy в відповідь :

public class DragMoveBehavior : Behavior<Window>
{
    protected override void OnAttached()
    {
        AssociatedObject.MouseMove += AssociatedObject_MouseMove;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
    }

    private void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed && sender is Window window)
        {
            // In maximum window state case, window will return normal state and
            // continue moving follow cursor
            if (window.WindowState == WindowState.Maximized)
            {
                window.WindowState = WindowState.Normal;

                // 3 or any where you want to set window location after
                // return from maximum state
                Application.Current.MainWindow.Top = 3;
            }

            window.DragMove();
        }
    }
}

Використання:

<Window ...
        xmlns:h="clr-namespace:A.Namespace.Of.DragMoveBehavior"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
    <i:Interaction.Behaviors>
        <h:DragMoveBehavior />
    </i:Interaction.Behaviors>
    ...
</Window>

1

Це все потрібно!

private void UiElement_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            if (this.WindowState == WindowState.Maximized) // In maximum window state case, window will return normal state and continue moving follow cursor
            {
                this.WindowState = WindowState.Normal;
                Application.Current.MainWindow.Top = 3;// 3 or any where you want to set window location affter return from maximum state
            }
            this.DragMove();
        }
    }

0

Найбільш корисний метод, як для WPF, так і для Windows, приклад WPF:

    [DllImport("user32.dll")]
    public static extern IntPtr SendMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);

    public static void StartDrag(Window window)
    {
        WindowInteropHelper helper = new WindowInteropHelper(window);
        SendMessage(helper.Handle, 161, 2, 0);
    }

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.