1
votes

I am currently facing a scenario that I am unsure what is the best way to handle.

Scenario:

  1. ControlA has 2 two custom visualstates, let’s call them “StateOn” and “StateOff”.
  2. I now apply a template on ControlA, let’s call it “templateA”.
  3. “templateA” has one control under it of type ControlB (who’s unaware of StateOn/Off).
  4. ControlB has a Template that handles the visualstate changes of ControlA, namely, StateOn and StateOff.

Problem:
ControlB does not receive changes to the VisualStates fired on ControlA, thus no visual changes happen.

I think the problem has to do with the root element being a control (ControlB), which doesn’t fire gotostate on the desired states. However, I’m wondering what is the simplest/cleanest way to propagate ControlA’s visualstate changes to ControlB.

Thanks for your help!

Henry

Xaml:-

<UserControl x:Class="VisualStateChangePropagation.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:VisualStateChangePropagation"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        <Grid.Resources>

            <SolidColorBrush x:Name="Fill_Bg_Red" Color="Red"/>
            <SolidColorBrush x:Name="Fill_Bg_Blue" Color="Blue"/>

            <ControlTemplate x:Name="templateA" TargetType="Control">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="Common">
                            <VisualState x:Name="StateOn">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="m_rect"
                                                                   Storyboard.TargetProperty="(Rectangle.Fill)">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource Fill_Bg_Red}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="StateOff"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Rectangle x:Name="m_rect" Fill="{StaticResource Fill_Bg_Blue}"/>
                </Grid>
            </ControlTemplate>

            <ControlTemplate x:Name="templateB" TargetType="Control">
                <local:ControlB Template="{StaticResource templateA}"/>
            </ControlTemplate>

        </Grid.Resources>
        <local:ControlA x:Name="m_control1" Template="{StaticResource templateA}" Grid.Column="0"/>
        <Button Click="Button_Click" Grid.Column="1" Content="swap"/>
        <local:ControlA x:Name="m_control2" Template="{StaticResource templateB}" Grid.Column="2"/>
    </Grid>
</UserControl>

code behind:

public class ControlA : Control
{
    public void ToggleState()
    {
        m_isSet = !m_isSet;
        UpdateVisualState();
    }

    private void UpdateVisualState()
    {
        string targetState = m_isSet ? "StateOn" : "StateOff";
        VisualStateManager.GoToState(this, targetState, false);
    }

    private bool m_isSet = false;
}

public class ControlB : Control
{

}
1
Welcome to SO, please take a few minutes to read the FAQ and the Markdown documentation (a useful synposis of which is available in the right hand margin when editing a question).AnthonyWJones

1 Answers

0
votes

First of all both ControlA and ControlB should have a Dependency Property for IsSet.

    public bool IsSet
    {
        get { return (bool)GetValue(IsSetProperty); }
        set { SetValue(IsSetProperty, value); }
    }

    public static readonly DependencyProperty IsSetProperty =
            DependencyProperty.Register(
                    "IsSet",
                    typeof(bool),
                    typeof(ControlA),  //Change in ControlB
                    new PropertyMetadata(false, OnIsSetPropertyChanged));

    private static void OnIsSetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Control source = d as Control;
         string newState = (bool)e.NewValue ? "StateOn" : "StateOff";
         VisualStateManager.GotoState(source, newState, true);
    }

Contrary to your intuition visual states do not propagate at all. The states are only meaningfull to the control to which they are directly attached. However with this dependency property added to both controls you can now propagate the property value via template binding:-

        <ControlTemplate x:Name="templateB" TargetType="Control">
            <local:ControlB Template="{StaticResource templateA}" IsSet="{TemplateBinding IsSet}" />
        </ControlTemplate>

As for you original ControlA code the m_isSet field is no longer required:-

public void ToggleState()
{
    IsSet = !IsSet;
}