我正在尝试学习一些WPF,我希望能够实现一个简单的游戏。在这个游戏中,Canvas
上有几个项目。出于这个问题的目的,让我们说只有一个,它是Ellipse
:
<Canvas Name="canvas">
<Ellipse Name="ellipse" Width="100" Height="100" Stroke="Black" StrokeThickness="3" Fill="GreenYellow"/>
</Canvas>
用户需要能够随意拖动这些项目。
所以我实现了以下代码,它似乎有效:
public MainWindow()
{
InitializeComponent();
Canvas.SetLeft(ellipse, 0);
Canvas.SetTop(ellipse, 0);
ellipse.MouseDown += new MouseButtonEventHandler(ellipse_MouseDown);
ellipse.MouseMove += new MouseEventHandler(ellipse_MouseMove);
ellipse.MouseUp += new MouseButtonEventHandler(ellipse_MouseUp);
}
void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
return;
ellipse.CaptureMouse();
ellipse.RenderTransform = new ScaleTransform(1.25, 1.25, ellipse.Width / 2, ellipse.Height / 2);
ellipse.Opacity = 0.75;
}
void ellipse_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
return;
var pos = e.GetPosition(canvas);
Canvas.SetLeft(ellipse, pos.X - ellipse.Width * 0.5);
Canvas.SetTop(ellipse, pos.Y - ellipse.Height * 0.5);
}
void ellipse_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!ellipse.IsMouseCaptured)
return;
ellipse.ReleaseMouseCapture();
ellipse.RenderTransform = null;
ellipse.Opacity = 1;
}
现在,如果你试试这个,你会发现这些动作非常不稳定。当您向下鼠标时,椭圆会立即增长并立即改变其透明度。我想让它变得平滑,以便没有突然跳跃。
我在DoubleAnimation
,Ellipse.OpacityProperty
和(特别是)ScaleTransform.ScaleXProperty
上使用Canvas.LeftProperty/TopProperty
尝试了显而易见的事情。但是,我遇到了以下问题:
一旦我在Canvas.LeftProperty/TopProperty
开始播放动画,我就再也不能使用Canvas.SetLeft/Top
了,因此拖动时椭圆不会移动。我找不到从对象中删除动画的方法。
如果用户在动画仍然发生时释放鼠标,ScaleTransform
上的“缩小”动画将从“增长”动画到达之前的全尺寸开始,这会突然导致跳。如果你疯狂地点击鼠标,对象的大小会疯狂跳跃,不应该跳过。
如果需要,您可以look at my failed code, which doesn’t work。
如何在WPF中正确实现这些平滑动作?
请不要先试用,否则不要发布答案。如果有任何突然跳跃,结果是不能令人满意的。谢谢!
答案 0 :(得分:3)
不是为每个更改创建一个新的ScaleTransform,而是使用相同的一个并继续应用新的动画。如果没有为动画指定From
属性,它将以当前值开始并执行平滑动画。
要避免位置跳过,请记住鼠标在椭圆内的位置,而不是始终居中。这样你就不用担心重新定位了。 (您可以使用空时间轴调用BeginAnimation来停止当前动画,但之后您将在第一个MouseMove上跳转。)
在XAML中:
<Ellipse Name="ellipse" Width="100" Height="100"
Stroke="Black" StrokeThickness="3" Fill="GreenYellow">
<Ellipse.RenderTransform>
<ScaleTransform x:Name="scale" CenterX="50" CenterY="50"/>
</Ellipse.RenderTransform>
</Ellipse>
在代码中:
private Point offsetInEllipse;
void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
return;
ellipse.CaptureMouse();
offsetInEllipse = e.GetPosition(ellipse);
var scaleAnimate = new DoubleAnimation(1.25,
new Duration(TimeSpan.FromSeconds(1)));
scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
}
void ellipse_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
return;
var pos = e.GetPosition(canvas);
Canvas.SetLeft(ellipse, pos.X - offsetInEllipse.X);
Canvas.SetTop(ellipse, pos.Y - offsetInEllipse.Y);
}
void ellipse_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!ellipse.IsMouseCaptured)
return;
ellipse.ReleaseMouseCapture();
var scaleAnimate = new DoubleAnimation(1,
new Duration(TimeSpan.FromSeconds(1)));
scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
}
如何返回
Canvas.SetLeft
有一个正常的操作 动画在Canvas.LeftProperty
?
一种方法是将FillBehavior设置为停止:
ellipse.BeginAnimation(Canvas.LeftProperty, new DoubleAnimation(
pos.X - ellipse.Width * 0.5,
new Duration(TimeSpan.FromSeconds(1)),
FillBehavior.Stop));
Canvas.SetLeft(ellipse, pos.X - ellipse.Width * 0.5);
这会导致属性在动画结束后返回其未设置动画的值。如果在开始动画后设置值,则未动画值将只是最终值。
另一种方法是在完成后清除动画:
ellipse.BeginAnimation(Canvas.LeftProperty, null);
但是,当你拖动时,其中任何一个都会导致它跳跃。您可以让拖动每次都开始一个新的动画,但这会使拖动感觉非常滞后。也许您想使用Canvas.Left处理拖动,但使用动画TranslateTransform处理平滑居中?
XAML:
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="scale" CenterX="50" CenterY="50"/>
<TranslateTransform x:Name="translate"/>
</TransformGroup>
</Ellipse.RenderTransform>
代码:
void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed)
return;
ellipse.CaptureMouse();
var scaleAnimate = new DoubleAnimation(1.25,
new Duration(TimeSpan.FromSeconds(1)));
scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
// We are going to move the center of the ellipse to the mouse
// location immediately, so start the animation with a shift to
// get it back to the current center and end the animation at 0.
var offsetInEllipse = e.GetPosition(ellipse);
translate.BeginAnimation(TranslateTransform.XProperty,
new DoubleAnimation(ellipse.Width / 2 - offsetInEllipse.X, 0,
new Duration(TimeSpan.FromSeconds(1))));
translate.BeginAnimation(TranslateTransform.YProperty,
new DoubleAnimation(ellipse.Height / 2 - offsetInEllipse.Y, 0,
new Duration(TimeSpan.FromSeconds(1))));
MoveEllipse(e);
}
void ellipse_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
return;
MoveEllipse(e);
}
private void MoveEllipse(MouseEventArgs e)
{
var pos = e.GetPosition(canvas);
Canvas.SetLeft(ellipse, pos.X - ellipse.Width / 2);
Canvas.SetTop(ellipse, pos.Y - ellipse.Height / 2);
}
答案 1 :(得分:2)
您可能应该查看Thumb控件。
这是使用它的nice CodeProject。
答案 2 :(得分:1)
你可以获得一些带效果的动画,它会软化初始拖动效果....我担心拖动动画可能不会被平滑掉,因为WPF不是为这种事情做的,我想你应该去对于XNA而言:p
此视频可能符合您的需求: http://windowsclient.net/learn/video.aspx?v=280279
答案 3 :(得分:1)
正如Quartermeister已经提到的那样,你不应该为动画指定from
值。这样,动画将以当前值开始,并将与当前正在执行的动画结合使用。此外,您不应每次都重新创建转换。
除此之外,我建议您使用TranslateTransform
而不是设置Top
的{{1}} / Left
属性。它为您提供了移动灵活性,而且您不依赖于Canvas面板。
所以,这就是我得到的:
<强> XAML:强>
Canvas
<强>代码隐藏:强>
<Canvas Name="canvas">
<Ellipse Name="ellipse" Width="100" Height="100" Stroke="Black" StrokeThickness="3" Fill="GreenYellow"
RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform />
<TranslateTransform />
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Canvas>