Home > C#, Universal Windows Platform > EnumStateTrigger for Universal Windows Platform

EnumStateTrigger for Universal Windows Platform

07/07/2015

The new Universal Windows Platform introduces Adaptive Triggers, that allow to use declaratives rules in XAML to define transitions between visual states, as described in a previous post.

The most interesting thing about this new concept is that we can define our triggers to handle specific scenarios: we simply need to inherit from the StateTriggerBase class and call its SetActive method to activate it.

Suppose for example that we want to show or hide certain UI elements based on some access rules, i.e., if the current user is logged or anonymous, or if he has read/write rights, etc. In this case, we can define an EnumStateTrigger like the following one:

public class EnumStateTrigger : StateTriggerBase
{
    public object Value
    {
        get { return (object)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(object), 
        typeof(EnumStateTrigger),
        new PropertyMetadata(null, ValuePropertyChanged));

    public string ActiveValues
    {
        get { return (string)GetValue(ActiveValuesProperty); }
        set { SetValue(ActiveValuesProperty, value); }
    }

    public static readonly DependencyProperty ActiveValuesProperty =
        DependencyProperty.Register("ActiveValues", typeof(string), 
        typeof(EnumStateTrigger),
        new PropertyMetadata(null, ValuePropertyChanged));

    private static void ValuePropertyChanged(object sender,
        DependencyPropertyChangedEventArgs e)
    {
        var obj = (EnumStateTrigger)sender;
        obj.UpdateTrigger();
    }

    private void UpdateTrigger()
    {
        if (Value == null || ActiveValues == null)
        {
            SetActive(false);
        }
        else
        {
            var currentStates = Value.ToString().ToLower()
                .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(s => s.Trim()).ToList();

            var stateStrings = ActiveValues.ToString().ToLower()
                .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(s => s.Trim()).ToList();

            var isActive = currentStates.Intersect(stateStrings).Any();
            SetActive(isActive);
        }
    }
}

It defines two dependency properties: Value is the current value that must be checked, while ActiveValues are comma separated enum values that activate the trigger. Then, in the UpdateTrigger method (line 32-51), we check if current values interesect active values and activate the trigger correspondingly. We use this tecnique to handle also the | operator for enum flags.

Now we can use this trigger in XAML:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
        <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
            <Button x:Name="accessLevelNone" Content="None" 
                    Click="accessLevelNone_Click" />
            <Button x:Name="accessLevelRead" Content="Read"
                    Click="accessLevelRead_Click" />
            <Button x:Name="accessLevelWrite" Content="Write"
                    Click="accessLevelWrite_Click" />
        </StackPanel>

        <TextBlock x:Name="enumStatus"         
                   HorizontalAlignment="Center" 
                   VerticalAlignment="Center" />
    </StackPanel>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup >
            <VisualState x:Name="none">
                <VisualState.StateTriggers>
                    <triggers:EnumStateTrigger 
                        Value="{x:Bind SelectedAccessLevel, Mode=OneWay}" 
                        ActiveValues="None" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="enumStatus.Text" Value="Access level: none" />
                    <Setter Target="accessLevelNone.FontWeight" Value="Bold" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="readerOrWriter">
                <VisualState.StateTriggers>
                    <triggers:EnumStateTrigger 
                        Value="{x:Bind SelectedAccessLevel, Mode=OneWay}" 
                        ActiveValues="Reader, Writer" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="enumStatus.Text" 
                            Value="Access level: reader or writer" />
                    <Setter Target="accessLevelRead.FontWeight" Value="Bold" />
                    <Setter Target="accessLevelWrite.FontWeight" Value="Bold" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Grid>

We have defined two visual states (lines 19-29 and 30-42), that are activated according to the value of the bound SelectedAccessLevel property (lines 21-23 and 32-34): the former is related to the None enum value, the latter to Reader or Writer.

And finally the code-behind for the page:

public sealed partial class MainPage : Page, INotifyPropertyChanged
{
    public MainPage()
    {
        this.InitializeComponent();
    }

    private AccessLevel selectedAccessLevel = AccessLevel.None;
    public AccessLevel SelectedAccessLevel
    {
        get { return selectedAccessLevel; }
        set
        {
            if (selectedAccessLevel != value)
            {
                selectedAccessLevel = value;
                PropertyChanged?.Invoke(this, 
                    new PropertyChangedEventArgs(nameof(SelectedAccessLevel)));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void accessLevelNone_Click(object sender, RoutedEventArgs e)
    {
        SelectedAccessLevel = AccessLevel.None;
    }

    private void accessLevelRead_Click(object sender, RoutedEventArgs e)
    {
        SelectedAccessLevel = AccessLevel.Reader;
    }

    private void accessLevelWrite_Click(object sender, RoutedEventArgs e)
    {
        SelectedAccessLevel = AccessLevel.Writer;
    }
}

public enum AccessLevel
{
    None,
    Reader,
    Writer
}

When we click one the the buttons (line 25-38), the SelectedAccessLevel property changes, so triggers that are bound to it are re-evaluated, causing the transition to the corresponding visual states.

  1. No comments yet.
  1. 07/07/2015 at 13:02
Comments are closed.
%d bloggers like this: