Reputation: 13
I created a control that works in WPF and now trying to port to UWP.
The control exposes a boolean property and when set to true, I change the background color of a Path (Stroke) via a story board with a RepeatBehavior of "forever".
After reading numerous articles I understand UWP uses VisualStates, Interactivity, etc... but no triggers. I have tried reworking the code but not getting the background to change/animate.
Part of ControlTemplate in WPF
<Path Fill="Transparent"
Stroke="{TemplateBinding Outline}"
StrokeThickness="{TemplateBinding Thickness}"
StrokeDashCap="Flat"
x:Name="OuterRing">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="OutlineFigurePart">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="OutlineArcPart" IsLargeArc="True" SweepDirection="Clockwise"/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=controls:MyControl}, Path=IsValueOutOfRange}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="Red"
AutoReverse="True"
Duration="0:0:0.8"
RepeatBehavior="Forever"
Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)"
Storyboard.TargetName="OuterRing"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="LightGray"
Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)"
Duration="0:0:0.8"
FillBehavior="Stop"
Storyboard.TargetName="OuterRing"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
Control used in the view (XAML)
<controls:MyControl Width="48"
Height="48"
Header="My Header"
IsValueOutOfRange="{x:Bind ValueOutOfRange" />
Part of ControlTemplate in UWP
<Path Fill="Transparent"
Stroke="{TemplateBinding Outline}"
StrokeThickness="{TemplateBinding Thickness}"
StrokeDashCap="Flat"
x:Name="OuterRing">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="OutlineFigurePart">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="OutlineArcPart" IsLargeArc="True" SweepDirection="Clockwise"/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{TemplateBinding IsValueOutOfRange}" Value="True" ComparisonCondition="Equal">
<media:ControlStoryboardAction ControlStoryboardOption="Play">
<media:ControlStoryboardAction.Storyboard>
<Storyboard>
<ColorAnimation
To="Red"
Storyboard.TargetName="OuterRing"
Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)"
AutoReverse="True"
Duration="0:0:8"
RepeatBehavior="Forever" />
</Storyboard>
</media:ControlStoryboardAction.Storyboard>
</media:ControlStoryboardAction>
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Path>
Upvotes: 1
Views: 1163
Reputation: 7727
In UWP, the more common way is to use VisualStateManager
to process:
According to your code, it can be rewritten like this:
code-behind
public bool IsValueOutOfRange
{
get { return (bool)GetValue(IsValueOutOfRangeProperty); }
set { SetValue(IsValueOutOfRangeProperty, value); }
}
// Using a DependencyProperty as the backing store for IsValueOutOfRange. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsValueOutOfRangeProperty =
DependencyProperty.Register("IsValueOutOfRange", typeof(bool), typeof(PathCustomControl), new PropertyMetadata(false,new PropertyChangedCallback(IsValueOutofRange_Changed)));
private static void IsValueOutofRange_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(e.NewValue is bool isOut)
{
var instance = d as PathCustomControl;
if (isOut)
VisualStateManager.GoToState(instance, "Invalid", false);
else
VisualStateManager.GoToState(instance, "Normal", false);
}
}
Template
<ControlTemplate TargetType="local:PathCustomControl">
<Grid x:Name="rootGrid">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Common">
<VisualState x:Name="Invalid">
<Storyboard>
<ColorAnimation To="Red"
AutoReverse="True"
Duration="0:0:0.8"
RepeatBehavior="Forever"
Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)"
Storyboard.TargetName="OuterRing"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Normal">
<Storyboard>
<ColorAnimation To="LightGray"
Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)"
Duration="0:0:0.8"
FillBehavior="Stop"
Storyboard.TargetName="OuterRing"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path Fill="Transparent"
StrokeThickness="{TemplateBinding Thickness}"
Stroke="{TemplateBinding Outline}"
StrokeDashCap="Flat"
x:Name="OuterRing">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="OutlineFigurePart">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="OutlineArcPart" IsLargeArc="True" SweepDirection="Clockwise"/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
</ControlTemplate>
When IsValueOutOfRange
changes, it will switch the control to a different state, thus running different animations.
This is just an example, I use a button on the device to switch the state of the control, it works. But if you want to adapt your project, you need to meet the following two conditions:
Stroke
valueIsValueOutOfRange
is False
when the control is first loaded, so the default color of your Path
should be the same as the Normal
state.Upvotes: 1