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 个单位。由于动画 运行 永远存在,您也可以为 By
和 Duration
属性使用其他值,但保持它们的比率,例如By = 20
和 Duration = 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);
}
}
我目前正在编写一个程序,我想在其中使用 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 个单位。由于动画 运行 永远存在,您也可以为 By
和 Duration
属性使用其他值,但保持它们的比率,例如By = 20
和 Duration = 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);
}
}