miechooy
miechooy

Reputation: 3422

WPF Move element with mouse

I am trying to move button with mouse in WPF app.

There is XAML Grid which is a root of structure:

<Grid Name="MyGrid"            >
    <Button Name="Samplebutton"
            PreviewMouseDown="Samplebutton_PreviewMouseDown"
            PreviewMouseUp="Samplebutton_PreviewMouseUp"
            PreviewMouseMove="Samplebutton_PreviewMouseMove"
            Content="Moving" Width="100" Height="35"/>
</Grid>

And code behind:

private bool _isMoving;


private void Samplebutton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    _isMoving = true;
}

private void Samplebutton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    _isMoving = false;
}

private void Samplebutton_PreviewMouseMove(object sender, MouseEventArgs e)
{
    if (!_isMoving) return;

    TranslateTransform transform = new TranslateTransform();
    transform.X = Mouse.GetPosition(MyGrid).X;
    transform.Y = Mouse.GetPosition(MyGrid).Y;
    this.Samplebutton.RenderTransform = transform;
}

First click into button moves him far away and then I can move button, but for the first time the button is moved away. What I am missing?

Upvotes: 5

Views: 15600

Answers (4)

Jeroen van Langen
Jeroen van Langen

Reputation: 22038

It's an old post, but (i think) this is a better way:

I think you should not use a boolean to check if the mouse is dragging the current control. Also, using the PreviewMouse.... is a useless fix to get the mouse positions (when the mouse is NOT above the control)

You should capture the mouse instead. When you capture the mouse, the mouse_move events are still raised even when the mouse is not above the control (even when off the current window). I think this is the way is should be done. It's much simpler and easier to read.

Here's is the code:

public partial class VisualBlock : UserControl
{
    private Point _positionInBlock;

    public VisualBlock()
    {
        InitializeComponent();
    }

    private void UserControl_MouseDown(object sender, MouseButtonEventArgs e)
    {
        // when the mouse is down, get the position within the current control. (so the control top/left doesn't move to the mouse position)
        _positionInBlock = Mouse.GetPosition(this);

        // capture the mouse (so the mouse move events are still triggered (even when the mouse is not above the control)
        this.CaptureMouse();
    }

    private void UserControl_MouseMove(object sender, MouseEventArgs e)
    {
        // if the mouse is captured. you are moving it. (there is your 'real' boolean)
        if (this.IsMouseCaptured)
        {
            // get the parent container
            var container = VisualTreeHelper.GetParent(this) as UIElement;

            if(container == null)
                return;
            
            // get the position within the container
            var mousePosition = e.GetPosition(container);

            // move the usercontrol.
            this.RenderTransform = new TranslateTransform(mousePosition.X - _positionInBlock.X, mousePosition.Y - _positionInBlock.Y);
        }
    }

    private void UserControl_MouseUp(object sender, MouseButtonEventArgs e)
    {
        // release this control.
        this.ReleaseMouseCapture();
    }
}

Update:

If you have a window, with, for example a canvas, you can add those usercontrols to the canvas.

Here is a gist of it: WPF Move controls


<Window x:Class="WPFTestControlMoveMouse.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFTestControlMoveMouse"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Button Grid.Row="0" Click="Button_Click">New</Button>
        <Canvas x:Name="MyCanvas" Grid.Row="1"></Canvas>
    </Grid>
</Window>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPFTestControlMoveMouse
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MyCanvas.Children.Add(new VisualBlock());
        }
    }
}

<UserControl x:Class="WPFTestControlMoveMouse.VisualBlock"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPFTestControlMoveMouse"
             mc:Ignorable="d" 
             Width="300" Height="200" MouseDown="UserControl_MouseDown" MouseUp="UserControl_MouseUp" MouseMove="UserControl_MouseMove">
    <Grid>
        <Border BorderBrush="Black" BorderThickness="1" Background="Silver" />
        <TextBlock FontSize="48" HorizontalAlignment="Center" VerticalAlignment="Center">Hi there!!</TextBlock>
    </Grid>
</UserControl>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPFTestControlMoveMouse
{
    /// <summary>
    /// Interaction logic for VisualBlock.xaml
    /// </summary>
    public partial class VisualBlock : UserControl
    {
        private Point _positionInBlock;

        public VisualBlock()
        {
            InitializeComponent();
        }

        private void UserControl_MouseDown(object sender, MouseButtonEventArgs e)
        {
            // when the mouse is down, get the position within the current control. (so the control top/left doesn't move to the mouse position)
            _positionInBlock = Mouse.GetPosition(this);

            // capture the mouse (so the mouse move events are still triggered (even when the mouse is not above the control)
            this.CaptureMouse();
        }

        private void UserControl_MouseMove(object sender, MouseEventArgs e)
        {
            // if the mouse is captured. you are moving it. (there is your 'real' boolean)
            if (this.IsMouseCaptured)
            {
                // get the parent container
                var container = VisualTreeHelper.GetParent(this) as UIElement;

                // get the position within the container
                var mousePosition = e.GetPosition(container);

                // move the usercontrol.
                this.RenderTransform = new TranslateTransform(mousePosition.X - _positionInBlock.X, mousePosition.Y - _positionInBlock.Y);
            }
        }

        private void UserControl_MouseUp(object sender, MouseButtonEventArgs e)
        {
            // release this control.
            this.ReleaseMouseCapture();
        }
    }
}

Moving a label with the mouse by deriving from the label control:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace WPFTestControlMoveMouse
{
    public class MyLabel : Label
    {
        private Point _positionInBlock;

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            // when the mouse is down, get the position within the current control. (so the control top/left doesn't move to the mouse position)
            _positionInBlock = Mouse.GetPosition(this);

            // capture the mouse (so the mouse move events are still triggered (even when the mouse is not above the control)
            this.CaptureMouse();
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            // if the mouse is captured. you are moving it. (there is your 'real' boolean)
            if (this.IsMouseCaptured)
            {
                // get the parent container
                var container = VisualTreeHelper.GetParent(this) as UIElement;

                // get the position within the container
                var mousePosition = e.GetPosition(container);

                // move the usercontrol.
                this.RenderTransform = new TranslateTransform(mousePosition.X - _positionInBlock.X, mousePosition.Y - _positionInBlock.Y);
            }
        }
        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            // release this control.
            this.ReleaseMouseCapture();
        }

    }
}

Upvotes: 9

dad_nvt
dad_nvt

Reputation: 11

Samplebutton_PreviewMouseUp i think has _isMoving = false is enough

Upvotes: 1

kmatyaszek
kmatyaszek

Reputation: 19296

Below is complete solution to your problem:

private bool _isMoving;
private Point? _buttonPosition;
private double deltaX;
private double deltaY;
private TranslateTransform _currentTT;

private void Samplebutton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    if(_buttonPosition == null)
        _buttonPosition = Samplebutton.TransformToAncestor(MyGrid).Transform(new Point(0, 0));
    var mousePosition = Mouse.GetPosition(MyGrid);
    deltaX = mousePosition.X - _buttonPosition.Value.X;
    deltaY = mousePosition.Y - _buttonPosition.Value.Y;
    _isMoving = true;
}

private void Samplebutton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    _currentTT = Samplebutton.RenderTransform as TranslateTransform;
    _isMoving = false;
}

private void Samplebutton_PreviewMouseMove(object sender, MouseEventArgs e)
{
    if (!_isMoving) return;

    var mousePoint = Mouse.GetPosition(MyGrid);

    var offsetX = (_currentTT == null ?_buttonPosition.Value.X : _buttonPosition.Value.X - _currentTT.X) + deltaX - mousePoint.X;
    var offsetY = (_currentTT == null ? _buttonPosition.Value.Y : _buttonPosition.Value.Y - _currentTT.Y) + deltaY - mousePoint.Y;

    this.Samplebutton.RenderTransform = new TranslateTransform(-offsetX, -offsetY);
}

Upvotes: 9

Michal Ciechan
Michal Ciechan

Reputation: 13888

You will need to save the position of the mouse in the first instance, and always use the "relative" X/Y positions from then on

private double _startX; // Or whatever number type Mouse.GetPosition(MyGrid).X returns
private double _startY; // Or whatever number type Mouse.GetPosition(MyGrid).Y returns   

private void Samplebutton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    _startX = Mouse.GetPosition(MyGrid).X;
    _startY = Mouse.GetPosition(MyGrid).Y;
    _isMoving = true;
}

private void Samplebutton_PreviewMouseMove(object sender, MouseEventArgs e)
{
    if (!_isMoving) return;

    TranslateTransform transform = new TranslateTransform();
    transform.X = Mouse.GetPosition(MyGrid).X - _startX;
    transform.Y = Mouse.GetPosition(MyGrid).Y - _startY;
    this.Samplebutton.RenderTransform = transform;
}

Upvotes: 2

Related Questions