Torne a janela WPF arrastável, independentemente do elemento clicado

111

Minha pergunta é dupla, e espero que haja soluções mais fáceis para ambas fornecidas pelo WPF, em vez das soluções padrão do WinForms (que Christophe Geers forneceu, antes de eu fazer este esclarecimento).

Primeiro, há uma maneira de tornar a janela arrastável sem capturar e processar eventos de clique do mouse + arrastar? Quer dizer, a janela pode ser arrastada pela barra de título, mas se eu definir uma janela para não ter uma e ainda quiser ser capaz de arrastá-la, há uma maneira de redirecionar os eventos de alguma forma para o que quer que lide com o arrasto da barra de título ?

Em segundo lugar, existe uma maneira de aplicar um manipulador de eventos a todos os elementos da janela? Como em, torne a janela arrastável independentemente do elemento que o usuário clicar e arrastar. Obviamente, sem adicionar o manipulador manualmente, a cada elemento. Basta fazer isso uma vez em algum lugar?

Alex K
fonte

Respostas:

284

Claro, aplique o seguinte MouseDownevento de seuWindow

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

Isso permitirá que os usuários arrastem a janela ao clicar / arrastar em qualquer controle, EXCETO para controles que comem o evento MouseDown ( e.Handled = true)

Você pode usar em PreviewMouseDownvez de MouseDown, mas o evento arrastar devora o Clickevento, de forma que sua janela pare de responder aos eventos de clique com o botão esquerdo do mouse. Se você realmente quisesse clicar e arrastar o formulário de qualquer controle, provavelmente poderia usar PreviewMouseDown, iniciar um cronômetro para iniciar a operação de arrastar e cancelar a operação se o MouseUpevento disparar dentro de X milissegundos.

Rachel
fonte
+1. É muito melhor deixar o gerenciador de janelas controlar a movimentação em vez de fingir, lembrando-se da posição e movendo a janela. (O último método também tende a dar errado em certos casos extremos, de qualquer maneira)
Joey
Por que não apenas definir o MouseLeftButtonDownevento, em vez de fazer check-in no .cs?
1
@Drowin Você provavelmente poderia usar esse evento em vez disso, mas certifique-se de testá-lo primeiro, pois MouseLeftButtonDowntem uma estratégia de roteamento direto enquanto MouseDowntem uma estratégia de roteamento borbulhante. Consulte a seção de comentários da página do MSDN para MouseLeftButtonDown para obter mais informações e para algumas coisas extras que você deve saber se for usar MouseLeftButtonDownover MouseDown.
Rachel
@Rachel Sim, estou usando e funciona, mas obrigado pela explicação!
2
@Rahul Arrastar um UserControl é muito mais difícil ... você precisará colocá-lo em um painel pai como um Canvas e definir manualmente as propriedades X / Y (ou Canvas.Top e Canvas.Left) conforme o usuário move o mouse. Usei eventos de mouse da última vez que fiz isso, então OnMouseDown captura posição e registra evento de movimentação, OnMouseMove altera X / Y e OnMouseUp remove evento de movimentação. Essa é a ideia básica disso :)
Rachel
9

se o formulário wpf precisa ser arrastado, não importa onde foi clicado, a solução mais fácil é usar um delegado para acionar o método DragMove () no evento Windows onload ou no evento de carregamento da grade

private void Grid_Loaded(object sender, RoutedEventArgs 
{
      this.MouseDown += delegate{DragMove();};
}
Pranavan Maru
fonte
2
Eu adicionei isso ao construtor. Funciona muito bem.
Joe Johnston
1
Isso lançará uma exceção se você clicar com o botão direito em qualquer lugar no formulário, porque DragMovesó pode ser chamado quando o botão principal do mouse está pressionado.
Stjepan Bakrac
4

Às vezes, não temos acesso a Window, por exemplo, se estivermos usando DevExpress, tudo o que está disponível é um UIElement.

Etapa 1: Adicionar propriedade anexada

A solução é:

  1. Conecte-se aos MouseMoveeventos;
  2. Pesquise na árvore visual até encontrar o primeiro pai Window;
  3. Ligue .DragMove()para o nosso recém-descoberto 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);
        }
    }
}

Etapa 2: adicione propriedade anexada a qualquer elemento para permitir que ele arraste a janela

O usuário pode arrastar a janela inteira clicando em um elemento específico, se adicionarmos esta propriedade anexada:

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

Apêndice A: Exemplo avançado opcional

Neste exemplo do DevExpress , substituímos a barra de título de uma janela de encaixe por nosso próprio retângulo cinza e garantimos que, se o usuário clicar e arrastar o retângulo cinza, a janela se arrastará 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>

Disclaimer: Eu estou não afiliado com DevExpress . Esta técnica funcionará com qualquer elemento do usuário, incluindo WPF padrão ou Telerik (outro excelente provedor de biblioteca WPF).

Contango
fonte
1
Isso é exatamente o que eu queria. IMHO todo o código WPF por trás deve ser escrito como comportamento anexado.
fjch1997
3
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
    this.DragMove();
}

Está gerando uma exceção em alguns casos (por exemplo, se na janela você também tiver uma imagem clicável que, ao ser clicada, abrirá uma caixa de mensagem. Quando você sair da caixa de mensagem, receberá um erro) É mais seguro usar

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

Portanto, você tem certeza de que o botão esquerdo foi pressionado naquele momento.

usuario
fonte
Estou usando em e.LeftButtonvez de Mouse.LeftButtonpara usar especificamente o botão associado aos argumentos do evento, embora isso provavelmente nunca importe.
Fls'Zen
2

É possível arrastar e soltar um formulário clicando em qualquer lugar no formulário, não apenas na barra de título. Isso é útil se você tiver um formulário sem borda.

Este artigo sobre CodeProject demonstra uma solução possível para implementar isso:

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

Basicamente, um descendente do tipo Form é criado, no qual os eventos para cima, para baixo e para baixo do mouse são manipulados.

  • Mouse para baixo: lembrar posição
  • Movimento do mouse: armazenar novo local
  • Mouse para cima: posicione o formulário no novo local

E aqui está uma solução semelhante explicada em um tutorial em vídeo:

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

Eu não permitiria arrastar o formulário quando um usuário clicar em um controle nesse formulário. Os usuários obtêm resultados diferentes quando clicam em controles diferentes. Quando meu formulário começa a se mover repentinamente porque cliquei em uma caixa de listagem, botão, rótulo ... etc. isso seria confuso.

Christophe Geers
fonte
Claro, ele não se moveria ao clicar em qualquer controle, mas se você clicar e arrastar, não esperaria que o formulário se movesse. Quero dizer, você não esperaria que um botão ou uma caixa de listagem se movesse, por exemplo, se você clicar e arrastar, o movimento do formulário é uma expectativa natural se você tentar clicar e arrastar um botão no formulário, eu acho.
Alex K,
Acho que é apenas gosto pessoal. De qualquer forma ... os controles precisariam lidar com os mesmos eventos do mouse. Você teria que notificar o formulário pai sobre esses eventos, pois eles não aparecem.
Christophe Geers,
Além disso, embora eu estivesse ciente da solução WinForms para isso, esperava uma maneira mais fácil de existir no WPF, acho que devo deixar isso mais claro na pergunta (agora é apenas uma tag).
Alex K,
Desculpe, minha culpa. Não percebeu a marca WPF. Não foi mencionado na pergunta original. Eu apenas assumi WinForms por padrão e examinei a tag.
Christophe Geers,
2

Como já mencionado por @ fjch1997 , é conveniente implementar um comportamento. Aqui está, a lógica central é a mesma da resposta 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
fonte
1

Tudo isso é necessário!

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

O método mais útil, tanto para WPF quanto para Windows Form, exemplo 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
fonte
0
<Window
...
WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
<x:Code>
    <![CDATA[            
        private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    ]]>
</x:Code>

fonte

Grigor Yeghiazaryan
fonte