无法让 VisualStateGroup.Transitions 在模板化控件中工作
Can't get VisualStateGroup.Transitions to work in templated control
我正在创建一个模板化控件。这个想法是创建一个扩展控件。它将有一个 Header 和一个详细信息 属性,每个对应一个 ContentPresenter
。用户将能够点击 Header,详细信息部分将展开并显示动画。当用户再次点击 header 时,详细信息部分将缩回并显示另一个动画。
我正在使用 Visual States 和 VisualTransitions
来实现这一点。这是我的代码。
[TemplatePart(Name ="Header", Type=typeof(ContentPresenter))]
[TemplatePart(Name = "Details", Type = typeof(ContentPresenter))]
[TemplateVisualState(GroupName ="ExpandStates",Name ="Expanded")]
[TemplateVisualState(GroupName = "ExpandStates", Name = "Compact")]
public sealed class ExpandingItem : Control
{
private ContentPresenter header;
private bool isExpanded;
public ExpandingItem()
{
this.DefaultStyleKey = typeof(ExpandingItem);
}
public FrameworkElement Header
{
get { return (FrameworkElement)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
// Using a DependencyProperty as the backing store for Header. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(FrameworkElement), typeof(ExpandingItem), new PropertyMetadata(default(FrameworkElement)));
public FrameworkElement Details
{
get { return (FrameworkElement)GetValue(DetailsProperty); }
set { SetValue(DetailsProperty, value); }
}
// Using a DependencyProperty as the backing store for Details. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DetailsProperty =
DependencyProperty.Register("Details", typeof(FrameworkElement), typeof(ExpandingItem), new PropertyMetadata(default(FrameworkElement)));
protected override void OnApplyTemplate()
{
if (header != null)
{
header.Tapped -= HeaderTapped;
}
base.OnApplyTemplate();
header = (ContentPresenter)GetTemplateChild("Header");
header.Tapped += HeaderTapped;
}
private void HeaderTapped(object sender, TappedRoutedEventArgs e)
{
if (isExpanded)
{
Retract();
OnStateChanged(new ExpandItemEventArgs(false));
}
else
{
Expand();
OnStateChanged(new ExpandItemEventArgs(true));
}
isExpanded = !isExpanded;
}
public void Expand()
{
VisualStateManager.GoToState(this, "Expanded", true);
}
public void Retract()
{
VisualStateManager.GoToState(this, "Compact", true);
}
public EventHandler<ExpandItemEventArgs> StateChanged;
private void OnStateChanged(ExpandItemEventArgs e)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<ExpandItemEventArgs> handler = StateChanged;
// Event will be null if there are no subscribers
if (handler != null)
{
handler(this, e);
}
}
}
和模板
<Style TargetType="controls:ExpandingItem" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ExpandingItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ExpandStates">
<VisualStateGroup.Transitions>
<VisualTransition From="Compact" To="Expanded">
<VisualTransition.Storyboard>
<Storyboard>
<FadeInThemeAnimation
TargetName="Details"/>
</Storyboard>
</VisualTransition.Storyboard>
</VisualTransition>
<VisualTransition From="Expanded" To="Compact">
<VisualTransition.Storyboard>
<Storyboard>
<FadeOutThemeAnimation
TargetName="Details"/>
</Storyboard>
</VisualTransition.Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Compact"/>
<VisualState x:Name="Expanded">
<VisualState.Setters>
<Setter Target="Details.Visibility" Value="Visible"/>
</VisualState.Setters>
<Storyboard>
<FadeInThemeAnimation
TargetName="Details"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
x:Name="Header"
Grid.Row="0"
Content="{TemplateBinding Header}"/>
<ContentPresenter
x:Name="Details"
Grid.Row="1"
Content="{TemplateBinding Details}"
Visibility="Collapsed"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我正在使用 fadein/fadeout 动画,因为它是一个简单的动画,但理想情况下我想使用类似 splitopen/splitclose 动画的东西。
问题是,即使状态之间的转换正常发生。动画永远不会发生。你能帮我找出问题所在吗?
编辑:这是 ExpandItemEventArgs
的代码
public class ExpandItemEventArgs : EventArgs
{
private readonly bool isExpanded;
public ExpandItemEventArgs(bool isExpanded)
{
this.isExpanded = isExpanded;
}
public bool IsExpanded => isExpanded;
}
The problem is that even though the transition between the states happens normally. The animations never occur. Can you help me identify the problem?
这是因为你的VisualTransition From="Compact" To="Expanded"
,一开始你没有让你的控件输入名为Compact
的VisualState
。当你 <VisualTransition From="Expanded" To="Compact">
,因为你没有为你的 Details
设置 Visibility
属性,它将擦除最后一个 VisualState
并返回到 Collapsed 状态直接,动画不会显示。
这里我修改了你的代码,为了让淡入淡出的动画更明显,我也改了动画:
<Style TargetType="local:ExpandingItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ExpandingItem">
<Grid Name="RootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ExpandStates">
<VisualStateGroup.Transitions>
<VisualTransition To="Expanded" GeneratedDuration="0:0:3">
<Storyboard x:Name="FadeIn">
<DoubleAnimation From="0.1" To="1" Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="Details" Duration="0:0:3">
<DoubleAnimation.EasingFunction>
<CubicEase />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</VisualTransition>
<VisualTransition From="Expanded" To="Compact" GeneratedDuration="0:0:2.5">
<VisualTransition.Storyboard>
<Storyboard x:Name="FadeOut">
<DoubleAnimation From="1" To="0.1" Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="Details" Duration="0:0:3">
<DoubleAnimation.EasingFunction>
<CubicEase />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</VisualTransition.Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Target="Details.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Compact">
<VisualState.Setters>
<Setter Target="Details.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Expanded">
<VisualState.Setters>
<Setter Target="Details.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
x:Name="Header"
Grid.Row="0"
Content="{TemplateBinding Header}" />
<ContentPresenter
x:Name="Details"
Grid.Row="1"
Content="{TemplateBinding Details}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
后面我修改的代码:
protected override void OnApplyTemplate()
{
if (header != null)
{
header.Tapped -= HeaderTapped;
}
base.OnApplyTemplate();
VisualStateManager.GoToState(this, "Compact", true); //Go to compact state at first
header = (ContentPresenter)GetTemplateChild("Header");
header.Tapped += HeaderTapped;
}
public void Expand()
{
VisualStateManager.GoToState(this, "Normal", true); //Go to Normal state firstly when it expanded, in order to make it visible.
VisualStateManager.GoToState(this, "Expanded", true);
}
渲染图像:
我正在创建一个模板化控件。这个想法是创建一个扩展控件。它将有一个 Header 和一个详细信息 属性,每个对应一个 ContentPresenter
。用户将能够点击 Header,详细信息部分将展开并显示动画。当用户再次点击 header 时,详细信息部分将缩回并显示另一个动画。
我正在使用 Visual States 和 VisualTransitions
来实现这一点。这是我的代码。
[TemplatePart(Name ="Header", Type=typeof(ContentPresenter))]
[TemplatePart(Name = "Details", Type = typeof(ContentPresenter))]
[TemplateVisualState(GroupName ="ExpandStates",Name ="Expanded")]
[TemplateVisualState(GroupName = "ExpandStates", Name = "Compact")]
public sealed class ExpandingItem : Control
{
private ContentPresenter header;
private bool isExpanded;
public ExpandingItem()
{
this.DefaultStyleKey = typeof(ExpandingItem);
}
public FrameworkElement Header
{
get { return (FrameworkElement)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
// Using a DependencyProperty as the backing store for Header. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(FrameworkElement), typeof(ExpandingItem), new PropertyMetadata(default(FrameworkElement)));
public FrameworkElement Details
{
get { return (FrameworkElement)GetValue(DetailsProperty); }
set { SetValue(DetailsProperty, value); }
}
// Using a DependencyProperty as the backing store for Details. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DetailsProperty =
DependencyProperty.Register("Details", typeof(FrameworkElement), typeof(ExpandingItem), new PropertyMetadata(default(FrameworkElement)));
protected override void OnApplyTemplate()
{
if (header != null)
{
header.Tapped -= HeaderTapped;
}
base.OnApplyTemplate();
header = (ContentPresenter)GetTemplateChild("Header");
header.Tapped += HeaderTapped;
}
private void HeaderTapped(object sender, TappedRoutedEventArgs e)
{
if (isExpanded)
{
Retract();
OnStateChanged(new ExpandItemEventArgs(false));
}
else
{
Expand();
OnStateChanged(new ExpandItemEventArgs(true));
}
isExpanded = !isExpanded;
}
public void Expand()
{
VisualStateManager.GoToState(this, "Expanded", true);
}
public void Retract()
{
VisualStateManager.GoToState(this, "Compact", true);
}
public EventHandler<ExpandItemEventArgs> StateChanged;
private void OnStateChanged(ExpandItemEventArgs e)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<ExpandItemEventArgs> handler = StateChanged;
// Event will be null if there are no subscribers
if (handler != null)
{
handler(this, e);
}
}
}
和模板
<Style TargetType="controls:ExpandingItem" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ExpandingItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ExpandStates">
<VisualStateGroup.Transitions>
<VisualTransition From="Compact" To="Expanded">
<VisualTransition.Storyboard>
<Storyboard>
<FadeInThemeAnimation
TargetName="Details"/>
</Storyboard>
</VisualTransition.Storyboard>
</VisualTransition>
<VisualTransition From="Expanded" To="Compact">
<VisualTransition.Storyboard>
<Storyboard>
<FadeOutThemeAnimation
TargetName="Details"/>
</Storyboard>
</VisualTransition.Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Compact"/>
<VisualState x:Name="Expanded">
<VisualState.Setters>
<Setter Target="Details.Visibility" Value="Visible"/>
</VisualState.Setters>
<Storyboard>
<FadeInThemeAnimation
TargetName="Details"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
x:Name="Header"
Grid.Row="0"
Content="{TemplateBinding Header}"/>
<ContentPresenter
x:Name="Details"
Grid.Row="1"
Content="{TemplateBinding Details}"
Visibility="Collapsed"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我正在使用 fadein/fadeout 动画,因为它是一个简单的动画,但理想情况下我想使用类似 splitopen/splitclose 动画的东西。
问题是,即使状态之间的转换正常发生。动画永远不会发生。你能帮我找出问题所在吗?
编辑:这是 ExpandItemEventArgs
public class ExpandItemEventArgs : EventArgs
{
private readonly bool isExpanded;
public ExpandItemEventArgs(bool isExpanded)
{
this.isExpanded = isExpanded;
}
public bool IsExpanded => isExpanded;
}
The problem is that even though the transition between the states happens normally. The animations never occur. Can you help me identify the problem?
这是因为你的VisualTransition From="Compact" To="Expanded"
,一开始你没有让你的控件输入名为Compact
的VisualState
。当你 <VisualTransition From="Expanded" To="Compact">
,因为你没有为你的 Details
设置 Visibility
属性,它将擦除最后一个 VisualState
并返回到 Collapsed 状态直接,动画不会显示。
这里我修改了你的代码,为了让淡入淡出的动画更明显,我也改了动画:
<Style TargetType="local:ExpandingItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ExpandingItem">
<Grid Name="RootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ExpandStates">
<VisualStateGroup.Transitions>
<VisualTransition To="Expanded" GeneratedDuration="0:0:3">
<Storyboard x:Name="FadeIn">
<DoubleAnimation From="0.1" To="1" Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="Details" Duration="0:0:3">
<DoubleAnimation.EasingFunction>
<CubicEase />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</VisualTransition>
<VisualTransition From="Expanded" To="Compact" GeneratedDuration="0:0:2.5">
<VisualTransition.Storyboard>
<Storyboard x:Name="FadeOut">
<DoubleAnimation From="1" To="0.1" Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="Details" Duration="0:0:3">
<DoubleAnimation.EasingFunction>
<CubicEase />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</VisualTransition.Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Target="Details.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Compact">
<VisualState.Setters>
<Setter Target="Details.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Expanded">
<VisualState.Setters>
<Setter Target="Details.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter
x:Name="Header"
Grid.Row="0"
Content="{TemplateBinding Header}" />
<ContentPresenter
x:Name="Details"
Grid.Row="1"
Content="{TemplateBinding Details}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
后面我修改的代码:
protected override void OnApplyTemplate()
{
if (header != null)
{
header.Tapped -= HeaderTapped;
}
base.OnApplyTemplate();
VisualStateManager.GoToState(this, "Compact", true); //Go to compact state at first
header = (ContentPresenter)GetTemplateChild("Header");
header.Tapped += HeaderTapped;
}
public void Expand()
{
VisualStateManager.GoToState(this, "Normal", true); //Go to Normal state firstly when it expanded, in order to make it visible.
VisualStateManager.GoToState(this, "Expanded", true);
}
渲染图像: