Rendre la fenêtre WPF déplaçable, quel que soit l'élément sur lequel vous avez cliqué

111

Ma question est double, et j'espère qu'il existe des solutions plus faciles à la fois fournies par WPF plutôt que les solutions standard de WinForms (que Christophe Geers a fournies, avant que j'aie fait cette clarification).

Tout d'abord, existe-t-il un moyen de rendre Window glissé sans capturer et traiter les événements clic-souris + glisser? Je veux dire que la fenêtre peut être déplacée par la barre de titre, mais si je configure une fenêtre pour ne pas en avoir et que je veux toujours pouvoir la faire glisser, y a-t-il un moyen de simplement rediriger les événements d'une manière ou d'une autre vers ce qui gère le glissement de la barre de titre ?

Deuxièmement, existe-t-il un moyen d'appliquer un gestionnaire d'événements à tous les éléments de la fenêtre? Comme dans, faites glisser la fenêtre quel que soit l'élément sur lequel l'utilisateur clique + fait glisser. Évidemment, sans ajouter le gestionnaire manuellement, à chaque élément. Le faire une fois quelque part?

Alex K
la source

Réponses:

284

Bien sûr, appliquez l' MouseDownévénement suivant de votreWindow

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

Cela permettra aux utilisateurs de faire glisser la fenêtre lorsqu'ils cliquent / font glisser sur n'importe quel contrôle, SAUF pour les contrôles qui mangent l'événement MouseDown ( e.Handled = true)

Vous pouvez utiliser à la PreviewMouseDownplace de MouseDown, mais l'événement de glissement mange l' Clickévénement, de sorte que votre fenêtre cesse de répondre aux événements de clic gauche de la souris. Si vous voulez VRAIMENT pouvoir cliquer et faire glisser le formulaire à partir de n'importe quel contrôle, vous pouvez probablement utiliser PreviewMouseDown, démarrer un minuteur pour commencer l'opération de glissement et annuler l'opération si l' MouseUpévénement se déclenche dans les X millisecondes.

Rachel
la source
+1. Il vaut mieux laisser le gestionnaire de fenêtres gérer le mouvement au lieu de le simuler en se souvenant de la position et en déplaçant la fenêtre. (Cette dernière méthode a également tendance à mal tourner dans certains cas extrêmes, de toute façon)
Joey
Pourquoi ne pas simplement définir l' MouseLeftButtonDownévénement, plutôt que d'archiver les fichiers .cs?
1
@Drowin Vous pourriez probablement utiliser cet événement à la place, mais assurez-vous de le tester d'abord car il MouseLeftButtonDowna une stratégie de routage direct tout en MouseDownayant une stratégie de routage bouillonnante. Consultez la section des remarques de la page MSDN pour MouseLeftButtonDown pour plus d'informations et pour quelques informations supplémentaires à prendre en compte si vous comptez utiliser MouseLeftButtonDownover MouseDown.
Rachel le
@Rachel Ouais je l'utilise et ça marche, mais merci pour l'explication!
2
@Rahul Faire glisser un UserControl est beaucoup plus difficile ... vous devrez le placer dans un panneau parent comme un Canvas et définir manuellement les propriétés X / Y (ou Canvas.Top et Canvas.Left) lorsque l'utilisateur déplace la souris. J'ai utilisé des événements de souris la dernière fois que j'ai fait cela, donc OnMouseDown capture la position et enregistre l'événement de déplacement, OnMouseMove change X / Y et OnMouseUp supprime l'événement de déplacement. C'est l'idée de base :)
Rachel
9

si le formulaire wpf doit être déplaçable quel que soit l'endroit où il a été cliqué, la solution de rechange facile consiste à utiliser un délégué pour déclencher la méthode DragMove () sur l'événement de chargement de Windows ou l'événement de chargement de la grille

private void Grid_Loaded(object sender, RoutedEventArgs 
{
      this.MouseDown += delegate{DragMove();};
}
Pranavan Maru
la source
2
J'ai ajouté ceci au constructeur. Fonctionne un charme.
Joe Johnston
1
Cela lèvera une exception si vous cliquez avec le bouton droit n'importe où sur le formulaire, car DragMovene peut être appelé que lorsque le bouton principal de la souris est enfoncé.
Stjepan Bakrac
4

Parfois, nous n'avons pas accès à Window, par exemple si nous utilisons DevExpress, tout ce qui est disponible est un fichier UIElement.

Étape 1: Ajouter une propriété jointe

La solution est de:

  1. Accrochez-vous aux MouseMoveévénements;
  2. Recherchez dans l'arborescence visuelle jusqu'à ce que nous trouvions le premier parent Window;
  3. Faites appel .DragMove()à notre nouvellement découvert 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);
        }
    }
}

Étape 2: Ajouter une propriété attachée à n'importe quel élément pour le laisser faire glisser la fenêtre

L'utilisateur peut faire glisser la fenêtre entière en cliquant sur un élément spécifique, si nous ajoutons cette propriété attachée:

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

Annexe A: Exemple avancé facultatif

Dans cet exemple de DevExpress , nous remplaçons la barre de titre d'une fenêtre d'ancrage par notre propre rectangle gris, puis nous nous assurons que si l'utilisateur clique et fait glisser ledit rectagle gris, la fenêtre se déplacera normalement:

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

Avertissement: je ne suis pas affilié à DevExpress . Cette technique fonctionnera avec n'importe quel élément utilisateur, y compris WPF standard ou Telerik (un autre fournisseur de bibliothèque WPF fin).

Contango
la source
1
C'est exactement ce que je voulais. À mon humble avis, tout le code WPF derrière doit être écrit en tant que comportement attaché.
fjch1997
3
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
    this.DragMove();
}

Lève une exception dans certains cas (c'est-à-dire si sur la fenêtre vous avez également une image cliquable qui, lorsque vous cliquez dessus, ouvre une boîte de message. Lorsque vous quittez la boîte de message, vous obtiendrez une erreur) Il est plus sûr d'utiliser

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

Vous êtes donc sûr que le bouton gauche est enfoncé à ce moment-là.

pseudo
la source
J'utilise e.LeftButtonau lieu de Mouse.LeftButtonpour utiliser spécifiquement le bouton associé aux arguments d'événement, même si cela n'aura probablement jamais d'importance.
Fls'Zen
2

Il est possible de glisser-déposer un formulaire en cliquant n'importe où sur le formulaire, pas seulement sur la barre de titre. Ceci est pratique si vous avez un formulaire sans bordure.

Cet article sur CodeProject montre une solution possible pour implémenter ceci:

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

Fondamentalement, un descendant du type Formulaire est créé dans lequel les événements de déplacement de la souris sont gérés.

  • Souris vers le bas: mémoriser la position
  • Déplacement de la souris: enregistrer un nouvel emplacement
  • Souris vers le haut: positionner le formulaire vers un nouvel emplacement

Et voici une solution similaire expliquée dans un didacticiel vidéo:

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

Je n'autoriserais pas de faire glisser le formulaire lorsqu'un utilisateur clique sur un contrôle dans ledit formulaire. Les utilisateurs obtiennent des résultats différents lorsqu'ils cliquent sur différents contrôles. Lorsque mon formulaire commence soudainement à bouger parce que j'ai cliqué sur une zone de liste, un bouton, une étiquette ... etc. ce serait déroutant.

Christophe Geers
la source
Bien sûr, il ne bougerait pas en cliquant sur un contrôle, mais si vous avez cliqué et fait glisser, vous ne vous attendriez pas à ce que le formulaire se déplace. Je veux dire que vous ne vous attendez pas à ce qu'un bouton ou une zone de liste se déplace, par exemple si vous cliquez + faites-le glisser, le mouvement du formulaire est une attente naturelle si vous essayez de cliquer et de faire glisser un bouton dans le formulaire, je pense.
Alex K le
Je suppose que c'est juste un goût personnel. Quoi qu'il en soit ... les contrôles devraient gérer les mêmes événements de souris. Vous devrez informer le formulaire parent de ces événements car ils ne bouillonnent pas.
Christophe Geers
De plus, alors que j'étais au courant de la solution WinForms à cela, j'espérais un moyen plus simple d'exister dans WPF, je suppose que je devrais clarifier cela dans la question (pour le moment, ce n'est qu'une balise).
Alex K le
Désolé mon mauvais. N'a pas remarqué la balise WPF. N'a pas été mentionné dans la question initiale. Je viens de supposer que WinForms par défaut, j'ai regardé la balise.
Christophe Geers
2

Comme déjà mentionné par @ fjch1997, il est pratique d'implémenter un comportement. Voilà, la logique de base est la même que dans la réponse 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();
        }
    }
}

Usage:

<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
la source
1

Tout cela est nécessaire!

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
la source
0

La méthode la plus utile, à la fois pour WPF et Windows Form, exemple 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
la source
0
<Window
...
WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
<x:Code>
    <![CDATA[            
        private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    ]]>
</x:Code>

la source

Grigor Yeghiazaryan
la source