Miguel
Miguel

Reputation: 13

UWP equivalent for WPF Style.Triggers

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" />
  1. When ValueOutOfRange is set to 'True' from the ViewModel, the Path.Stroke color should animate on 'OuterRing'
  2. When ValueOutOfRange is set to 'False' from the ViewModel, the Path.Stroke color should go back to normal.

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

Answers (1)

Anran Zhang
Anran Zhang

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:

  1. Provide an initial Stroke value
  2. The default IsValueOutOfRange 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

Related Questions