C# 移动鼠标中断 Dispatcher Timer

C# moving Mouse interrupst Dispatcher Timer

我目前正在编写一个程序,我想在其中使用 Dispatcher Timer 自动移动一个椭圆。只要我的鼠标静止不动,它就可以正常工作。但是,一旦我开始移动鼠标,椭圆就移动得很慢,甚至完全停止移动。当停止移动它时,程序再次正常运行。椭圆是 Canvas 的子元素,背景中除此 canvas 外没有其他 UI 元素。我也没有向 canvas 添加任何事件。 我的目标是将鼠标移到应用程序上,而不影响动画。 有人对解决这个问题有什么建议吗? 先感谢您! 阿祖罗

以下是最小可重现示例中的所有代码: Xaml.cs:

public partial class MainWindow : Window
{
    public DispatcherTimer timer = new DispatcherTimer();
    public MainWindow()
    {
        InitializeComponent();

        Canvas.SetLeft(Ball, 0);
        Canvas.SetTop(Ball, 225);

        timer.Interval = TimeSpan.FromMilliseconds(4.16666666);
        timer.Tick += Move;

        timer.Start();
    }

    private void Move(object sender, EventArgs e)
    {
        if ((MapCanvas.ActualWidth - Ball.Width) > Canvas.GetLeft(Ball))
        {
            Canvas.SetLeft(Ball, Canvas.GetLeft(Ball) + 2);
        }
    }
}

这里是 xaml:

<Window x:Class="BallSpielExample.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:BallSpielExample"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <Canvas x:Name="MapCanvas" Background="NavajoWhite" Width="auto" Height="auto">
        <Ellipse Name="Ball" Width="30" Height="30" Fill="SaddleBrown"/>
    </Canvas>
</Grid>

使用下面的动画

private void Move(object sender, EventArgs e)
{
    if ((MapCanvas.ActualWidth - Ball.Width) >= Canvas.GetLeft(Ball))
    {
        MoveAnim(Ball, .00001, Canvas.GetLeft(Ball), Canvas.GetLeft(Ball) + 2);
    }
}
public static void MoveAnim(UIElement element, double duration, double from, double to)
{
    string property = "(Canvas.Left)";
    Storyboard myStoryboard = new Storyboard();
    myStoryboard.FillBehavior = FillBehavior.HoldEnd;
    DoubleAnimation animation = new DoubleAnimation();

    animation.From = from;
    animation.To = to;
    animation.Duration = TimeSpan.FromSeconds(duration);
    animation.FillBehavior = FillBehavior.HoldEnd;
    Storyboard.SetTarget(animation, element);
    Storyboard.SetTargetProperty(animation, new PropertyPath(property));
    myStoryboard.Children.Add(animation);
    myStoryboard.Begin();
}
timer.Interval = TimeSpan.FromMilliseconds(4.16666666);

没有多大意义。 DispatcherTimer 既不应该 运行 200 Hz,也不应该 运行 精确到几分之一毫秒。 Ellipse 应由 DoubleAnimation 或 PointAnimation 移动。

以下示例中的 DoubleAnimation 每 5 毫秒将 Ellipse 的 Canvas.Left 属性 动画化 2 个单位。由于动画 运行 永远存在,您也可以为 ByDuration 属性使用其他值,但保持它们的比率,例如By = 20Duration = TimeSpan.FromMilliseconds(50).

public MainWindow()
{
    InitializeComponent();

    Canvas.SetLeft(Ball, 0);
    Canvas.SetTop(Ball, 225);

    var animation = new DoubleAnimation
    {
        By = 2,
        Duration = TimeSpan.FromMilliseconds(5),
        IsCumulative = true,
        RepeatBehavior = RepeatBehavior.Forever
    };

    Ball.BeginAnimation(Canvas.LeftProperty, animation);
}

第一个问题是,DispatcherTime 不够稳定,所以对动画没有用处。第二个问题是您应该使用(delta)时间来计算运动。如果您使用 WPF 动画,您将失去对其位置等的控制(边界检查等)

我会为此使用 CompositionTarget.Rendering 事件。这样您就可以在渲染线程上移动球。

使用秒表为运动计时,因为您希望它在不同设备上以相同速度运动。

我做了一个小例子来展示它是如何工作的:

MainWindow.xaml

<Window x:Class="BouncingBall.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:BouncingBall"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
    <Canvas x:Name="MapCanvas">
        <Ellipse Name="Ball" Width="30" Height="30" Fill="SaddleBrown"/>
    </Canvas>
</Window>

MainWindow.xaml.cs

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private double _ballPositionX = 100;
    private double _ballPositionY = 100;
    private double _ballSpeedX = 225; // pixels per second
    private double _ballSpeedY = 150; // pixels per second

    private TimeSpan _previousTime;

    private readonly Stopwatch _stopwatch = Stopwatch.StartNew();

    public MainWindow()
    {
        InitializeComponent();

        _previousTime = _stopwatch.Elapsed;

        // set the (initial) position
        UpdateBall(0);

        // Attach the Update method on the WPF renderTarget <----
        CompositionTarget.Rendering += Update;
    }

    private void Update(object sender, EventArgs e)
    {
        // check delta time
        var currentTime = _stopwatch.Elapsed;
        var delta = (currentTime - _previousTime).TotalSeconds;
        _previousTime = currentTime;

        UpdateBall(delta);
        // if you want to animate multiple shapes, Call something like UpdateBall
    }

    private void UpdateBall(double delta)
    {
        // ensure that the ball moves by using the deltatime.
        _ballPositionX += _ballSpeedX * delta;
        _ballPositionY += _ballSpeedY * delta;

        // check bounds X
        if (_ballPositionX < 0)
        {
            _ballSpeedX = -_ballSpeedX;
            _ballPositionX = 0;
        }
        else if (_ballPositionX > MapCanvas.ActualWidth - Ball.ActualWidth)
        {
            _ballSpeedX = -_ballSpeedX;
            _ballPositionX = MapCanvas.ActualWidth - Ball.ActualWidth;
        }

        // check bounds Y
        if (_ballPositionY < 0)
        {
            _ballSpeedY = -_ballSpeedY;
            _ballPositionY = 0;
        }
        else if (_ballPositionY > MapCanvas.ActualHeight - Ball.ActualHeight)
        {
            _ballSpeedY = -_ballSpeedY;
            _ballPositionY = MapCanvas.ActualHeight - Ball.ActualHeight;
        }

        // update the position
        Canvas.SetLeft(Ball, _ballPositionX);
        Canvas.SetTop(Ball, _ballPositionY);
    }
}