Reputation: 3422
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
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
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
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