Сделайте окно WPF перетаскиваемым, независимо от того, какой элемент нажат


111

У меня двоякий вопрос, и я надеюсь, что WPF предлагает более простые решения , чем стандартные решения WinForms (которые предоставил Кристоф Гирс, прежде чем я сделал это разъяснение).

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

Во-вторых, есть ли способ применить обработчик событий ко всем элементам в окне? Например, сделайте окно перетаскиваемым независимо от того, какой элемент пользователь щелкает + перетаскивает. Очевидно, без добавления обработчика вручную для каждого отдельного элемента. Просто сделать это хоть раз где-нибудь?

Ответы:


285

Конечно, примените следующее 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. Намного лучше позволить оконному менеджеру обрабатывать перемещение, а не имитировать его, запоминая положение и перемещая окно. (Последний метод также имеет тенденцию к сбоям в некоторых крайних случаях)
Джоуи

Почему бы просто не установить MouseLeftButtonDownсобытие, а не проверять .cs?

1
@Drowin Вы, вероятно, могли бы использовать это событие вместо этого, но сначала убедитесь, что вы его протестировали, поскольку у MouseLeftButtonDownнего есть стратегия прямой маршрутизации, а MouseDownесть стратегия восходящей маршрутизации. См. Раздел примечаний на странице MSDN для MouseLeftButtonDown для получения дополнительной информации и некоторых дополнительных вещей, о которых следует знать, если вы собираетесь использовать MouseLeftButtonDownover 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

Обычно создается потомок типа Form, в котором обрабатываются события мыши вниз, вверх и движение.

  • Мышь вниз: запомнить положение
  • Движение мыши: сохранить новое местоположение
  • Мышь вверх: переместить форму в новое место

А вот аналогичное решение, описанное в видеоуроке:

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

Я бы не позволил перетаскивать форму, когда пользователь нажимает на элемент управления в указанной форме. Пользователи получают разные результаты, когда нажимают на разные элементы управления. Когда моя форма внезапно начинает двигаться из-за того, что я щелкнул список, кнопку, метку и т. Д. это сбивает с толку.


Конечно, он не переместится, щелкнув любой элемент управления, но если вы щелкнете и перетащите, разве вы не ожидаете, что форма переместится. Я имею в виду, что вы не ожидаете, что кнопка или список переместятся, например, если вы нажмете + перетащите его, движение формы - это естественное ожидание, если вы попытались щелкнуть и перетащить кнопку в форме, я думаю.
Alex K

Думаю, это просто личный вкус. В любом случае .... элементы управления должны будут обрабатывать одни и те же события мыши. Вам нужно будет уведомить родительскую форму об этих событиях, поскольку они не всплывают.
Christophe Geers,

Кроме того, хотя я знал о решении этой проблемы WinForms, я надеялся на более простой способ существования в WPF, думаю, мне следует сделать это более ясным в вопросе (сейчас это просто тег).
Alex K

Извини, моя ошибка. Не заметил тега WPF. Не упоминалось в исходном вопросе. Я просто использовал WinForms по умолчанию, просмотрел тег.
Christophe Geers,

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, так и для окон, пример 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.