Umożliwia przeciąganie okna WPF, bez względu na kliknięty element

111

Moje pytanie jest 2-krotne i mam nadzieję, że istnieją łatwiejsze rozwiązania dla obu zapewniane przez WPF, a nie standardowe rozwiązania z WinForms (które dostarczył Christophe Geers, zanim zrobiłem to wyjaśnienie).

Po pierwsze, czy istnieje sposób na umożliwienie przeciągania okna bez przechwytywania i przetwarzania zdarzeń kliknięcia i przeciągnięcia? Chodzi mi o to, że okno można przeciągać za pomocą paska tytułu, ale jeśli ustawię okno tak, aby go nie miało i nadal chcę móc go przeciągać, czy istnieje sposób, aby jakoś przekierować zdarzenia do tego, co obsługuje przeciąganie paska tytułu ?

Po drugie, czy istnieje sposób na zastosowanie procedury obsługi zdarzeń do wszystkich elementów w oknie? Tak jak w, ustaw okno do przeciągania bez względu na to, który element kliknie i przeciągnie użytkownik. Oczywiście bez ręcznego dodawania handlera do każdego elementu. Po prostu zrób to raz gdzieś?

Alex K.
źródło

Odpowiedzi:

284

Jasne, zastosuj następujące MouseDownzdarzenie swojegoWindow

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

Umożliwi to użytkownikom przeciąganie okna po kliknięciu / przeciągnięciu dowolnej kontrolki, Z WYJĄTKIEM kontrolek, które zjadają zdarzenie MouseDown ( e.Handled = true)

Możesz użyć PreviewMouseDownzamiast MouseDown, ale zdarzenie przeciągania zjada Clickzdarzenie, więc twoje okno przestaje reagować na zdarzenia kliknięcia lewym przyciskiem myszy. Jeśli NAPRAWDĘ chciałbyś mieć możliwość kliknięcia i przeciągnięcia formularza z dowolnej kontrolki, prawdopodobnie możesz użyć PreviewMouseDown, uruchomić licznik czasu, aby rozpocząć operację przeciągania i anulować operację, jeśli MouseUpzdarzenie zostanie uruchomione w ciągu X milisekund.

Rachel
źródło
+1. Znacznie lepiej pozwolić menedżerowi okien zająć się ruchem, zamiast udawać, że zapamiętuje pozycję i przesuwa okno. (W każdym razie ta ostatnia metoda ma również tendencję do niepowodzenia w niektórych skrajnych przypadkach)
Joey
Dlaczego po prostu nie ustawić MouseLeftButtonDownwydarzenia, zamiast sprawdzać pliki .cs?
1
@Drowin Prawdopodobnie możesz użyć tego zdarzenia zamiast tego, ale upewnij się, że najpierw je przetestujesz, ponieważ MouseLeftButtonDownma strategię routingu bezpośredniego, a MouseDownstrategię routingu bulgotania. Zobacz sekcję uwag na stronie MSDN dla MouseLeftButtonDown, aby uzyskać więcej informacji i kilka dodatkowych rzeczy, o których należy pamiętać, jeśli zamierzasz używać MouseLeftButtonDownover MouseDown.
Rachel,
@Rachel Tak, używam tego i działa, ale dzięki za wyjaśnienie!
2
@Rahul Przeciąganie elementu UserControl jest znacznie trudniejsze ... musisz umieścić go w panelu nadrzędnym, takim jak Canvas i ręcznie ustawić właściwości X / Y (lub Canvas.Top i Canvas.Left), gdy użytkownik porusza myszą. Użyłem zdarzeń myszy ostatnim razem, gdy to zrobiłem, więc OnMouseDown przechwytuje pozycję i rejestruje zdarzenie ruchu, OnMouseMove zmienia X / Y i OnMouseUp usuwa zdarzenie przenoszenia. To podstawowa idea :)
Rachel,
9

jeśli formularz wpf musi być przeciągany bez względu na to, gdzie został kliknięty, prostym obejściem jest użycie delegata do wywołania metody DragMove () na zdarzeniu onload systemu Windows lub zdarzeniu ładowania siatki

private void Grid_Loaded(object sender, RoutedEventArgs 
{
      this.MouseDown += delegate{DragMove();};
}
Pranavan Maru
źródło
2
Dodałem to do konstruktora. Działa czarująco.
Joe Johnston
1
Spowoduje to zgłoszenie wyjątku, jeśli klikniesz prawym przyciskiem myszy w dowolnym miejscu formularza, ponieważ DragMovemożna go wywołać tylko wtedy, gdy główny przycisk myszy jest wciśnięty.
Stjepan Bakrac
4

Czasami nie mamy dostępu Window, np. Jeśli korzystamy DevExpress, wszystko co jest dostępne to plikUIElement .

Krok 1: Dodaj dołączoną właściwość

Rozwiązaniem jest:

  1. Podłącz do MouseMove wydarzeń;
  2. Przeszukuj drzewo wizualne, aż znajdziemy pierwszego rodzica Window ;
  3. Zadzwoń .DragMove()do naszego nowo odkrytego Window.

Kod:

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

Krok 2: Dodaj dołączoną właściwość do dowolnego elementu, aby przeciągnąć okno

Użytkownik może przeciągnąć całe okno klikając na konkretny element, jeśli dodamy taką załączoną właściwość:

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

Dodatek A: Opcjonalny przykład zaawansowany

W tym przykładzie z DevExpress zastępujemy pasek tytułu okna dokowanego naszym własnym szarym prostokątem, a następnie upewniamy się, że jeśli użytkownik kliknie i przeciągnie szarego prostokąta, okno będzie się przeciągać normalnie:

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

Zastrzeżenie: Ja nie związany z DevExpress . Ta technika będzie działać z dowolnym elementem użytkownika, w tym standardowym WPF lub Telerik (inny dobry dostawca biblioteki WPF).

Contango
źródło
1
To jest dokładnie to, czego chciałem. IMHO cały kod WPF powinien być zapisany jako dołączone zachowanie.
fjch1997
3
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
    this.DragMove();
}

W niektórych przypadkach rzuca wyjątek (np. Jeśli w oknie masz również klikalny obraz, który po kliknięciu otwiera okno komunikatu. Po wyjściu z okna komunikatu pojawi się błąd). Bezpieczniej jest użyć

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

Więc masz pewność, że lewy przycisk jest wciśnięty w tym momencie.

Nacięcie
źródło
Używam e.LeftButtonzamiast Mouse.LeftButtonspecjalnie używać przycisku związanego z argumentami zdarzenia, mimo że prawdopodobnie nie będzie to miało znaczenia.
Fls'Zen
2

Formularz można przeciągać i upuszczać, klikając w dowolnym miejscu formularza, a nie tylko na pasku tytułu. Jest to przydatne, jeśli masz formularz bez obramowania.

W tym artykule na CodeProject przedstawiono jedno możliwe rozwiązanie do zaimplementowania tego:

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

Zasadniczo tworzony jest element potomny typu Form, w którym obsługiwane są zdarzenia myszy w dół, w górę i ruch.

  • Mysz w dół: zapamiętaj pozycję
  • Ruch myszy: zapisz nową lokalizację
  • Mysz w górę: umieść formularz w nowej lokalizacji

A oto podobne rozwiązanie wyjaśnione w samouczku wideo:

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

Nie pozwoliłbym na przeciąganie formularza, gdy użytkownik kliknie kontrolkę we wspomnianym formularzu. Użytkownicy uzyskują różne wyniki po kliknięciu różnych elementów sterujących. Kiedy mój formularz nagle zaczyna się poruszać, ponieważ kliknąłem pole listy, przycisk, etykietę ... itd. to byłoby mylące.

Christophe Geers
źródło
Na pewno nie poruszy się po kliknięciu dowolnej kontrolki, ale jeśli klikniesz i przeciągniesz, czy nie spodziewasz się, że formularz się poruszy. Chodzi mi o to, że nie spodziewałbyś się, że przycisk lub lista przesunie się, na przykład, jeśli klikniesz i przeciągniesz go, ruch formularza jest naturalnym oczekiwaniem, jeśli spróbujesz kliknąć i przeciągnąć przycisk w formularzu, tak myślę.
Alex K,
Chyba to tylko osobisty gust. W każdym razie ... kontrolki musiałyby obsługiwać te same zdarzenia myszy. Musiałbyś powiadomić formularz nadrzędny o tych wydarzeniach, ponieważ nie pojawiają się.
Christophe Geers,
Ponadto, chociaż byłem świadomy rozwiązania tego problemu przez WinForms, miałem nadzieję na łatwiejszy sposób zaistnienia w WPF, myślę, że powinienem wyjaśnić to w pytaniu (w tej chwili to tylko tag).
Alex K,
Przepraszam moja wina. Nie zauważyłem tagu WPF. Nie wspomniano w pierwotnym pytaniu. Po prostu założyłem, że domyślnie WinForms, przejrzałem tag.
Christophe Geers,
2

Jak już wspomniano w @ fjch1997 , wygodnie jest zaimplementować zachowanie. Tu jest logika rdzenia jest taki sam jak w @ loi.efy za odpowiedź :

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

Stosowanie:

<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
źródło
1

To wszystko jest potrzebne!

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
źródło
0

Najbardziej przydatna metoda, zarówno dla WPF, jak i formularza Windows, przykład 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
źródło
0
<Window
...
WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
<x:Code>
    <![CDATA[            
        private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    ]]>
</x:Code>

źródło

Grigor Yeghiazaryan
źródło