基于RenderCapability在触发器中切换Storyboard及EventTrigger适配问询
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:Referencein your storyboards, ensure the target element (rootLayout) is initialized before the storyboard. You can avoid this by usingStoryboard.TargetNameinstead (just giverootLayoutanx:Nameand ensure it’s in the same name scope). - Dynamic Tier Changes: If your
RenderCapabilityWrapperupdates theTierproperty 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
BeginStoryboardand useStopStoryboardto avoid overlapping animations.
内容的提问来源于stack exchange,提问作者MrQuery

