views:

32

answers:

1

I've got a WPF app that has a listbox which I'm trying to apply some mouseover effects to. It all works fine when I use simple Setters to change the background color on mouseover/selection, but I figured it would look nicer if it animated between states, so I switched the Setters for enter/exit Storyboards. It all works nicely initially (mouseover animates in & out, selection animates in & out), but once something has been selected and then become deselcted, it will never do the mouseover effect again.

Here's the minimal code example to show the issue:

<Window x:Class="WpfTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="500" Width="500">
  <Window.Resources>
    <Style x:Key="ListboxItemStyle" TargetType="{x:Type ListBoxItem}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type ListBoxItem}">
            <Border x:Name="border" BorderThickness="1" Height="50" Background="#000">
              <ContentPresenter />
            </Border>
            <ControlTemplate.Triggers>
              <MultiTrigger>
                <MultiTrigger.Conditions>
                  <Condition Property="IsMouseOver" Value="True" />
                  <Condition Property="IsSelected" Value="False" />
                </MultiTrigger.Conditions>
                <MultiTrigger.EnterActions>
                  <BeginStoryboard>
                    <Storyboard>
                      <ColorAnimation Duration="0:0:0.15" Storyboard.TargetName="border"
                          Storyboard.TargetProperty="Background.Color" To="#00F" />
                    </Storyboard>
                  </BeginStoryboard>
                </MultiTrigger.EnterActions>
                <MultiTrigger.ExitActions>
                  <BeginStoryboard>
                    <Storyboard>
                      <ColorAnimation Duration="0:0:0.3" Storyboard.TargetName="border"
                          Storyboard.TargetProperty="Background.Color" To="#008" />
                    </Storyboard>
                  </BeginStoryboard>
                </MultiTrigger.ExitActions>
              </MultiTrigger>
              <Trigger Property="IsSelected" Value="True">
                <Trigger.EnterActions>
                  <BeginStoryboard>
                    <Storyboard>
                      <ColorAnimation Duration="0:0:0.15" Storyboard.TargetName="border"
                          Storyboard.TargetProperty="Background.Color" To="#F00" />
                    </Storyboard>
                  </BeginStoryboard>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                  <BeginStoryboard>
                    <Storyboard>
                      <ColorAnimation Duration="0:0:0.3" Storyboard.TargetName="border"
                          Storyboard.TargetProperty="Background.Color" To="#800" />
                    </Storyboard>
                  </BeginStoryboard>
                </Trigger.ExitActions>
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Window.Resources>
  <Grid>
    <ListBox x:Name="ConnectedDevicesListBox"
        ItemContainerStyle="{DynamicResource ListboxItemStyle}" >
      <ListItem/>
      <ListItem/>
      <ListItem/>
      <ListItem/>
      <ListItem/>
      <ListItem/>
      <ListItem/>
      <ListItem/>
    </ListBox>
  </Grid>
</Window>

I've made the exit animations fade to a non-black color so you can see that the it's getting stuck after the IsSelected exit storyboard.

Any ideas?

+2  A: 

The default behavior of a Storyboard is to continue setting the property value to value from the last frame of the animation. See Animation Tips and Tricks, especially the "Can't Change the Value of a Property after Animating it" section. You are never stopping the storyboards, so eventually they are all running and whichever one is declared last is setting the final value.

You can set the FillBehavior of the Storyboards to Stop so that the Storyboard will stop setting the value after it completes. I think you will want to do this on the ExitActions storyboards, but not on the EnterAction storyboards unless you also set the background color with a normal trigger. Something like this:

<MultiTrigger>
    <MultiTrigger.Conditions>
        <Condition Property="IsMouseOver" Value="True" />
        <Condition Property="IsSelected" Value="False" />
    </MultiTrigger.Conditions>
    <MultiTrigger.EnterActions>
        <BeginStoryboard>
            <Storyboard>
                <ColorAnimation Duration="0:0:0.15"
                    Storyboard.TargetName="border"
                    Storyboard.TargetProperty="Background.Color" To="#00F" />
            </Storyboard>
        </BeginStoryboard>
    </MultiTrigger.EnterActions>
    <MultiTrigger.ExitActions>
        <BeginStoryboard>
            <Storyboard FillBehavior="Stop">
                <ColorAnimation Duration="0:0:0.3"
                    Storyboard.TargetName="border"
                    Storyboard.TargetProperty="Background.Color" To="#008" />
            </Storyboard>
        </BeginStoryboard>
    </MultiTrigger.ExitActions>
</MultiTrigger>
<Trigger Property="IsSelected" Value="True">
    <Trigger.EnterActions>
        <BeginStoryboard>
            <Storyboard>
                <ColorAnimation Duration="0:0:0.15"
                    Storyboard.TargetName="border"
                    Storyboard.TargetProperty="Background.Color" To="#F00" />
            </Storyboard>
        </BeginStoryboard>
    </Trigger.EnterActions>
    <Trigger.ExitActions>
        <BeginStoryboard>
            <Storyboard FillBehavior="Stop">
                <ColorAnimation Duration="0:0:0.3"
                    Storyboard.TargetName="border"
                    Storyboard.TargetProperty="Background.Color" To="#800" />
            </Storyboard>
        </BeginStoryboard>
    </Trigger.ExitActions>
</Trigger>
Quartermeister
Thanks for that, makes perfect sense. I forgot about the whole `FillBehavior` thing... been a while since I dealt with storyboards (asside gripe: why, oh why didn't they provide a `"StopAndHold"` option which stopped the storyboard (like `Stop`), but kept the final state (like `HoldEnd`) and make that the default?).
Alconja