2

This question is a follow-up to my previous question as well as this related question about how VisualStates work in WPF.

Currently, my understanding is that animating the same property within different VisualStateGroups can cause problems (see the linked questions).

To resolve these problems, it requires loop-holes to be taken advantage of. (Perhaps loop-hole isn't the correct term, but it appears that the solution isn't what the WPF designers intended.)

I'm wondering what is the correct way to animate the same property in multiple VisualStateGroups without causing adverse side-effects. If it is not possible, what is the correct route for achieving the same visual behavior for a control?

I was able to find some related documentation at MSDN:

The control is always in exactly one state per group. For example, a Button can have focus even when the mouse pointer is not over it, so a Button in the Focused state can be in the MouseOver, Pressed, or Normal state.

This leads me to a second question...

How can you provide a visual behavior that should only occur when two specific VisualStates are active?

Take for example a ToggleButton:

  • If the button is Checked, I would like to display Behavior 1.
  • If the button is Disabled, I would like to display Behavior 2.
  • Finally, if the button is Checked and Disabled, I would like to display Behavior 3.

In the above example, how would you go about rendering the third visual behavior?

Community
  • 1
  • 1
Nicholas Miller
  • 4,205
  • 2
  • 39
  • 62

1 Answers1

3

For the first part of your question, you'll want each state to interact with a different individual object for each instead of hitting the same one with each VisualState, as example;

    <VisualState x:Name="Disabled">
      <Storyboard>
          <ObjectAnimationUsingKeyFrames Storyboard.TargetName="DisabledState" 
                                  Storyboard.TargetProperty="(UIElement.Visibility)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
          </ObjectAnimationUsingKeyFrames>
       </Storyboard>
    </VisualState>
    <VisualState x:Name="Checked">
      <Storyboard>
          <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CheckedState" 
                                  Storyboard.TargetProperty="(UIElement.Visibility)">
            <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
          </ObjectAnimationUsingKeyFrames>
       </Storyboard>
    </VisualState>

<!-- Each state interacts with its own object in your ControlTemplate ideally -->
<Border x:Name="CheckedState" Visibility="Collapsed"
        Background="Green"/>
<Border x:Name="DisabledState" Visibility="Collapsed"
        Background="White" Opacity=".5"/>

Instead of one object sharing a property change like;

<VisualState x:Name="Disabled">
  <Storyboard>
      <ColorAnimation d:IsOptimized="True"
                      Duration="0"
                      Storyboard.TargetName="Background"
                      Storyboard.TargetProperty="(SolidColorBrush.Color)"
                      To="White" />
   </Storyboard>
</VisualState>
<VisualState x:Name="Checked">
  <Storyboard>
      <ColorAnimation d:IsOptimized="True"
                      Duration="0"
                      Storyboard.TargetName="Background"
                      Storyboard.TargetProperty="(SolidColorBrush.Color)"
                      To="Green" />
   </Storyboard>
</VisualState>

<Border x:Name="Background" Background="Blue"/>

As per your second question, A VisualState is going to act as a bool in that it either is, or it isn't in that state. To share a declaration of a state you'd have to add a little more finesse with a MultiTrigger or a converter somewhere or something.

Hope this helps. Cheers

EDIT ADDITION:

So you also have available to you VisualTransition you could employ like;

 <VisualStateManager.VisualStateGroups>
  <VisualStateGroup x:Name="CommonStates">
    <VisualStateGroup.Transitions>
      <VisualTransition From="Normal"
        GeneratedDuration="0:0:0.2"
        To="Checked">
        <VisualTransition.GeneratedEasingFunction>
          <ExponentialEase EasingMode="EaseIn" Exponent="7" />
        </VisualTransition.GeneratedEasingFunction>
      </VisualTransition>
      <VisualTransition From="Checked"
        GeneratedDuration="0:0:0.2"
        To="Normal">
        <VisualTransition.GeneratedEasingFunction>
          <CircleEase EasingMode="EaseIn" />
        </VisualTransition.GeneratedEasingFunction>
      </VisualTransition>
    </VisualStateGroup.Transitions>
    <VisualState x:Name="Normal" />
    <!-- etc, etc, etc -->

So you can play with your different eases, your generated duration times, etc.

Chris W.
  • 22,835
  • 3
  • 60
  • 94
  • Thanks for the answer, it is very helpful. Since you are changing the visibility, the visual behavior occurs immediately. If I need to have a gradual transition, does that mean I must animate the opacity? – Nicholas Miller Sep 12 '14 at 17:28
  • Nah, you can just set the `Duration` so something like `DiscreteObjectKeyFrame Duration="0:0:5"` or whatever works for ya. The Visibility change instead of Opacity is better performant. – Chris W. Sep 12 '14 at 18:31
  • Ah I guess there must be a misunderstanding. The `Duration` property actually belongs to `ObjectAnimationUsingKeyFrames`. Unfortunately, this doesn't cause the **gradual** effect of slowly appearing into view. To be honest, I'm not sure how WPF would handle gradually switching to `Visibility.Hidden` or `Visibility.Collapsed`. I guess modifying the opacity is the only way to achieve what I'm looking for. – Nicholas Miller Sep 12 '14 at 18:51
  • Oh shoot, sorry this is what I get for speeding through questions while I'm waiting on builds, anyway see edited answer in just a sec, you just need a `VisualTransition` – Chris W. Sep 12 '14 at 19:05