WPF 可拖动和可点击按钮

WPF draggable and clickable button

我有一个无边框透明Window只有一个Button

My fancy button looks like this

预期的行为是:

起初我尝试在 PreviewMouseDown 事件上调用 DragMove(),但这阻止了点击事件。现在我的想法是:我在鼠标按下后设置了 100 毫秒的延迟。如果时间过去了,按钮将被拖动,否则只是点击。

代码

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

基本上可以用,但有一些错误:

有人有更好的解决方案吗?

对于你的第一个问题:
当您按下按钮但未移动时,您必须处理这种情况。我认为更好的方法(而不是 100 毫秒延迟)是指定一个最小移动阈值,超过该阈值将开始拖动。
你可以这样做:

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

对于你的第二个问题:按钮将捕获鼠标按下事件。
完成拖动后,您需要使用 ReleaseMouseCapture 方法释放捕获的鼠标。

private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    if (_dragging)
    {
        _dragging = false;
        e.Handled = true;
        var button = sender as Button;
        button.ReleaseMouseCapture();
    }

    cancellation?.Cancel();
}