English 中文(简体)
How to combine DataTrigger and EventTrigger?
原标题:

NOTE I have asked the related question (with an accepted answer): How to combine DataTrigger and Trigger?

I think I need to combine an EventTrigger and a DataTrigger to achieve what I m after:

  • when an item appears in my ListBox, it should flash for a few moments
  • if the item is Critical then it should remain highlighted

Currently I have a DataTemplate that looks like this:

<DataTemplate DataType="{x:Type Notifications:NotificationViewModel}">
    <Grid HorizontalAlignment="Stretch">
        <Border Name="Background" CornerRadius="8" Background="#80c0c0c0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
        <Border Name="Highlight"  CornerRadius="8" Background="Red"       HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
        <!-- snip actual visual stuff -->
        <Grid.Triggers>
            <EventTrigger RoutedEvent="Grid.Loaded">
                <EventTrigger.Actions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation x:Name="LoadedAnimation" 
                                             Storyboard.TargetName="Highlight" 
                                             Storyboard.TargetProperty="Opacity" 
                                             From="0" To="1" 
                                             RepeatBehavior="5x" 
                                             Duration="0:00:0.2" 
                                             AutoReverse="True" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger.Actions>
            </EventTrigger>
        </Grid.Triggers>
    </Grid>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=IsCritical}" Value="True">
            <Setter TargetName="LoadedAnimation" Property="RepeatBehavior" Value="5.5x" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

The idea is that an EventTrigger animates the Highlight border s opacity between 0 and 1 and back again repeatedly when the item is first loaded, drawing the user s attention to it. The DataTrigger determines the number of times to animate. If the view model reports that the item IsCritical then the animation occurs 5.5 times (such that it ends at opacity 1), otherwise it occurs 5 times (ending at opacity 0.)

However the above XAML doesn t work because the DataTrigger s setter fails with:

Child with Name LoadedAnimation not found in VisualTree.

Fair enough. So, shy of using a custom value converter or putting the animation count on the view model and binding to it, what are my options?

问题回答

I know you said you weren t keen on the idea of a converter, but it looks like the Blend solutions require a library to be installed. The converter isn t much work and signals intent that the rate is directly dependent on the IsCritical property:

public class CriticalAnimationRateConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // Error handling omitted for brevity.
        if ((bool)value)
            return new System.Windows.Media.Animation.RepeatBehavior(5.5);
        else
            return new System.Windows.Media.Animation.RepeatBehavior(5.0);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

And then update your animation:

<DoubleAnimation Storyboard.TargetName="Highlight"
                 Storyboard.TargetProperty="Opacity"
                 From="0"
                 To="1"
                 RepeatBehavior="{Binding IsCritical, Converter={StaticResource CriticalAnimationRateConverter}}"
                 Duration="0:00:0.2"
                 AutoReverse="True" />

The DataTrigger can then be removed.

I would use a behavior instead of triggers in this case. You can write a behavior that attaches an event handler to the associated object s load event and then applies the animation. The behavior may expose some properties, I would expose an AnimationCount (int) property that tells the behavior how many time to repeat the animation on the element that it is associated with. You can then bind this property to the IsCritical property in the view model and use a value converter to convert false to 5 and true to 5.5

Hope this helps

If you have access to the Blend SDK (you should if you re using VS2012+), you should be able to accomplish this entirely in XAML, with something like this (disclaimer: untested):

<Grid HorizontalAlignment="Stretch">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="NotificationStates">
            <VisualState x:Name="Flashing">
                <Storyboard>
                    <DoubleAnimation x:Name="LoadedAnimation" 
                                     Storyboard.TargetName="Highlight" 
                                     Storyboard.TargetProperty="Opacity" 
                                     From="0" To="1" 
                                     RepeatBehavior="5x" 
                                     Duration="0:00:0.2" 
                                     AutoReverse="True" />
                </Storyboard>
            </VisualState>
            <VisualState x:Name="Normal" />
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <Border Name="Background" CornerRadius="8" Background="#80c0c0c0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
    <Border Name="Highlight"  CornerRadius="8" Background="Red"       HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
    <!-- snip actual visual stuff -->
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <ic:GoToStateAction StateName="Flashing"/>
        </i:EventTrigger>
        <ie:DataTrigger Binding="{Binding Path=IsCritical}" Value="True">
            <ic:GoToStateAction StateName="Flashing"/>
        </ie:DataTrigger>
    </i:Interaction.Triggers>
</Grid>

Extract your Storyboard to a VisualState, then use the expression library to switch states within the XAML. You ll need Microsoft.Expression.Interactions library, see also WPF/Silverlight States - Activate from XAML?





相关问题
Manually implementing high performance algorithms in .NET

As a learning experience I recently tried implementing Quicksort with 3 way partitioning in C#. Apart from needing to add an extra range check on the left/right variables before the recursive call, ...

Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

How do I compare two decimals to 10 decimal places?

I m using decimal type (.net), and I want to see if two numbers are equal. But I only want to be accurate to 10 decimal places. For example take these three numbers. I want them all to be equal. 0....

Exception practices when creating a SynchronizationContext?

I m creating an STA version of the SynchronizationContext for use in Windows Workflow 4.0. I m wondering what to do about exceptions when Post-ing callbacks. The SynchronizationContext can be used ...

Show running instance in single instance application

I am building an application with C#. I managed to turn this into a single instance application by checking if the same process is already running. Process[] pname = Process.GetProcessesByName("...

How to combine DataTrigger and EventTrigger?

NOTE I have asked the related question (with an accepted answer): How to combine DataTrigger and Trigger? I think I need to combine an EventTrigger and a DataTrigger to achieve what I m after: when ...

热门标签