Machen Sie das WPF-Fenster ziehbar, unabhängig davon, auf welches Element geklickt wird

111

Meine Frage ist zweifach und ich hoffe, dass es für beide von WPF bereitgestellte Lösungen einfacher gibt als für die Standardlösungen von WinForms (die Christophe Geers bereitgestellt hat, bevor ich diese Klarstellung vorgenommen habe).

Gibt es eine Möglichkeit, das Fenster ziehbar zu machen, ohne Ereignisse mit Mausklick und Ziehen zu erfassen und zu verarbeiten? Ich meine, das Fenster kann über die Titelleiste gezogen werden, aber wenn ich ein Fenster so einstelle, dass es keines hat und es trotzdem ziehen kann, gibt es eine Möglichkeit, die Ereignisse einfach irgendwie auf das zu lenken, was das Ziehen der Titelleiste behandelt ?

Zweitens gibt es eine Möglichkeit, einen Ereignishandler auf alle Elemente im Fenster anzuwenden? Machen Sie das Fenster wie in ziehbar, unabhängig davon, auf welches Element der Benutzer klickt + zieht. Natürlich ohne den Handler manuell zu jedem einzelnen Element hinzuzufügen. Mach es einfach einmal irgendwo?

Alex K.
quelle

Antworten:

284

Sicher, wenden Sie das folgende MouseDownEreignis von Ihnen anWindow

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

Auf diese Weise können Benutzer das Fenster ziehen, wenn sie auf ein Steuerelement klicken / ziehen, AUSSER für Steuerelemente, die das MouseDown-Ereignis ( e.Handled = true) essen.

Sie können PreviewMouseDownanstelle von verwenden MouseDown, aber das Drag-Ereignis frisst das ClickEreignis, sodass Ihr Fenster nicht mehr auf Ereignisse mit der linken Maustaste reagiert. Wenn Sie WIRKLICH in der Lage sein möchten, auf das Formular von einem Steuerelement aus zu klicken und es zu ziehen, können Sie wahrscheinlich PreviewMouseDowneinen Timer starten, um den Ziehvorgang zu starten, und den Vorgang abbrechen, wenn das MouseUpEreignis innerhalb von X Millisekunden ausgelöst wird.

Rachel
quelle
+1. Es ist viel besser, den Fenstermanager die Bewegung ausführen zu lassen, als sie vorzutäuschen, indem er sich an die Position erinnert und das Fenster bewegt. (Die letztere Methode hat sowieso auch die Tendenz, in bestimmten Randfällen schief zu gehen)
Joey
Warum nicht einfach das MouseLeftButtonDownEreignis festlegen , anstatt die .cs einzuchecken?
1
@Drowin Sie könnten dieses Ereignis wahrscheinlich stattdessen verwenden, aber stellen Sie sicher, dass Sie es zuerst testen, da MouseLeftButtonDownes eine direkte Routing-Strategie und MouseDowneine sprudelnde Routing-Strategie hat. Weitere Informationen und einige zusätzliche Dinge, die Sie beachten sollten, wenn Sie over verwenden, finden Sie im Abschnitt "Anmerkungen" auf der MSDN-Seite für MouseLeftButtonDown . MouseLeftButtonDownMouseDown
Rachel
@ Rachel Ja, ich benutze es und es funktioniert, aber danke für die Erklärung!
2
@Rahul Das Ziehen eines UserControls ist viel schwieriger ... Sie müssen es wie ein Canvas in einem übergeordneten Bedienfeld platzieren und die X / Y-Eigenschaften (oder Canvas.Top und Canvas.Left) manuell festlegen, wenn der Benutzer die Maus bewegt. Ich habe das letzte Mal Mausereignisse verwendet, also hat OnMouseDown die Position erfasst und das Verschiebungsereignis registriert, OnMouseMove X / Y geändert und OnMouseUp das Verschiebungsereignis entfernt. Das ist die Grundidee davon :)
Rachel
9

Wenn das wpf-Formular unabhängig davon, wo es angeklickt wurde, ziehbar sein muss, wird bei der einfachen Umgehung ein Delegat verwendet, um die DragMove () -Methode entweder für das Windows-Onload-Ereignis oder das Grid-Load-Ereignis auszulösen

private void Grid_Loaded(object sender, RoutedEventArgs 
{
      this.MouseDown += delegate{DragMove();};
}
Pranavan Maru
quelle
2
Ich habe dies dem Konstruktor hinzugefügt. Wirkt ein Zauber.
Joe Johnston
1
Dies löst eine Ausnahme aus, wenn Sie mit der rechten DragMoveMaustaste auf eine beliebige Stelle im Formular klicken, da diese nur aufgerufen werden kann, wenn die primäre Maustaste gedrückt ist.
Stjepan Bakrac
4

Manchmal haben wir keinen Zugriff auf Window, z. B. wenn wir verwenden DevExpress, ist nur a verfügbar UIElement.

Schritt 1: Fügen Sie eine angehängte Eigenschaft hinzu

Die Lösung ist:

  1. Haken Sie in MouseMoveEreignisse ein;
  2. Durchsuchen Sie den visuellen Baum, bis wir den ersten Elternteil finden Window.
  3. Rufen Sie .DragMove()unseren neu entdeckten an Window.

Code:

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

Schritt 2: Fügen Sie jedem Element eine angehängte Eigenschaft hinzu, damit es das Fenster ziehen kann

Der Benutzer kann das gesamte Fenster ziehen, indem er auf ein bestimmtes Element klickt, wenn wir diese angehängte Eigenschaft hinzufügen:

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

Anhang A: Optionales erweitertes Beispiel

In diesem Beispiel von DevExpress ersetzen wir die Titelleiste eines Docking-Fensters durch unser eigenes graues Rechteck und stellen dann sicher, dass das Fenster normal gezogen wird, wenn der Benutzer auf das graue Rechteck klickt und es zieht:

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

Haftungsausschluss: Ich bin nicht mit DevExpress verbunden . Diese Technik funktioniert mit jedem Benutzerelement, einschließlich Standard-WPF oder Telerik (einem anderen guten WPF-Bibliotheksanbieter).

Contango
quelle
1
Genau das wollte ich. IMHO sollte der gesamte dahinter stehende WPF-Code als angehängtes Verhalten geschrieben werden.
fjch1997
3
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
    this.DragMove();
}

Löst in einigen Fällen eine Ausnahme aus (dh wenn Sie im Fenster auch ein anklickbares Bild haben, das beim Klicken ein Meldungsfeld öffnet. Wenn Sie das Meldungsfeld verlassen, wird eine Fehlermeldung angezeigt). Die Verwendung ist sicherer

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

Sie sind also sicher, dass die linke Taste in diesem Moment gedrückt wird.

Nick
quelle
Ich verwende e.LeftButtonanstatt Mouse.LeftButtonspeziell die Schaltfläche zu verwenden, die den Ereignisargumenten zugeordnet ist, obwohl es wahrscheinlich nie wichtig sein wird.
Fls'Zen
2

Sie können ein Formular ziehen und ablegen, indem Sie auf eine beliebige Stelle im Formular klicken, nicht nur auf die Titelleiste. Dies ist praktisch, wenn Sie ein randloses Formular haben.

Dieser Artikel über CodeProject zeigt eine mögliche Lösung, um dies zu implementieren:

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

Grundsätzlich wird ein Nachkomme des Formulartyps erstellt, in dem Ereignisse mit der Maus nach unten, oben und nach oben behandelt werden.

  • Maus runter: Position merken
  • Maus bewegen: Neuen Standort speichern
  • Maus hoch: Positionieren Sie das Formular an einem neuen Ort

Und hier ist eine ähnliche Lösung, die in einem Video-Tutorial erklärt wird:

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

Ich würde das Ziehen des Formulars nicht zulassen, wenn ein Benutzer auf ein Steuerelement in diesem Formular klickt. Benutzer zeigen unterschiedliche Ergebnisse an, wenn sie auf unterschiedliche Steuerelemente klicken. Wenn sich mein Formular plötzlich bewegt, weil ich auf ein Listenfeld, eine Schaltfläche, eine Beschriftung usw. geklickt habe. das wäre verwirrend.

Christophe Geers
quelle
Sicher, es würde sich nicht durch Klicken auf ein Steuerelement bewegen, aber wenn Sie klicken und ziehen würden, würden Sie nicht erwarten, dass sich das Formular bewegt. Ich meine, Sie würden nicht erwarten, dass sich eine Schaltfläche oder ein Listenfeld bewegt, wenn Sie beispielsweise darauf klicken und es ziehen. Die Bewegung des Formulars ist eine natürliche Erwartung, wenn Sie versuchen, auf eine Schaltfläche im Formular zu klicken und sie zu ziehen, denke ich.
Alex K
Ich denke, das ist nur persönlicher Geschmack. Wie auch immer ... die Steuerelemente müssten dieselben Mausereignisse verarbeiten. Sie müssten die übergeordnete Form über diese Ereignisse informieren, da sie nicht in die Luft sprudeln.
Christophe Geers
Auch wenn ich mir der WinForms-Lösung dafür bewusst war, hoffte ich auf eine einfachere Möglichkeit, in WPF zu existieren. Ich denke, ich sollte dies in der Frage klarer machen (im Moment ist es nur ein Tag).
Alex K
Es tut mir leid. Das WPF-Tag wurde nicht bemerkt. Wurde in der ursprünglichen Frage nicht erwähnt. Ich habe gerade standardmäßig WinForms angenommen und mir das Tag angesehen.
Christophe Geers
2

Wie bereits von @ fjch1997 erwähnt, ist es praktisch, ein Verhalten zu implementieren. Hier ist die Kernlogik dieselbe wie in der Antwort von @ 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();
        }
    }
}

Verwendung:

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

Das ist alles nötig!

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

Die nützlichste Methode, sowohl für WPF- als auch für Windows-Formulare, Beispiel für 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
quelle
0
<Window
...
WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
<x:Code>
    <![CDATA[            
        private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    ]]>
</x:Code>

Quelle

Grigor Yeghiazaryan
quelle