【发布时间】:2015-03-26 21:29:54
【问题描述】:
问题前奏:
如何为
RotateTransform的Angle属性设置动画 一个UIElement A当自定义DependencyProperty的值 当我点击一个 boolean 类型的UIElement B,都在UserControl内? 仅在 XAML 中(或大部分)?如果可能的话:)
我编写了以下所有内容,以提供所有我的问题所需的详细信息。您可以随时停止从上到下阅读;甚至直接跳转到帖子第一季度内的实际问题。
上下文:
问题是关于动画触发器和自定义属性绑定,它们都在一个UserControl中。到目前为止没有涉及任何窗口。
首先,假设我创建了一个 UserControl,它的主 Grid 包含另外两个 Grids >。最简单的模式:
<!-- MyControl.xaml -->
<UserControl ...blahblahblah>
<Grid>
<Grid x:Name="TiltingGrid">
<!-- This Grid contains UIElements that I want to tilt alltogether -->
</Grid>
<Grid>
<Ellipse x:Name="TiltingTrigger" ...blahblahblah>
<!-- This Ellipse is my "click-able" area -->
</Ellipse>
</Grid>
</Grid>
</UserControl>
然后,在 Code Behind 中,我有一个名为 IsTilting 的 DependencyProperty。
// MyControl.xaml.cs
public bool IsTilting
{
// Default value is : false
get { return (bool)this.GetValue(IsTiltingProperty); }
set { this.SetValue(IsTiltingProperty, value); }
}
private static readonly DependencyProperty IsTiltingProperty =
DependencyProperty.Register(
"IsTilting",
typeof(bool),
typeof(MyControl),
new FrameworkPropertyMetadata(
false,
new PropertyChangedCallback(OnIsTiltingPropertyChanged)));
private static void OnIsTiltingPropertyChanged(...) { ... }
// .. is a classic Callback which calls
// private void OnIsTiltingChanged((bool)e.NewValue)
// and/or
// protected virtual void OnIsTiltingChanged(e) ...
然后,我在 XAML 中为名为 TiltingGrid 的网格定义了一些属性:
<Grid x:Name="TiltingGrid"
RenderTransformOrigin="0.3, 0.5">
<Grid.RenderTransform>
<RotateTransform
x:Name="TiltRotate" Angle="0.0" />
<!-- Angle is the Property I want to animate... -->
</Grid.RenderTransform>
<!-- This Grid contains UIElements -->
<Path ... />
<Path ... />
<Ellipse ... />
</Grid>
我想在单击此 UserControl 内的特定区域时触发倾斜:第二个网格中的椭圆:
<Grid>
<Ellipse x:Name="TiltingTrigger"
... Fill and Stroke goes here ...
MouseLeftButtonDown="TryTilt_MouseLeftButtonDown"
MouseLeftButtonUp="TryTilt_MouseLeftButtonUp">
</Ellipse>
</Grid>
如果我没记错的话,Ellipse 没有点击事件,所以我必须为MouseLeftButtonDown 和MouseLeftButtonUp 创建两个事件处理程序。我必须这样做才能做到:
- 让 Ellipse 在 MouseLeftButtonDown 时捕获鼠标,并将私有字段设置为
true - 在MouseLeftButtonUp时测试鼠标点是否在椭圆内,将私有字段的值设置为
false,然后释放鼠标。 - 如果发生看起来像“点击”的事情,则反转 DependencyProperty
IsTilting的值(真/假)(如果我能够解决适当的绑定,这将触发倾斜动画..)李>
我会为您保存 MouseLeftDown/Up 代码,但如果需要,我可以提供。他们所做的就是改变DP的价值。
问题:
当我的 DependencyProperty 更新时,我不知道如何触发角度动画。好吧。这不是一个实际问题,我认为这是缺乏知识:
- 我不知道如何捕获要与
<EventTrigger>一起使用的自定义事件 - 我不知道如何以及在何处使用 True/False DependencyProperty 触发 StoryBoard。
而实际的问题是:
从现在开始,我如何声明生成
Angle的代码RotateTransform的属性从0.0动画到 当我的 DPIsTilting设置为true时,45.0(我的网格的渲染变换 "TiltingGrid"),并且动画回到0.0什么时候是False?主要以 XAML 方式..?
我确实在 C# 代码后面有一个工作代码(详情如下)我正在寻找的是 XAML 中的可行解决方案(因为当您在 CodeBehind 中重写几乎所有内容时通常非常容易)知道如何在 XAML 中执行此操作)
到目前为止我尝试了什么......
从现在开始,除非您绝对想知道所有细节,否则您无需再阅读更多内容...
1) 使用本机定义的 Ellipse EventTriggers 触发动画仅适用于为此特定 UIElement 定义的事件(Enter/Leave/MouseLeftDown...)用许多 UIElements 完成了很多。
但这些触发器不是我需要的:我的网格应该基于 DP 中的 On/Off 或 True/False 自定义状态倾斜,而不是在发生鼠标活动时。
<Ellipse.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="TiltRotate"
Storyboard.TargetProperty="Angle"
From="0.0" To="45.0"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="UIElement.MouseLeave">
...
</Ellipse.Triggers>
当鼠标进入椭圆时,我的 Grid 会相应地倾斜,但是,如何访问我的 UserControl 中定义的自定义事件?
2) 然后,基于上述方案,我想我只需要在我的 MyControl 类上创建一个 Routed Event,实际上是两个:
TiltingActivatedTiltingDisabled
.
public static readonly RoutedEvent TiltingActivatedEvent =
EventManager.RegisterRoutedEvent(
"TiltingActivated",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(EventHandler));
public event RoutedEventHandler TiltingActivated
{
add { AddHandler(MyControl.TiltingActivatedEvent, value); }
remove { RemoveHandler(MyControl.TiltingActivatedEvent, value); }
}
private void RaiseTiltingActivatedEvent()
{
RoutedEventArgs newEventArgs =
new RoutedEventArgs(MyControl.TiltingActivatedEvent, this);
RaiseEvent(newEventArgs);
}
然后,当 IsTilting DependencyProperty Callback 的新值为 true 和 RaiseTiltingDisabledEvent() 当其新值为 时,我在一个方法中调用 RaiseTiltingActivatedEvent() >false.
注意:IsTilting 值在椭圆“单击”时更改为 true 或 false,并相应地触发两个事件。但是有一个问题:触发事件的不是 Ellipse,而是 UserControl 本身。
无论如何,我尝试将<EventTrigger RoutedEvent="UIElement.MouseEnter"> 替换为以下内容:
尝试一:
<EventTrigger RoutedEvent="
{Binding ic:MyControl.TiltingActivated,
ElementName=ThisUserControl}">
.. 我明白了:
"System.Windows.Markup.XamlParseException: (...)"
"A 'Binding' can only be set on a DependencyProperty of a DependencyObject."
我假设我无法绑定到事件?
尝试二:
<EventTrigger RoutedEvent="ic:MyControl.TiltingActivated">
.. 我明白了:
"System.NotSupportedException:"
"cannot convert RoutedEventConverter from system.string"
我假设无法解析 RoutedEvent 名称?无论如何,这种方法让我偏离了我最初的目标:当自定义属性发生变化时触发 DoubleAnimation(因为在更复杂的场景中,触发不同的动画和调用特定的方法不是更容易,所有这些都在 CodeBehind 当我们可以有几十个不同的值,而不是创建冗长而棘手的 XAML 东西?最好的当然是学习如何做这两个。我很想知道)
3) 然后我遇到了this article:初学者的WPF动画教程。
代码背后动画创作。这就是我想在知道如何在 XAML 中做到这一点之后学习的东西。无论如何,让我们试一试。
a) 创建两个动画属性(私有),一个用于倾斜动画,另一个用于向后倾斜动画。
private DoubleAnimation p_TiltingPlay = null;
private DoubleAnimation TiltingPlay
{
get {
if (p_TiltingPlay == null) {
p_TiltingPlay =
new DoubleAnimation(
0.0, 45.0, new Duration(TimeSpan.FromSeconds(0.2)));
}
return p_TiltingPlay;
}
}
// Similar thing for TiltingReverse Property...
b) 订阅这两个事件,然后在运行时在后面的代码中设置我们的 RotateTransform 的角度动画:
private void MyControl_TiltingActivated(object source, EventArgs e)
{
TiltRotate.BeginAnimation(
RotateTransform.AngleProperty, TiltingPlay);
}
// Same thing for MyControl_TiltingDisabled(...)
// Subscribe to the two events in constructor...
public MyControl()
{
InitializeComponent();
this.TiltingActivated +=
new RoutedEventHandler(MyControl_TiltingActivated);
this.TiltingDisabled +=
new RoutedEventHandler(MyControl_TiltingDisabled);
}
基本上,当我在椭圆上“单击”(MouseButtonLeftDown + Up)时:
- 鼠标点击点已解决
- 如果在椭圆区域内,请将 DP IsTilting 更改为 not IsTilting。
- IsTilting 然后触发 TiltingActivated 或 TiltingDisabled。
- 两者都被捕获,然后在网格的命名
<RotateTransform ..>上激活相关的倾斜动画(私有属性)。
而且有效!!!
我说后面的代码很容易! (冗长的代码 .. 是的,但它有效)希望使用 sn-ps 模板,它不会那么无聊。
但我仍然不知道如何在 XAML 中执行此操作。 :/
4) 由于我的自定义事件似乎超出了 XAML 端的范围,那么 <Style> 呢? 通常,在 Style 中绑定就像呼吸一样。但老实说,我不知道从哪里开始。
- 动画目标是应用于
Grid的<RotateTransform />的Angle属性。 - 绑定的部门。属性 IsTilting 是
MyControl的自定义 DP,而不是UserControl。 - 和一个
Ellipse驱动DP的更新。
让我们试试<RotateTransform.Style>
<RotateTransform ...>
<RotateTransform.st...>
</RotateTransform>
<!-- such thing does not exists -->
或RotateTransform.Triggers ? ...也不存在。
更新: 这种方法通过将网格中的样式声明为动画,如Clemens's answer 中所述。解决自定义 UserControl 属性绑定,我只需要使用
RelativeSource={RelativeSource AncestorType=UserControl}}。并 “瞄准” RotateTransform 的角度属性,我只需要使用RenderTransform.Angle.
还有什么?
我经常看到将
DataContext设置为“self”之类的示例。我真的不明白什么是 DataContext,但我假设它使所有路径解析默认指向声明的类,用于绑定。我已经在一个解决了我的问题的 UserControl 中使用了它,但我没有深入了解如何以及为什么。也许这可以帮助解决直接从 XAML 端捕获代码中的自定义事件?-
一种 XAML 主要方式我几乎可以肯定会起作用的是:
- 为该椭圆创建一个自定义 UserControl,例如
EllipseButton,并具有自己的事件和属性 - 然后,将其嵌入
MyControlUserControl。 - 捕获 EllipseButton 的 TiltingActivated 事件以在 EllipseButton 的 Storyboard 中触发 DoubleAnimation,就像对 Button 的 Click 事件一样。
这很好用,但我发现 hacky 创建 并嵌入 另一个控件 只是为了能够访问适当的自定义事件. MyControl 不是一个需要这种手术的 SuperWonderfulMegaTop 项目。我确定我错过了一些非常明显的东西;简直不敢相信在 WPF 世界之外这么简单的东西在 WPF 中就更简单了。
无论如何,这样的交叉连接很容易发生内存泄漏(这里可能不是这种情况,但我尽量避免这种情况......)
- 为该椭圆创建一个自定义 UserControl,例如
也许定义
<Grid.Style>或类似的方法可以解决问题……但我不知道怎么做。我只知道怎么用<Setter>。我不知道如何在 Style 声明中创建 EventTriggers。 更新:由Clemens's answer解释。此SO question(基于 DependencyProperty 的 UserControl 中的触发触发器)建议在
UserControl.Resources中创建样式。尝试了以下...它不起作用(反正那里没有动画 - 我还不知道如何在 Style 中声明动画)
.
<Style TargetType="RotateTransform">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsTilting, ElementName=ThisUserControl}" Value="True">
<Setter Property="Angle" Value="45.0" />
</DataTrigger>
</Style.Triggers>
</Style>
这个SO question(DataTemplate 中的 RotateTransform Angle 绑定不生效)对我来说有很多未知的知识可以理解。但是,假设建议的解决方法有效,我看不到任何看起来像动画的东西。只是绑定到没有动画的值。我不认为角度会神奇地为自己设置动画。
在像上面的工作代码一样的代码后面,我可以创建另一个名为
GridAngle(double) 的 DependencyProperty,然后将 RotateTransform 的 Angle 属性绑定到该新 DP,然后直接为该 DP 设置动画???值得一试,但稍后:我累了。刚刚发现我的注册事件属于冒泡策略。如果事件要被某些父容器捕获,这将很重要,但我想直接在 UserControl 内处理所有内容,而不是像 SO question 那样。但是,我还不了解的隧道策略可能会起作用:隧道是否允许我的 Ellipse 捕获我的 UserControl 的事件?必须一遍又一遍地阅读文档,因为它对我来说仍然很模糊......现在让我感到烦恼的是我仍然无法在这个 UserControl 中使用我的自定义事件:/
a CommandBinding 呢?这看起来很有趣,但这是一个完全不同的章节来学习。它似乎涉及很多代码,因为我已经有一个工作代码(对我来说看起来更具可读性......)
-
在这个SO question(WPF 数据触发器和故事板)中,接受的答案似乎只有在我为可以具有
UIElement.Style定义的 UI 元素的属性设置动画时才有效。RotateTransform没有这种能力。另一个答案建议使用
ContentControl、ControlTemplate... 就像上面的 CommandBinding 一样,我还没有深入了解如何使其适应我的 UserControl。但是,这些答案似乎最符合我的需求,尤其是 ContentControl 方式。稍后我会进行一些尝试,看看它是否解决了实现所需行为的 XAML 主要方式。 :)
最后,这个SO question(EventTrigger 绑定到来自 DataContext 的事件)建议使用
Blend/Interactivity。这种方法看起来不错,但我没有 Blend SDK,也不是真的愿意,除非我绝对必须...再次:又一整章吃...:/
旁注:
如您所料,我是 WPF/XAML 的初学者(我知道这不是借口),我几周前就开始学习了。我有点“现在在 WinForms 中很容易完成所有事情......” 但也许你可以帮我弄清楚在 WPF 中实现它是多么容易:)
我搜索了很多(我知道这也不是借口),但这次我没有运气。 - 好的,我刚刚阅读了三打文章,代码项目和 SO 主题,以及关于触发器、动画、路由事件的 MSDN 文档.. 似乎只是打磨表面而没有深入挖掘核心(似乎 MS 认为继承from Button 是解决几乎所有问题的方法...)
【问题讨论】:
标签: c# wpf xaml eventtrigger rotatetransform