I have a borderless, transparent Window
with only one Button
.
My fancy button looks like this
The expected behaviour is:
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.
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:
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();
}