21
votes

Is it possible to give the condition within the EventTrigger?? I have written a following EventTrigger (Mouse.MouseLeave) for Radio button. I want this should not be trigged for an item which is in Checked condition (IsChecked=True).

<EventTrigger RoutedEvent="Mouse.MouseLeave" SourceName="border">                                 
      <BeginStoryboard Name="out_BeginStoryboard" Storyboard="{StaticResource out}" />
      <RemoveStoryboard BeginStoryboardName="over_BeginStoryboard" />
</EventTrigger>

Please let me know how can I achieve this?

Thanks in advance.

6

6 Answers

24
votes

You can't use EventTrigger in this way. WPF's RoutedEventHandler that invokes EventTriggers doesn't provide any mechanism for making the trigger conditional, and you can't fix this by subclassing TriggerAction because there is no protected Invoke() or Execute() action to override.

However this can be done quite easily using a custom class. Here's how it would be used:

<Border>
  <my:ConditionalEventTrigger.Triggers>
    <my:ConditionalEventTriggerCollection>
      <my:ConditionalEventTrigger RoutedEvent="Mouse.MouseLeave"
                                  Condition="{Binding IsChecked, ElementName=checkbox}">
        <BeginStoryboard Name="out_BeginStoryboard" Storyboard="{StaticResource out}" />               
        <RemoveStoryboard BeginStoryboardName="over_BeginStoryboard" />               
      </my:ConditionalEventTrigger>               
    </my:ConditionalEventTriggerCollection>
  </my:ConditionalEventTrigger.Triggers>
  ...

And here's how it would be implemented:

[ContentProperty("Actions")] 
public class ConditionalEventTrigger : FrameworkContentElement
{ 
  public RoutedEvent RoutedEvent { get; set; } 
  public List<TriggerAction> Actions { get; set; }

  // Condition
  public bool Condition { get { return (bool)GetValue(ConditionProperty); } set { SetValue(ConditionProperty, value); } }
  public static readonly DependencyProperty ConditionProperty = DependencyProperty.Register("Condition", typeof(bool), typeof(ConditionalEventTrigger));

  // "Triggers" attached property
  public static ConditionalEventTriggerCollection GetTriggers(DependencyObject obj) { return (ConditionalEventTriggerCollection)obj.GetValue(TriggersProperty); }
  public static void SetTriggers(DependencyObject obj, ConditionalEventTriggerCollection value) { obj.SetValue(TriggersProperty, value); }
  public static readonly DependencyProperty TriggersProperty = DependencyProperty.RegisterAttached("Triggers", typeof(ConditionalEventTriggerCollection), typeof(ConditionalEventTrigger), new PropertyMetadata 
  { 
    PropertyChangedCallback = (obj, e) => 
    { 
      // When "Triggers" is set, register handlers for each trigger in the list 
      var element = (FrameworkElement)obj; 
      var triggers = (List<ConditionalEventTrigger>)e.NewValue;
      foreach(var trigger in triggers)
        element.AddHandler(trigger.RoutedEvent, new RoutedEventHandler((obj2, e2) =>
          trigger.OnRoutedEvent(element)));
    } 
  });

  public ConditionalEventTrigger()
  {
    Actions = new List<TriggerAction>();
  }

  // When an event fires, check the condition and if it is true fire the actions 
  void OnRoutedEvent(FrameworkElement element) 
  { 
    DataContext = element.DataContext;  // Allow data binding to access element properties
    if(Condition) 
    { 
      // Construct an EventTrigger containing the actions, then trigger it 
      var dummyTrigger = new EventTrigger { RoutedEvent = _triggerActionsEvent }; 
      foreach(var action in Actions) 
        dummyTrigger.Actions.Add(action); 

      element.Triggers.Add(dummyTrigger); 
      try 
      { 
        element.RaiseEvent(new RoutedEventArgs(_triggerActionsEvent)); 
      } 
      finally 
      { 
        element.Triggers.Remove(dummyTrigger); 
      } 
    } 
  } 

  static RoutedEvent _triggerActionsEvent = EventManager.RegisterRoutedEvent("", RoutingStrategy.Direct, typeof(EventHandler), typeof(ConditionalEventTrigger)); 

} 

// Create collection type visible to XAML - since it is attached we cannot construct it in code 
public class ConditionalEventTriggerCollection : List<ConditionalEventTrigger> {} 

Enjoy!

11
votes

This is what worked for me...

I wanted to execute an animation based on the mouse hovering over a UI element and the UI element's associated owner being active (i.e. enabled to make a player move).

To support these requirements, I used relative source binding to overcome the lack of support for event trigger conditions.

Example:

<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding RelativeSource={RelativeSource self}, Path=IsMouseOver}" Value="True" />
        <Condition Binding="{Binding Path=IsPlayer1Active}" Value="True" />
    </MultiDataTrigger.Conditions>
    <MultiDataTrigger.EnterActions>
        <BeginStoryboard>
            <Storyboard>
                <ColorAnimation Storyboard.TargetProperty="Background.GradientStops[0].Color" To="#FF585454" Duration="0:0:.25"/>
                <ColorAnimation Storyboard.TargetProperty="Background.GradientStops[1].Color" To="Black" Duration="0:0:2"/>
            </Storyboard>
        </BeginStoryboard>
    </MultiDataTrigger.EnterActions>
</MultiDataTrigger>
4
votes

Here's my modified version of Ray's answer, which creates and attaches dummy events only when the source triggers are set, rather than doing it each time. I thought this would be better for my scenario as I am raising an event on hundreds of items, not just one or two:

[ContentProperty("Actions")]
public class ConditionalEventTrigger : FrameworkContentElement
{
    static readonly RoutedEvent DummyEvent = EventManager.RegisterRoutedEvent(
        "", RoutingStrategy.Direct, typeof(EventHandler), typeof(ConditionalEventTrigger));

    public static readonly DependencyProperty TriggersProperty = DependencyProperty.RegisterAttached(
        "Triggers", typeof(ConditionalEventTriggers), typeof(ConditionalEventTrigger),
        new FrameworkPropertyMetadata(RefreshTriggers));

    public static readonly DependencyProperty ConditionProperty = DependencyProperty.Register(
        "Condition", typeof(bool), typeof(ConditionalEventTrigger)); // the Condition is evaluated whenever an event fires

    public ConditionalEventTrigger()
    {
        Actions = new List<TriggerAction>();
    }

    public static ConditionalEventTriggers GetTriggers(DependencyObject obj)
    { return (ConditionalEventTriggers)obj.GetValue(TriggersProperty); }

    public static void SetTriggers(DependencyObject obj, ConditionalEventTriggers value)
    { obj.SetValue(TriggersProperty, value); }

    public bool Condition
    {
        get { return (bool)GetValue(ConditionProperty); }
        set { SetValue(ConditionProperty, value); }
    }

    public RoutedEvent RoutedEvent { get; set; }
    public List<TriggerAction> Actions { get; set; }

    // --- impl ----

    // we can't actually fire triggers because WPF won't let us (stupid sealed internal methods)
    // so, for each trigger, make a dummy trigger (on a dummy event) with the same actions as the real trigger,
    // then attach handlers for the dummy event
    public static void RefreshTriggers(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var targetObj = (FrameworkElement)obj;
        // start by clearing away the old triggers
        foreach (var t in targetObj.Triggers.OfType<DummyEventTrigger>().ToArray())
            targetObj.Triggers.Remove(t);

        // create and add dummy triggers
        foreach (var t in ConditionalEventTrigger.GetTriggers(targetObj))
        {
            t.DataContext = targetObj.DataContext; // set and Track DataContext so binding works
            // targetObj.GetDataContextChanged().WeakSubscribe(dc => t.DataContext = targetObj.DataContext);

            var dummyTrigger = new DummyEventTrigger { RoutedEvent = DummyEvent };
            foreach (var action in t.Actions)
                dummyTrigger.Actions.Add(action);

            targetObj.Triggers.Add(dummyTrigger);
            targetObj.AddHandler(t.RoutedEvent, new RoutedEventHandler((o, args) => {
                if (t.Condition) // evaluate condition when the event gets fired
                    targetObj.RaiseEvent(new RoutedEventArgs(DummyEvent));
            }));
        }
    }

    class DummyEventTrigger : EventTrigger { }
}

public class ConditionalEventTriggers : List<ConditionalEventTrigger> { }

It's used like this:

<Border>
  <local:ConditionalEventTrigger.Triggers>
    <local:ConditionalEventTriggers>
      <local:ConditionalEventTrigger RoutedEvent="local:ClientEvents.Flash" Condition="{Binding IsFlashing}">
        <BeginStoryboard Name="FlashAnimation">...

The line

// targetObj.GetDataContextChanged().WeakSubscribe(dc => t.DataContext = targetObj.DataContext);

is using the reactive framework and some extension methods I wrote, basically we need to subscribe to the .DataContextChanged event of the target object, but we need to do it with a weak reference. If your objects don't ever change their datacontext, then you won't need this code at all

1
votes

I know this is an old post, but here is something that worked for me when I ended up here for answers. Basically I wanted a panel that would animate from the right side of the screen on mouse over, and then go back when the mouse left. But, only when the panel wasn't pinned. The IsShoppingCartPinned property is present on my ViewModel. As far as your scenario, you could replace the IsShoppingCartPinned property with your checkbox IsChecked property, and run any kind of animations on the EventTriggers.

Here is the code:

<Grid.Style>
     <Style TargetType="{x:Type Grid}">
          <Setter Property="Margin" Value="0,20,-400,20"/>
          <Setter Property="Grid.Column" Value="0"/>
          <Style.Triggers>
               <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                         <Condition Binding="{Binding IsShoppingCartPinned}" Value="False"/>
                         <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True"/>
                    </MultiDataTrigger.Conditions>
                    <MultiDataTrigger.EnterActions>
                         <BeginStoryboard Name="ExpandPanel">
                              <Storyboard>
                                   <ThicknessAnimation Duration="0:0:0.1" Storyboard.TargetProperty="Margin" To="0,20,0,20"/>
                              </Storyboard>
                         </BeginStoryboard>
                    </MultiDataTrigger.EnterActions>
                    <MultiDataTrigger.ExitActions>
                         <BeginStoryboard Name="HidePanel">
                              <Storyboard>
                                   <ThicknessAnimation Duration="0:0:0.1" Storyboard.TargetProperty="Margin" To="0,20,-400,20"/>
                              </Storyboard>
                         </BeginStoryboard>
                    </MultiDataTrigger.ExitActions>
               </MultiDataTrigger>
               <DataTrigger Binding="{Binding IsShoppingCartPinned}" Value="True">
                    <DataTrigger.EnterActions>
                         <RemoveStoryboard BeginStoryboardName="ExpandPanel"/>
                         <RemoveStoryboard BeginStoryboardName="HidePanel"/>
                    </DataTrigger.EnterActions>
                    <DataTrigger.Setters>
                         <Setter Property="Margin" Value="0"/>
                         <Setter Property="Grid.Column" Value="1"/>
                    </DataTrigger.Setters>
               </DataTrigger>
          </Style.Triggers>
     </Style>
</Grid.Style>
0
votes

in your case you need:

<EventTrigger RoutedEvent="Checked" SourceName="border">

EDIT: Based on your comments, you are looking for a multidatatrigger.

   <MultiDataTrigger>
        <MultiDataTrigger.Conditions>
            <Condition SourceName="border" Property="IsMouseOver" Value="false" />                                            
        </MultiDataTrigger.Conditions>
        <MultiDataTrigger.EnterActions>
            <BeginStoryboard Name="out_BeginStoryboard" Storyboard="{StaticResource out}" />
            <RemoveStoryboard BeginStoryboardName="over_BeginStoryboard" />
        </MultiDataTrigger.EnterActions>
   </MultiDataTrigger>
0
votes

Based on Ray and Orion, Here is my version, the goal is that you can bind 2 triggers to a button, and flip the states when click (Or more states if you like, and it should work for all Control). When you bind the ConditionProperty, it's a little tricky that you have to write ConditionValue as False for True, and Ture for False. I guess it's because the event handler of button executed before updating bindings. It used like this:

<Button x:Name="HoldButton" Content="{Binding Status.Running}"/>
    <mut:ConditionalEventTrigger.ConditionTriggers>
        <mut:ConditionalEventTriggers>
            <mut:ConditionalEventTrigger RoutedEvent="ButtonBase.Click" ConditionProperty="{Binding Status.Running}" ConditionValue="False">
                <BeginStoryboard x:Name="OnHold_BeginStoryboard" Storyboard="{StaticResource OnHold}"/>
            </mut:ConditionalEventTrigger>
            <mut:ConditionalEventTrigger RoutedEvent="ButtonBase.Click" ConditionProperty="{Binding Status.Running}" ConditionValue="True">
                <StopStoryboard BeginStoryboardName="OnHold_BeginStoryboard"/>
            </mut:ConditionalEventTrigger>
        </mut:ConditionalEventTriggers>
    </mut:ConditionalEventTrigger.ConditionTriggers>
</Button>

Here is code:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Markup;

namespace MyUtility.Trigger
{
    [ContentProperty("Actions")]
    public class ConditionalEventTrigger : FrameworkContentElement
    {
        public static readonly DependencyProperty ConditionTriggersProperty = DependencyProperty.RegisterAttached(
            "ConditionTriggers",
            typeof(ConditionalEventTriggers),
            typeof(ConditionalEventTrigger),
            new FrameworkPropertyMetadata(OnConditionalEventTriggersChanged));

        public static ConditionalEventTriggers GetConditionTriggers(FrameworkElement element)
        {
            return (ConditionalEventTriggers)element.GetValue(ConditionTriggersProperty);
        }

        public static void SetConditionTriggers(FrameworkElement element, List<ConditionalEventTrigger> value)
        {
            element.SetValue(ConditionTriggersProperty, value);
        }

        public static readonly DependencyProperty ConditionPropertyProperty = DependencyProperty.Register(
            "ConditionProperty",
            typeof(bool),
            typeof(ConditionalEventTrigger));

        public bool ConditionProperty
        {
            get
            {
                return (bool)GetValue(ConditionPropertyProperty);
            }
            set
            {
                SetValue(ConditionPropertyProperty, value);
            }
        }

        public static readonly DependencyProperty ConditionValueProperty = DependencyProperty.Register(
            "ConditionValue",
            typeof(bool),
            typeof(ConditionalEventTrigger));

        public bool ConditionValue
        {
            get
            {
                return (bool)GetValue(ConditionValueProperty);
            }
            set
            {
                SetValue(ConditionValueProperty, value);
            }
        }

        private static readonly RoutedEvent m_DummyEvent = EventManager.RegisterRoutedEvent(
            "ConditionalEventTriggerDummyEvent",
            RoutingStrategy.Direct,
            typeof(EventHandler),
            typeof(ConditionalEventTrigger));

        public RoutedEvent RoutedEvent { get; set; }
        public List<TriggerAction> Actions { get; set; }

        public ConditionalEventTrigger()
        {
            Actions = new List<TriggerAction>();
        }

        public static void OnConditionalEventTriggersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var element = (FrameworkElement)obj;
            var triggers = (ConditionalEventTriggers)e.NewValue;
            foreach(ConditionalEventTrigger t in triggers)
            {
                element.RemoveHandler(t.RoutedEvent, new RoutedEventHandler((obj2, e2) => t.OnRoutedEvent(element)));
                element.AddHandler(t.RoutedEvent, new RoutedEventHandler((obj2, e2) => t.OnRoutedEvent(element)));
            }
        }

        public void OnRoutedEvent(FrameworkElement element)
        {
            this.DataContext = element.DataContext;
            if (this.ConditionProperty == this.ConditionValue)
            {
                // .Net doesn't allow us fire a trigger directly, so we bingd trigger on Element and then fire the element.
                var dummyTrigger = new EventTrigger { RoutedEvent = m_DummyEvent };

                foreach (TriggerAction action in this.Actions)
                {
                    dummyTrigger.Actions.Add(action);
                }

                element.Triggers.Add(dummyTrigger);

                try
                {
                    element.RaiseEvent(new RoutedEventArgs(m_DummyEvent));
                }
                finally
                {
                    element.Triggers.Remove(dummyTrigger);
                }
            }
        }
    }

    public class ConditionalEventTriggers : List<ConditionalEventTrigger> {}
}