You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于RenderCapability在触发器中切换Storyboard及EventTrigger适配问询

How to Switch Storyboards for EventTriggers Based on Render Capability Tier in WPF

Great question! Since WPF's EventTrigger doesn't natively support conditional logic like MultiDataTrigger, we have two clean, XAML-friendly approaches to solve this—one using a value converter for quick implementation, and another using attached properties + multi-triggers for more control.


Option 1: Use a Value Converter to Select the Storyboard

This is the simplest approach if you just need to pick between two storyboards based on the tier.

Step 1: Create the Converter

First, make a converter that maps the render tier to the appropriate storyboard resource:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

public class TierToStoryboardConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // Cast the tier value and the parent element (to access resources)
        if (value is int tier && parameter is FrameworkElement hostElement)
        {
            var resourceKey = tier == 0 ? "FadeOutLow" : "FadeOutHigh";
            return hostElement.TryFindResource(resourceKey) ?? DependencyProperty.UnsetValue;
        }
        return DependencyProperty.UnsetValue;
    }

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

Step 2: Update Your XAML

Add the converter to your resources, then bind the Storyboard property in your BeginStoryboard to the tier value, using the converter to select the right resource:

<Window.Resources>
    <!-- Declare the converter -->
    <local:TierToStoryboardConverter x:Key="TierToStoryboardConverter" />

    <!-- Your existing storyboards -->
    <Storyboard x:Key="FadeOutLow">
        <DoubleAnimation Storyboard.Target="{x:Reference rootLayout}" Storyboard.TargetProperty="Opacity" From="1" To="0.5" Duration="0:0:.2" />
    </Storyboard>
    <Storyboard x:Key="FadeOutHigh">
        <DoubleAnimation Storyboard.Target="{x:Reference rootLayout}" Storyboard.TargetProperty="Opacity" From="1" To="0.5" Duration="0:0:.2" />
        <DoubleAnimation Storyboard.Target="{x:Reference rootLayout}" Storyboard.TargetProperty="Effect.Radius" From="0" To="3" Duration="0:0:.2" />
    </Storyboard>
</Window.Resources>

<!-- Update your PromptServiceListener -->
<controls:PromptServiceListener>
    <controls:PromptServiceListener.Triggers>
        <EventTrigger RoutedEvent="controls:PromptServiceListener.PromptShown">
            <BeginStoryboard 
                Storyboard="{Binding Source={x:Static local:RenderCapabilityWrapper.Instance}, 
                                    Path=Tier, 
                                    Converter={StaticResource TierToStoryboardConverter},
                                    ConverterParameter={x:Reference rootLayout}}" />
        </EventTrigger>
    </controls:PromptServiceListener.Triggers>
</controls:PromptServiceListener>

Option 2: Use Attached Properties + MultiTriggers

This approach is better if you need more control over trigger conditions or want to avoid converter logic. It converts the event into a bindable property that we can combine with the render tier.

Step 1: Create Attached Properties

Make attached properties to track when the PromptShown event fires:

using System.Windows;
using System.Windows.Threading;

public static class PromptServiceHelpers
{
    // Property to mark when the prompt is shown
    public static readonly DependencyProperty IsPromptShownProperty =
        DependencyProperty.RegisterAttached(
            "IsPromptShown",
            typeof(bool),
            typeof(PromptServiceHelpers),
            new PropertyMetadata(false));

    public static bool GetIsPromptShown(DependencyObject obj) => (bool)obj.GetValue(IsPromptShownProperty);
    public static void SetIsPromptShown(DependencyObject obj, bool value) => obj.SetValue(IsPromptShownProperty, value);

    // Property to enable event tracking
    public static readonly DependencyProperty TrackPromptShownProperty =
        DependencyProperty.RegisterAttached(
            "TrackPromptShown",
            typeof(bool),
            typeof(PromptServiceHelpers),
            new PropertyMetadata(false, OnTrackPromptShownChanged));

    public static bool GetTrackPromptShown(DependencyObject obj) => (bool)obj.GetValue(TrackPromptShownProperty);
    public static void SetTrackPromptShown(DependencyObject obj, bool value) => obj.SetValue(TrackPromptShownProperty, value);

    private static void OnTrackPromptShownChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is controls:PromptServiceListener listener && (bool)e.NewValue)
        {
            listener.PromptShown += (s, args) =>
            {
                // Mark the prompt as shown
                SetIsPromptShown(listener, true);
                // Reset after a short delay to allow the trigger to fire again
                Dispatcher.CurrentDispatcher.BeginInvoke(
                    DispatcherPriority.Background,
                    () => SetIsPromptShown(listener, false));
            };
        }
    }
}

Step 2: Update Your XAML

Use a style with MultiTrigger to combine the IsPromptShown flag and render tier:

<controls:PromptServiceListener local:PromptServiceHelpers.TrackPromptShown="True">
    <controls:PromptServiceListener.Style>
        <Style TargetType="controls:PromptServiceListener">
            <Style.Triggers>
                <!-- Low-tier: Use blur-free storyboard -->
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="local:PromptServiceHelpers.IsPromptShown" Value="True" />
                        <Condition Binding="{Binding Source={x:Static local:RenderCapabilityWrapper.Instance}, Path=Tier}" Value="0" />
                    </MultiTrigger.Conditions>
                    <MultiTrigger.EnterActions>
                        <BeginStoryboard Storyboard="{StaticResource FadeOutLow}" />
                    </MultiTrigger.EnterActions>
                </MultiTrigger>

                <!-- High-tier: Use storyboard with blur -->
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="local:PromptServiceHelpers.IsPromptShown" Value="True" />
                        <Condition Binding="{Binding Source={x:Static local:RenderCapabilityWrapper.Instance}, Path=Tier}" Value="1" />
                    </MultiTrigger.Conditions>
                    <MultiTrigger.EnterActions>
                        <BeginStoryboard Storyboard="{StaticResource FadeOutHigh}" />
                    </MultiTrigger.EnterActions>
                </MultiTrigger>
            </Style.Triggers>
        </Style>
    </controls:PromptServiceListener.Style>

    <!-- Your storyboard resources here -->
</controls:PromptServiceListener>

Key Notes

  • Initialization Order: If you use x:Reference in your storyboards, ensure the target element (rootLayout) is initialized before the storyboard. You can avoid this by using Storyboard.TargetName instead (just give rootLayout an x:Name and ensure it’s in the same name scope).
  • Dynamic Tier Changes: If your RenderCapabilityWrapper updates the Tier property when hardware capabilities change, both approaches will automatically pick the right storyboard for subsequent events.
  • Storyboard Management: If you need to handle repeated events, add a name to your BeginStoryboard and use StopStoryboard to avoid overlapping animations.

内容的提问来源于stack exchange,提问作者MrQuery

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 03:59:33