WPF draggable and clickable button - c#

I have a borderless, transparent Window with only one Button.
My fancy button looks like this
The expected behaviour is:
When I click and drag the Button, the Button must follow the cursor.
When I only click on the Button, the MouseDown, PreviewMouseDown events or Command binding should raise.
At first I tried to call the DragMove() on PreviewMouseDown event, but that blocks the click events. Now my idea is: I set a 100ms delay after mouse down. If the time passed, than the button wil be dragging, otherwise it was just a click.
Code
private bool _dragging;
private Point startpos;
CancellationTokenSource cancellation;
private void Button_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_dragging && e.LeftButton == MouseButtonState.Pressed)
{
var currentpos = e.GetPosition(this);
Left += currentpos.X - startpos.X;
Top += currentpos.Y - startpos.Y;
}
}
private async void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left)
return;
_dragging = false;
startpos = e.GetPosition(this);
cancellation?.Cancel();
cancellation = new CancellationTokenSource();
await Task.Delay(100, cancellation.Token).ContinueWith(task =>
{
_dragging = !task.IsCanceled;
});
}
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (_dragging)
{
_dragging = false;
e.Handled = true;
}
cancellation?.Cancel();
}
Basically it works, but has some bugs:
When I hold down the mouse button for longer time and release, then the click won't work, because after 100ms the dragging will be active.
After I dragged the button, and click anywhere outside the Button and Window control, the PreviewMouseDown and PreviewMouseUp events are raised. I don't know why??
Does somebody have any better solution?

For your first problem:
You must handle the case when your button is pushed down but not moved. I think a better way to do this (instead of the 100ms delay) would be to specify a minimum threshold of movement above which the dragging will start.
You could do it like this:
private const double _dragThreshold = 1.0;
private bool _dragging;
private Point startpos;
CancellationTokenSource cancellation;
private void Button_PreviewMouseMove(object sender, MouseEventArgs e)
{
var currentpos = e.GetPosition(this);
var delta = currentpos - startpos;
if ((delta.Length > _dragThreshold || _dragging) && e.LeftButton == MouseButtonState.Pressed)
{
_dragging = true;
Left += currentpos.X - startpos.X;
Top += currentpos.Y - startpos.Y;
}
}
private async void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left)
return;
_dragging = false;
startpos = e.GetPosition(this);
cancellation?.Cancel();
cancellation = new CancellationTokenSource();
}
For your second problem: the button will capture the mouse on the mouse down event.
You need to release the captured mouse with the ReleaseMouseCapture method when you are done with your drag.
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (_dragging)
{
_dragging = false;
e.Handled = true;
var button = sender as Button;
button.ReleaseMouseCapture();
}
cancellation?.Cancel();
}

Related

Button.MouseMove event in WPF always has e.LeftMouse with status Released

I am trying to start a drag and drop on a Button control in WPF. I am using Button because I also want to handle click event.
Adding a Button in XAML and handling the MouseMove event always has e.LeftMouse equal to MouseButtonState.Released.
<Button MouseMove="Button_MouseMove"/>
In the following handler implementation the exception is never thrown.
private void Button_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
throw new Exception("It works!");
}
I noticed the same is true for any control I place inside a Button and try to process MouseMove event.
How do I handle drag and drop from a Button control or any other control inside a button in WPF?
EDIT - Solution based on mm8's answer
The updated XAML is:
<Button PreviewMouseDown="Button_PreviewMouseDown"
PreviewMouseUp="Button_PreviewMouseUp"
PreviewMouseMove="Button_PreviewMouseMove">
</Button>
The updated handler code:
Point startPosition;
double delta = 10;
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
startPosition = e.GetPosition(this);
}
private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
Point currentPosition = e.GetPosition(this);
if ((currentPosition - startPosition).Length < delta)
throw new Exception("MouseClick");
}
private void Button_PreviewMouseMove(object sender, MouseEventArgs e)
{
Point currentPosition = e.GetPosition(this);
double currentDelta = (currentPosition - startPosition).Length;
if (e.LeftButton == MouseButtonState.Pressed && currentDelta >= delta)
throw new Exception("DragAndDrop");
}
You could perhaps handle the PreviewMouseDown event instead of Click:
private void Button_Click(object sender, MouseButtonEventArgs e)
{
//handle click here...
e.Handled = true;
}
XAML:
<Button PreviewMouseDown="Button_Click" MouseMove="Button_MouseMove"/>

WPF dragdrop control cursor escapes the control

I've created my own dragable control. The dragging is very simple:
bool moving = false; Point click = new Point(0, 0);
private void _MouseDown(object sender, MouseButtonEventArgs e)
{
moving = true;
click = Mouse.GetPosition(this);
}
private void _MouseUp(object sender, MouseButtonEventArgs e) { moving = false; }
private void _MouseMove(object sender, MouseEventArgs e)
{
if (moving == true)
{
Point po = Mouse.GetPosition(this);
this.Margin = new Thickness(this.Margin.Left + (po.X - click.X), this.Margin.Top + (po.Y - click.Y), 0, 0);
}
}
My problem is that if I drag too fast the cursor "escapes" my control. It's obvious why, however it's not too obvious how to fix this since I can't easily subscribe to every other control's mousemove in the window, and my control is small (about 35,15 px) so this happends a lot. I think that if I can easily force the mouse cursor to stay in the controll that would be a solution(not ideal, though).
So what is the bast way to fix this? How professinoal controls handle this?
P.S. I'm learning WPF, so I'm probably doing some things wrong
Your cursor leaves the usercontrol on fast moves and the MouseMove event will not be triggered anymore.
As said in the comments from the author in Drag Drop UserControls using the MouseMove event of a surrounding Canvas should help.
I've figured it out, it's very simple, using a timer.
bool moving = false; Point click = new Point(0, 0);
System.Timers.Timer _MOVER = new System.Timers.Timer();
public PersonControl()
{
InitializeComponent();
_MOVER.Elapsed += new System.Timers.ElapsedEventHandler((o, v) => { Dispatcher.Invoke(Move); });
_MOVER.Enabled = true;
_MOVER.Interval = 10;
}
private void _MouseDown(object sender, MouseButtonEventArgs e)
{
moving = true;
click = Mouse.GetPosition(this);
Canvas.SetZIndex(this, 100);
_MOVER.Start();
}
private void _MouseUp(object sender, MouseButtonEventArgs e)
{
moving = false;
Canvas.SetZIndex(this, 0);
_MOVER.Stop();
}
private void Move()
{
if (moving == true)
{
Point po = Mouse.GetPosition(this);
this.Margin = new Thickness(this.Margin.Left + (po.X - click.X), this.Margin.Top + (po.Y - click.Y), 0, 0);
}
}

How we move a label when mouse click on it and when mouse key up then stop moving inside a group box

When is try with code, there appear two label and when move, screen become white from where they move. I want single label move with mouse move.
bool mDown = false;
private void label13_MouseMove(object sender, MouseEventArgs e)
{
if (mDown)
{
label13.Location = e.Location;
}
}
private void label13_MouseDown(object sender, MouseEventArgs e)
{
mDown = true;
}
private void label13_MouseUp(object sender, MouseEventArgs e)
{
mDown = false;
}
The e.Location gives you a mouse position relative to the control that is being clicked. So to fix that, instead of
label13.Location = e.Location;
use
var pos = this.PointToClient(Cursor.Position);
label13.Location = new Point(pos.X - offset.X, pos.Y - offset.Y);`
Create the offset variable as a property of the form (type Point) and initialize it on the mouse down event:
offset = e.Location;

Activate MouseEvent on Edit Button click C#

I have Form with textboxes and they have MouseEvent(MouseMove, MouseDown) and they are enabled on form load, but my question is how to call them just when i Click the Edit Button, so just then the textboxes can move?
My code:
private void textBox_MouseMove(object sender, MouseEventArgs e)
{
TextBox txt = sender as TextBox;
foreach (TextBox text in textBoxs)
{
if (e.Button == MouseButtons.Left)
{
if (txt.Name == text.Name)
{
txt.Left = e.X + txt.Left - MouseDownLocation.X;
txt.Top = e.Y + txt.Top - MouseDownLocation.Y;
}
}
}
}
private void textBox_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
MouseDownLocation = e.Location;
}
}
private void btnEdit_Click(object sender, EventArgs e)
{
btnEdit.Visible = false;
btnPrint.Visible = false;
btnSave.Visible = true;
//Want to call mouse function here.
}
any suggestions?
Instead of hooking the events from the Visual Studio designer, you should manually hook the events in the btnEdit_Click handler method by adding this line:
textboxname.MouseMove += new MouseEventHandler(textBox_MouseMove);
Then unhook the event when your save button is clicked (I'm assuming you have some method btnSave_Click) by doing the following:
textboxname.MouseMove -= new MouseEventHandler(textBox_MouseMove);
The same goes for your MouseDown event.
If I understand your post, you want the textbox functionality to become "active" after you click the btnEdit button?
You could set a flag in btnEdit_Click, and only process the functionality in the other functions if that flag is true
Or, maybe add the event in the btnEdit_Click function, e.g.
private void btnEdit_Click(object sender, EventArgs e)
{
btnEdit.Visible = false;
btnPrint.Visible = false;
btnSave.Visible = true;
//Want to call mouse function here.
textBox.MouseDown += new MouseEventHandler(textBox_MouseDown);
}
But remove that extra line from where it currently exists in your code.

Checking if panel has moved during MouseDown

I have a panel, that can be moved left or right (chosen automatically according to its current position, distance is static) by click. Also, the user can drag the panel vertically however he wants by clicking the panel, holding the button and moving his mouse. The problem is, that the panel does the left/right move also, when it is dropped after being moved vertically, so the user has to click it again afterwards, to get to correct side (left/right). Here are the methods I am using:
Adding eventhandlers to panel(called Strip here)
Strip.MouseDown += new MouseEventHandler(button_MouseDown);
Strip.MouseMove += new MouseEventHandler(button_MouseMove);
Strip.MouseUp += new MouseEventHandler(button_MouseUp);
Strip.Click += new EventHandler(strip_Click);
And here all the methods mentioned above:
void button_MouseDown(object sender, MouseEventArgs e)
{
activeControl = sender as Control;
previousLocation = e.Location;
Cursor = Cursors.Hand;
}
void button_MouseMove(object sender, MouseEventArgs e)
{
if (activeControl == null || activeControl != sender)
return;
var location = activeControl.Location;
location.Offset(0, e.Location.Y - previousLocation.Y);
activeControl.Location = location;
}
void button_MouseUp(object sender, MouseEventArgs e)
{
activeControl = null;
Cursor = Cursors.Default;
}
void strip_Click(object sender, EventArgs e) // The one moving strip to left or right
{
activeControl = sender as Control;
if (activeControl.Left != 30)
activeControl.Left = 30;
else
activeControl.Left = 5;
}
How to make the panel not move left or right when it was moved vertically?
You'll need to distinguish between a click and a drag. So add a private field named "dragged".
private bool dragged;
In the MouseDown event handler add:
dragged = false;
In the MouseMove event handler add:
if (Math.Abs(location.Y - previousLocation.Y) >
SystemInformation.DoubleClickSize.Height) dragged = true;
In the Click event handler add:
if (dragged) return;

Categories