Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VisualStateManager VisualGroup precedence

I am trying to use the VisualStateManager inside my ControlTemplate for a ToggleButton. I want the ToggleButton to look one way when checked and another when unchecked. I also want the ToggleButton to look different when it's disabled. The issue I'm having is the Unchecked VisualState seems to be trumping the Disabled VisualState.

The documentation states that "Each VisualStateGroup contains a collection of VisualState objects that are mutually exclusive." That's nice, but what about mutual exclusivity between groups?

Anyhow, here's my ControlTemplate. How can I get the TextBlock to use different color for each of the three states; Checked, Unchecked and Disabled?

<Style x:Key="GraphToggleButtonStyle" TargetType="{x:Type ToggleButton}">
    <Setter Property="BorderThickness" Value="0" />
    <Setter Property="Cursor" Value="Hand" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToggleButton}">
                <Border Background="Transparent">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ColorAnimation Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="TextBlock" To="Pink" Duration="0" />
                                </Storyboard>
                            </VisualState>
                       </VisualStateGroup>
                        <VisualStateGroup x:Name="CheckStates">
                            <VisualState x:Name="Checked">
                                <Storyboard>
                                    <ColorAnimation Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="TextBlock" To="#3AA5DB" Duration="0" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unchecked">
                                <Storyboard>
                                    <ColorAnimation Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="TextBlock" To="Green" Duration="0" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Indeterminate" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border x:Name="Border" CornerRadius="4" Background="{TemplateBinding Background}">
                        <StackPanel>
                            <TextBlock Name="TextBlock" FontFamily="/Resources/#Entypo" Text="🔆" FontSize="87" Foreground="#909090" HorizontalAlignment="Center" Margin="0,-25,0,0" />
                            <TextBlock FontFamily="Proxima Nova Rg" Text="Stimulator" FontSize="18" Foreground="{StaticResource BlackBrush}" HorizontalAlignment="Center" Margin="0,12,0,0" />
                        </StackPanel>
                    </Border>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
like image 452
Matt Becker Avatar asked Sep 16 '25 22:09

Matt Becker


1 Answers

Each VisualStateGroup contains a collection of VisualState objects that are mutually exclusive

This is actually true. However there is still one more requirement, that's each VisualState should not affect the same properties. In this case your Unchecked state and Disabled state affect the same property Foreground of the same element.

So I don't think we can have any elegant solution for this. We just have this work-around (this is in fact used commonly when styling element in WPF). We need some fake element called DisabledTextBlock, this should be placed in the same Grid with the original element TextBlock. Once the Disabled state comes, that fake element should be shown and hide the original one as well as hide all the effect of the Unchecked (or Checked) state and bring the effect of Disabled to the front. Here is the working code:

<ControlTemplate TargetType="{x:Type ToggleButton}">
   <Border Background="Transparent">
     <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CheckStates">
           <!-- unchanged -->
        </VisualStateGroup>
        <VisualStateGroup x:Name="CommonStates">                            
          <VisualState Name="Disabled">
            <Storyboard>
             <DoubleAnimation Storyboard.TargetProperty="Opacity" 
                       Storyboard.TargetName="DisabledTextBlock" To="1" Duration="0" />
            </Storyboard>
          </VisualState>                            
         </VisualStateGroup>                        
      </VisualStateManager.VisualStateGroups>
      <Border x:Name="Border" CornerRadius="4" Background="{TemplateBinding Background}">
         <StackPanel>
           <Grid>
             <TextBlock Name="TextBlock" FontFamily="/Resources/#Entypo" Text="🔆"
                        FontSize="87" Foreground="#909090" HorizontalAlignment="Center"
                        Margin="0,-25,0,0"  Background="Transparent"/>
             <!-- the fake element -->
             <TextBlock Name="DisabledTextBlock" Opacity="0" 
                        FontFamily="{Binding FontFamily, ElementName=TextBlock}" 
                        Text="{Binding Text,ElementName=TextBlock}" 
                        FontSize="{Binding FontSize,ElementName=TextBlock}" 
                        Foreground="Pink" HorizontalAlignment="Center" 
                        Margin="{Binding Margin, ElementName=TextBlock}"  
                        Background="Transparent"
                        FontStyle="{Binding FontStyle,ElementName=TextBlock}" 
                        FontWeight="{Binding FontSize, ElementName=TextBlock}"/>
           </Grid>
           <TextBlock FontFamily="Proxima Nova Rg" Text="Stimulator" FontSize="18" 
                      Foreground="Black" HorizontalAlignment="Center" Margin="0,12,0,0"/>
         </StackPanel>
       </Border>
     </Border>
 </ControlTemplate>

You may have some new requirement, however the idea here is clear. That's the only work-around I think.

like image 89
King King Avatar answered Sep 20 '25 10:09

King King