Haga que la ventana de WPF se pueda arrastrar, sin importar en qué elemento se haga clic

111

Mi pregunta es doble, y espero que haya soluciones más fáciles para ambas proporcionadas por WPF en lugar de las soluciones estándar de WinForms (que proporcionó Christophe Geers, antes de hacer esta aclaración).

Primero, ¿hay alguna manera de hacer que Window se pueda arrastrar sin capturar y procesar los eventos de clic del mouse + arrastrar? Me refiero a que la ventana se puede arrastrar por la barra de título, pero si configuro una ventana para que no tenga una y aún así quiero poder arrastrarla, ¿hay alguna manera de redirigir los eventos de alguna manera a lo que sea que maneje la barra de título arrastrando? ?

En segundo lugar, ¿hay alguna forma de aplicar un controlador de eventos a todos los elementos de la ventana? Al igual que en, haga que la ventana se pueda arrastrar sin importar en qué elemento haga clic + arrastre el usuario. Obviamente, sin agregar el controlador manualmente, a cada elemento. ¿Solo hacerlo una vez en algún lugar?

Alex K
fuente

Respuestas:

284

Claro, aplique el siguiente MouseDownevento de suWindow

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

Esto permitirá a los usuarios arrastrar la ventana cuando hagan clic / arrastre en cualquier control, EXCEPTO los controles que comen el evento MouseDown ( e.Handled = true)

Puede usar en PreviewMouseDownlugar de MouseDown, pero el evento de arrastre se come el Clickevento, por lo que su ventana deja de responder a los eventos de clic izquierdo del mouse. Si REALMENTE desea poder hacer clic y arrastrar el formulario desde cualquier control, probablemente podría usar PreviewMouseDown, iniciar un temporizador para comenzar la operación de arrastre y cancelar la operación si el MouseUpevento se dispara dentro de X milisegundos.

Raquel
fuente
+1. Es mucho mejor dejar que el administrador de ventanas maneje el movimiento en lugar de fingirlo recordando la posición y moviendo la ventana. (El último método también tiende a fallar en ciertos casos extremos, de todos modos)
Joey
¿Por qué no configurar el MouseLeftButtonDownevento en lugar de registrar el .cs?
1
@Drowin Probablemente podría usar ese evento en su lugar, pero asegúrese de probarlo primero ya que MouseLeftButtonDowntiene una estrategia de enrutamiento directo mientras que MouseDowntiene una estrategia de enrutamiento burbujeante. Consulte la sección de comentarios de la página de MSDN para MouseLeftButtonDown para obtener más información y algunas cosas adicionales que debe tener en cuenta si va a usar MouseLeftButtonDownover MouseDown.
Rachel
@Rachel Sí, lo estoy usando y funciona, ¡pero gracias por la explicación!
2
@Rahul Arrastrar un UserControl es mucho más difícil ... deberá colocarlo en un panel principal como un Canvas y configurar manualmente las propiedades X / Y (o Canvas.Top y Canvas.Left) a medida que el usuario mueve el mouse. Usé eventos de mouse la última vez que hice eso, por lo que OnMouseDown captura la posición y registra el evento de movimiento, OnMouseMove cambia X / Y y OnMouseUp elimina el evento de movimiento. Esa es la idea básica :)
Rachel
9

si el formulario wpf debe poder arrastrarse sin importar dónde se haya hecho clic, la solución fácil es usar un delegado para activar el método DragMove () en el evento de carga de Windows o en el evento de carga de la cuadrícula

private void Grid_Loaded(object sender, RoutedEventArgs 
{
      this.MouseDown += delegate{DragMove();};
}
Pranavan Maru
fuente
2
Agregué esto al constructor. Funciona de maravilla.
Joe Johnston
1
Esto generará una excepción si hace clic con el botón derecho en cualquier parte del formulario, porque DragMovesolo se puede llamar cuando el botón principal del mouse está presionado.
Stjepan Bakrac
4

A veces, no tenemos acceso a Window, por ejemplo, si estamos usando DevExpress, todo lo que está disponible es un UIElement.

Paso 1: agregar propiedad adjunta

La solucion es:

  1. Conéctate a los MouseMoveeventos;
  2. Busque en el árbol visual hasta encontrar el primer padre Window;
  3. Llame .DragMove()a nuestro recién descubierto Window.

Código:

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);
        }
    }
}

Paso 2: agregue la propiedad adjunta a cualquier elemento para permitir que arrastre la ventana

El usuario puede arrastrar toda la ventana haciendo clic en un elemento específico, si agregamos esta propiedad adjunta:

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

Apéndice A: Ejemplo avanzado opcional

En este ejemplo de DevExpress , reemplazamos la barra de título de una ventana de acoplamiento con nuestro propio rectángulo gris, luego nos aseguramos de que si el usuario hace clic y arrastra dicho rectángulo gris, la ventana se arrastrará normalmente:

<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>

Exención de responsabilidad: estoy no afiliado con DevExpress . Esta técnica funcionará con cualquier elemento de usuario, incluido el estándar WPF o Telerik (otro excelente proveedor de bibliotecas de WPF).

Aplazamiento de pago
fuente
1
Esto es exactamente lo que quería. En mi humilde opinión, todo el código WPF subyacente debe escribirse como comportamiento adjunto.
fjch1997
3
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
    this.DragMove();
}

Está lanzando una excepción en algunos casos (es decir, si en la ventana también tiene una imagen en la que se puede hacer clic, que cuando se hace clic se abre un cuadro de mensaje. Cuando salga del cuadro de mensaje, obtendrá un error) Es más seguro de usar

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

Así que está seguro de que el botón izquierdo está presionado en ese momento.

Mella
fuente
Estoy usando en e.LeftButtonlugar de Mouse.LeftButtonusar específicamente el botón asociado con los argumentos del evento, aunque probablemente nunca importe.
Fls'Zen
2

Es posible arrastrar y soltar un formulario haciendo clic en cualquier parte del formulario, no solo en la barra de título. Esto es útil si tiene un formulario sin bordes.

Este artículo sobre CodeProject demuestra una posible solución para implementar esto:

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

Básicamente, se crea un descendiente del tipo Form en el que se manejan los eventos de mouse hacia abajo, arriba y mover.

  • Ratón abajo: recuerda la posición
  • Movimiento del mouse: almacenar nueva ubicación
  • Mouse hacia arriba: coloque el formulario en una nueva ubicación

Y aquí hay una solución similar explicada en un video tutorial:

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

No permitiría arrastrar el formulario cuando un usuario haga clic en un control en dicho formulario. Los usuarios obtienen diferentes resultados cuando hacen clic en diferentes controles. Cuando mi formulario de repente comienza a moverse porque hice clic en un cuadro de lista, un botón, una etiqueta, etc. eso sería confuso.

Christophe Geers
fuente
Seguro que no se moverá haciendo clic en ningún control, pero si hace clic y arrastra, ¿no esperaría que el formulario se mueva? Quiero decir que no esperaría que un botón o un cuadro de lista se mueva, por ejemplo, si hace clic + lo arrastra, el movimiento del formulario es una expectativa natural si intenta hacer clic y arrastrar un botón en el formulario, creo.
Alex K
Supongo que eso es solo gusto personal. De todos modos .... los controles necesitarían manejar los mismos eventos del mouse. Tendría que notificar al formulario principal de estos eventos, ya que no aparecen.
Christophe Geers
Además, aunque conocía la solución de WinForms para esto, esperaba una forma más fácil de existir en WPF, supongo que debería aclarar esto en la pregunta (en este momento es solo una etiqueta).
Alex K
Perdón, es mi culpa. No noté la etiqueta WPF. No se mencionó en la pregunta original. Simplemente asumí WinForms por defecto, miré la etiqueta.
Christophe Geers
2

Como ya lo mencionó @ fjch1997 , es conveniente implementar un comportamiento. Aquí está, la lógica central es la misma que en la respuesta de @ 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();
        }
    }
}

Uso:

<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>
stop-cran
fuente
1

¡Todo esto es necesario!

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();
        }
    }
loi.efy
fuente
0

El método más útil, tanto para WPF como para Windows, ejemplo de 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);
    }
dexiang
fuente
0
<Window
...
WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
<x:Code>
    <![CDATA[            
        private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    ]]>
</x:Code>

fuente

Grigor Yeghiazaryan
fuente