Simplifying Visual State Manager with TargetName

Gerald Versluis

Gerald

The Visual State Manager (VSM) has been around since Xamarin.Forms 3.0, but we’re not done developing it. One of the things we really wanted to add is the ability to change a property on any child element within scope. Up until now, you could only set values of properties of the element you applied the VSM to. Recently a PR was merged that changes that for our new Xamarin.Forms 4.5 pre-release version. This functionality will add a lot of extra flexibility to the VSM. In this post I will tell you all about it and show you how to use it yourself.

What is the Visual State Manager?

If you are not familiar with the VSM at all, let us fill you in on that a little bit first. What you can do with the help of the VSM is define certain states for your element and together with that state you can determine how that element should look. For example, think of a button that has a different color when it’s disabled or when it’s focused.

The button in this example can be any Xamarin.Forms VisualElement and the color can be any bindable property. While Xamarin.Forms has defined a couple of states for you, you are also able to define your own custom states. Below you can see a piece of XAML that defines the look of a StackLayout when it is in a Normal or Invalid state. When we switch to the Invalid state the background color of the StackLayout where the VSM resides will become Azure. When we then switch back to Normal, no explicit values are defined and thus the background color will go back to its default value.

<StackLayout x:Name="MyStackLayout" HorizontalOptions="Center" VerticalOptions="CenterAndExpand">
        <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="ColorStates">
                    <VisualState x:Name="Normal" />
                    <VisualState x:Name="Invalid">
                        <VisualState.Setters>
                            <Setter Property="BackgroundColor" Value="Azure"/>
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>

            <Label x:Name="WelcomeLabel" Text="Welcome to Xamarin.Forms!" HorizontalTextAlignment="Start" HorizontalOptions="Start" />

            <Label x:Name="CurrentState"></Label>

            <Button x:Name="ToggleValidButton" Text="Toggle State" Clicked="ToggleValid_OnClicked"></Button>
    </StackLayout>

In the case of the above example we have a simple button that toggles the state. In a more real-life example the trigger might be an invalid data entry of sorts.

Setting State on Multiple Controls

If we continue on this path, you can imagine that the XAML will get very bloated, fast. If we also wanted to give the label and the button a specific style when the state becomes invalid, the XAML would start looking like below.

<StackLayout x:Name="MyStackLayout" HorizontalOptions="Center" VerticalOptions="CenterAndExpand">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ColorStates">
                <VisualState x:Name="Normal" />
                <VisualState x:Name="Invalid">
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="Azure"/>
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <Label x:Name="WelcomeLabel" Text="Welcome to Xamarin.Forms!" HorizontalTextAlignment="Start" HorizontalOptions="Start">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="ColorStates">
                    <VisualState x:Name="Normal" />
                    <VisualState x:Name="Invalid">
                        <VisualState.Setters>
                            <Setter Property="TextColor" Value="Red"/>
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
        </Label>

        <Label x:Name="CurrentState"></Label>

        <Button x:Name="ToggleValidButton" Text="Toggle State" Clicked="ToggleValid_OnClicked">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="ColorStates">
                    <VisualState x:Name="Normal" />
                    <VisualState x:Name="Invalid">
                        <VisualState.Setters>
                            <Setter Property="TextColor" Value="Red"/>
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
        </Button>
    </StackLayout>

Can you spot the problem? We will have to repeat the whole VSM part for each control that we want to have these states on. Wouldn’t it be much easier if we could just reach into child elements within the same scope and set the properties directly from one central Visual State definition?

While the visual representation is in this case not particularly important, or pretty, let us show you the app when the code is running.

Visual State Manager in Action
Visual State Manager in Action

Using TargetName with Visual State Manager

With the new functionality that is added, we can solve the above problem with significantly less XAML. And if there is one thing I love more than XAML, it’s less XAML.

Let’s have another look at the above situation, but now with the help of the TargetName we introduced on the Setter object.

<StackLayout x:Name="MyStackLayout" HorizontalOptions="Center" VerticalOptions="CenterAndExpand">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ColorStates">
                <VisualState x:Name="Normal" />
                <VisualState x:Name="Invalid">
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="Azure"/>

                        <!-- Added the two lines below -->
                        <Setter TargetName="WelcomeLabel" Property="Label.TextColor" Value="Red"/>
                        <Setter TargetName="ToggleValidButton" Property="Button.TextColor" Value="Red"/>
                        <!-- Look here! Added the two lines above -->

                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <Label x:Name="WelcomeLabel" Text="Welcome to Xamarin.Forms!" HorizontalTextAlignment="Start" HorizontalOptions="Start" />

        <Label x:Name="CurrentState"></Label>

        <Button x:Name="ToggleValidButton" Text="Toggle State" Clicked="ToggleValid_OnClicked"></Button>
    </StackLayout>

Notice how I have now just added two lines in the VisualState.Setters collection. With the new TargetName property I am able to reference other elements by its name. Instead of having to repeat the whole visual state XAML, I can now just add these two lines to directly. That way, I can access the elements I want to change some properties on. Note that when you use it like this, you will have to specify the full path to the property in the Property attribute. For example, if you want to set the TextColor on a Label, you will have to specify it as Label.TextColor. The implementation at this time only works for bindable properties.

Learn More

When you want to read more on the Visual State Manager, head over to the documentation pages. Code from this post can be found at this sample repository, use it to give it a try yourself. If you are interested in the actual PR, you can have a look here. And of course, a big thank you to the original author that did most of the work for this.

Besides this change there is a lot of other (upcoming) awesome stuff in the 4.5 release of Xamarin.Forms. Do you have more ideas on how to improve the VSM itself? Please don’t hesitate to open an issue and let us know!

3 comments

Comments are closed.