Rendi la finestra WPF trascinabile, indipendentemente dall'elemento su cui si fa clic


111

La mia domanda è doppia e spero che ci siano soluzioni più semplici per entrambe fornite da WPF piuttosto che le soluzioni standard di WinForms (fornite da Christophe Geers, prima che io abbia fatto questo chiarimento).

Innanzitutto, esiste un modo per rendere Window trascinabile senza acquisire ed elaborare eventi di clic del mouse + trascinamento? Voglio dire che la finestra è trascinabile dalla barra del titolo, ma se imposto una finestra in modo che non ne abbia una e voglio comunque essere in grado di trascinarla, c'è un modo per reindirizzare gli eventi in qualche modo a qualunque cosa gestisca il trascinamento della barra del titolo ?

In secondo luogo, c'è un modo per applicare un gestore di eventi a tutti gli elementi nella finestra? Come in, rendi la finestra trascinabile indipendentemente dall'elemento su cui l'utente fa clic + trascina. Ovviamente senza aggiungere manualmente il gestore, ad ogni singolo elemento. Farlo solo una volta da qualche parte?

Risposte:


284

Certo, applica il seguente MouseDownevento del tuoWindow

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

Ciò consentirà agli utenti di trascinare la finestra quando fanno clic / trascina su qualsiasi controllo, TRANNE i controlli che mangiano l'evento MouseDown ( e.Handled = true)

Puoi usare al PreviewMouseDownposto di MouseDown, ma l'evento di trascinamento mangia l' Clickevento, quindi la tua finestra smette di rispondere agli eventi di clic con il pulsante sinistro del mouse. Se volevi VERAMENTE essere in grado di fare clic e trascinare il modulo da qualsiasi controllo, potresti probabilmente usare PreviewMouseDown, avviare un timer per iniziare l'operazione di trascinamento e annullare l'operazione se l' MouseUpevento si attiva entro X millisecondi.


+1. Molto meglio lasciare che il window manager gestisca la mossa invece di fingere ricordando la posizione e spostando la finestra. (Quest'ultimo metodo ha anche la tendenza a sbagliare in alcuni casi limite, comunque)
Joey

Perché non impostare semplicemente l' MouseLeftButtonDownevento, anziché archiviare i file .cs?

1
@Drowin Potresti probabilmente usare quell'evento invece, ma assicurati di testarlo prima poiché MouseLeftButtonDownha una strategia di routing diretto mentre MouseDownha una strategia di routing bubbling. Vedere la sezione delle osservazioni della pagina MSDN per MouseLeftButtonDown per ulteriori informazioni e per alcune cose extra di cui tenere conto se si intende utilizzare MouseLeftButtonDownover MouseDown.
Rachel

@ Rachel Sì, lo sto usando e funziona, ma grazie per la spiegazione!

2
@Rahul Trascinare un UserControl è molto più difficile ... dovrai posizionarlo in un pannello genitore come un Canvas e impostare manualmente le proprietà X / Y (o Canvas.Top e Canvas.Left) mentre l'utente sposta il mouse. L'ultima volta ho usato gli eventi del mouse, quindi OnMouseDown cattura la posizione e registra l'evento di spostamento, OnMouseMove cambia X / Y e OnMouseUp rimuove l'evento di spostamento. Questa è l'idea di base :)
Rachel

9

se il modulo wpf deve essere trascinabile indipendentemente da doveèstato cliccato, la soluzione facile è usare un delegato per attivare il metodo DragMove () sull'evento di windows onload o sull'evento di caricamento della griglia

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

2
L'ho aggiunto al costruttore. Funziona un fascino.
Joe Johnston,

1
Ciò genererà un'eccezione se fai clic con il pulsante destro del mouse in un punto qualsiasi del modulo, perché DragMovepuò essere chiamato solo quando il pulsante principale del mouse è premuto.
Stjepan Bakrac

4

A volte, non abbiamo accesso a Window, ad esempio, se stiamo utilizzando DevExpress, tutto ciò che è disponibile è un file UIElement.

Passaggio 1: aggiungi proprietà allegata

La soluzione è:

  1. Agganciarsi agli MouseMoveeventi;
  2. Cerca nell'albero visivo finché non troviamo il primo genitore Window;
  3. Chiama .DragMove()il nostro appena scoperto Window.

Codice:

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

Passaggio 2: aggiungi proprietà collegata a qualsiasi elemento per trascinarlo nella finestra

L'utente può trascinare l'intera finestra facendo clic su un elemento specifico, se aggiungiamo questa proprietà allegata:

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

Appendice A: esempio avanzato opzionale

In questo esempio da DevExpress , sostituiamo la barra del titolo di una finestra di ancoraggio con il nostro rettangolo grigio, quindi assicuriamo che se l'utente fa clic e trascina detto rettangolo grigio, la finestra si trascina 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>

Dichiarazione di non responsabilità: non sono affiliato con DevExpress . Questa tecnica funzionerà con qualsiasi elemento utente, inclusi WPF standard o Telerik (un altro ottimo provider di librerie WPF).


1
Questo è esattamente quello che volevo. IMHO tutto il codice WPF dietro dovrebbe essere scritto come comportamento allegato.
fjch1997

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

Genera un'eccezione in alcuni casi (cioè se nella finestra hai anche un'immagine cliccabile che se cliccata apre una finestra di messaggio. Quando esci dalla finestra di messaggio riceverai un errore) È più sicuro da usare

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

Quindi sei sicuro che il pulsante sinistro sia premuto in quel momento.


Sto usando e.LeftButtoninvece di Mouse.LeftButtonusare specificamente il pulsante associato agli argomenti dell'evento, anche se probabilmente non avrà mai importanza.
Fls'Zen

2

È possibile trascinare e rilasciare un modulo facendo clic in un punto qualsiasi del modulo, non solo sulla barra del titolo. Questo è utile se hai un modulo senza bordi.

Questo articolo su CodeProject mostra una possibile soluzione per implementare questo:

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

Fondamentalmente viene creato un discendente del tipo Form in cui vengono gestiti gli eventi del mouse giù, su e di spostamento.

  • Mouse giù: ricorda la posizione
  • Spostamento del mouse: memorizza una nuova posizione
  • Mouse su: posiziona il modulo nella nuova posizione

Ed ecco una soluzione simile spiegata in un video tutorial:

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

Non consentirei il trascinamento del modulo quando un utente fa clic su un controllo in detto modulo. Gli utenti ottengono risultati diversi quando fanno clic su controlli diversi. Quando il mio modulo inizia improvvisamente a muoversi perché ho fatto clic su una casella di riepilogo, un pulsante, un'etichetta ... ecc. sarebbe fonte di confusione.


Certo non si sposterebbe facendo clic su alcun controllo, ma se facessi clic e trascinassi, non ti aspetteresti che il modulo si sposti. Voglio dire, non ti aspetteresti che un pulsante o una casella di riepilogo si muovano, ad esempio se fai clic e trascini, il movimento del modulo è un'aspettativa naturale se provassi a fare clic e trascinare un pulsante nel modulo, credo.
Alex K

Immagino, questo è solo gusto personale. Comunque ... i controlli dovrebbero gestire gli stessi eventi del mouse. Dovresti avvisare il modulo genitore di questi eventi poiché non si manifestano.
Christophe Geers

Inoltre, mentre ero a conoscenza della soluzione WinForms a questo, speravo in un modo più semplice per esistere in WPF, immagino che dovrei renderlo più chiaro nella domanda (in questo momento è solo un tag).
Alex K

Scusa colpa mia. Non ho notato il tag WPF. Non è stato menzionato nella domanda originale. Ho appena assunto WinForms per impostazione predefinita, ho controllato il tag.
Christophe Geers

2

Come già accennato da @ fjch1997 è conveniente implementare un comportamento. Ecco, la logica di base è la stessa della risposta di @ 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>

1

È tutto ciò che serve!

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

Il metodo più utile, sia per WPF che per Windows Form, esempio 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);
    }

0
<Window
...
WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
<x:Code>
    <![CDATA[            
        private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    ]]>
</x:Code>

fonte

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.