125
votes

My question is 2 fold, and I am hoping there are easier solutions to both provided by WPF rather than the standard solutions from WinForms (which Christophe Geers provided, before I've made this clarification).

First, is there a way to make Window draggable without capturing and processing mouse-click+drag events? I mean the window is draggable by the title bar, but if I set a window not to have one and still want to be able to drag it, is there a way to just re-direct the events somehow to whatever handles the title bar dragging?

Second, is there a way to apply an event handler to all elements in the window? As in, make the window draggable no matter which element the user click+drags. Obviously without adding the handler manually, to every single element. Just do it once somewhere?

10

10 Answers

312
votes

Sure, apply the following MouseDown event of your Window

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

This will allow users to drag the Window when they click/drag on any control, EXCEPT for controls which eat the MouseDown event (e.Handled = true)

You can use PreviewMouseDown instead of MouseDown, but the drag event eats the Click event, so your window stops responding to left-mouse click events. If you REALLY wanted to be able to click and drag the form from any control, you could probably use PreviewMouseDown, start a timer to begin the drag operation, and cancel the operation if the MouseUp event fires within X milliseconds.

9
votes

if the wpf form needs to be draggable no matter where it was clicked the easy work around is using a delegate to trigger the DragMove() method on either the windows onload event or the grid load event

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

Sometimes, we do not have access to Window, e.g. if we are using DevExpress, all that is available is a UIElement.

Step 1: Add attached property

The solution is to:

  1. Hook into MouseMove events;
  2. Search up the visual tree until we find the first parent Window;
  3. Call .DragMove() on our newly discovered 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);
        }
    }
}

Step 2: Add Attached Property to any element to let it drag the window

The user can drag the entire window by clicking on a specific element, if we add this attached property:

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

Appendix A: Optional Advanced Example

In this example from DevExpress, we replace the title bar of a docking window with our own grey rectangle, then ensure that if the user clicks and drags said grey rectagle, the window will drag normally:

<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: I am not affiliated with DevExpress. This technique will work with any user element, including standard WPF or Telerik (another fine WPF library provider).

4
votes

As already mentioned by @fjch1997 it's convenient to implement a behavior. Here it is, the core logic is the same as in the @loi.efy's answer:

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>
3
votes
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
    this.DragMove();
}

Is throwing an exception in some cases (i.e. if on the window you also have a clickable image that when clicked opens a message box. When you exit from message box you will get error) It is safer to use

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

So you are sure that left button is pressed at that moment.

3
votes

This is all needed!

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

It is possible to drag & drop a form by clicking anywhere on the form, not just the title bar. This is handy if you have a borderless form.

This article on CodeProject demonstrates one possible solution to implement this:

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

Basically a descendant of the Form type is created in which the mouse down, up and move events are handled.

  • Mouse down: remember position
  • Mouse move: store new location
  • Mouse up: position form to new location

And here's a similar solution explained in a video tutorial:

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

I would not allow dragging the form when a user clicks upon a control in said form. Users epexct different results when they click on different controls. When my form suddenly starts moving because I clicked a listbox, button, label...etc. that would be confusing.

0
votes

The most usefull method, both for WPF and windows form, WPF example:

    [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
votes
<Window
...
WindowStyle="None" MouseLeftButtonDown="WindowMouseLeftButtonDown"/>
<x:Code>
    <![CDATA[            
        private void WindowMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DragMove();
        }
    ]]>
</x:Code>

source

0
votes

Add this to your Window style (I think properties are self-explanatory)

<Setter Property="WindowChrome.WindowChrome">
  <Setter.Value>
    <WindowChrome GlassFrameThickness="0" ResizeBorderThickness="3" CornerRadius="0" CaptionHeight="40" />
  </Setter.Value>
</Setter>