Как указать условие для EventTrigger?

18

Можно ли дать условие в EventTrigger ?? Я написал следующую кнопку EventTrigger (Mouse.MouseLeave) для радио. Я хочу, чтобы это не должно запускаться для элемента, который находится в состоянии «Проверено» (IsChecked = True).

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

Пожалуйста, дайте мне знать, как я могу это достичь?

Спасибо заранее.

    
задан Prabu 04.05.2010 в 12:17
источник

5 ответов

21

Вы не можете использовать EventTrigger таким образом. WPF RoutedEventHandler, который вызывает EventTriggers, не предоставляет никакого механизма для создания условного триггера, и вы не можете исправить это путем подклассификации TriggerAction, потому что для переопределения не требуется действие Invoke () или Execute ().

Однако это можно сделать довольно легко с помощью пользовательского класса. Вот как это будет использоваться:

<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>
  ...

И вот как это будет реализовано:

[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> {} 

Наслаждайтесь!

    
ответ дан Ray Burns 13.05.2010 в 00:52
  • Не могли бы вы загрузить здесь действующий код? То, что вы опубликовали, не работает. Было бы очень полезно, если вы разместите какое-либо примерное приложение для этого. –  Prabu 17.05.2010 в 09:15
  • Что, вы хотите получить действующий код? Это будет дополнительно $ 10, пожалуйста ;-) То, что я написал раньше, было совсем не в том числе, и было опечатки и ошибка. Я бросил его в Visual Studio только сейчас, исправил опечатки и протестировал его. Я обновил ответ с помощью рабочего кода. –  Ray Burns 17.05.2010 в 18:29
  • Что вы имеете в виду? Я сделал. Я ответил на ваш вопрос 12 мая с головы. Вы запросили фактический рабочий код 17 мая, и я опубликовал рабочий код несколько часов спустя. Я знаю, что код, который я опубликовал 17 мая, работает, потому что я его протестировал. Попробуйте. Если вы получите ошибки, сообщите мне, что это такое. –  Ray Burns 31.05.2010 в 05:21
  • Большое спасибо за этот ответ. Я попытался реализовать триггеры условного события, и вы просто не можете этого сделать. Я хочу найти кодировщиков WPF, которые решили сделать все триггерные файлы запечатаны внутренними и пощекотать их :-( Так или иначе, к тому времени, когда вы попытаетесь (и не сработаете) с кучей подходов к реализации условных триггеров событий, вы в значительной степени с помощью точного решения, которое вы получили здесь. Одна незначительная настройка, которую я добавил, заключается в том, что она добавляет и удаляет фиктивные триггеры только тогда, когда условие изменяется, а не каждый раз, когда происходит событие. –  Orion Edwards 02.08.2010 в 01:51
  • @Orion: «Тонкая настройка», которую вы описываете, не имеет для меня смысла. EventTriggers почти никогда не запускаются более 1 или 2 раза в секунду, но условие может легко меняться тысячи раз в секунду. Таким образом, самый безопасный курс для общего использования - это работа во время триггера события. Это также требует намного меньше кода по двум причинам: 1. С помощью того, как вы предлагаете, вам нужно использовать ObservableCollection для Actions и обрабатывать INotifyCollectionChanged для обновления фиктивного триггера, который представляет собой много кода, и 2. Вам нужно чтобы отслеживать фиктивный триггер, чтобы вы могли его удалить позже. –  Ray Burns 04.08.2010 в 02:12
Показать остальные комментарии
7

Это то, что сработало для меня ...

Я хотел выполнить анимацию, основанную на мыши, зависающей над элементом пользовательского интерфейса, и связанный с ним пользовательский элемент элемента интерфейса (т. е. включен, чтобы заставить игрока перемещаться).

Чтобы поддержать эти требования, я использовал относительную привязку источника, чтобы преодолеть отсутствие поддержки условий запуска событий.

Пример:

<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>
    
ответ дан Scott Nimrod 14.11.2010 в 15:43
4

Вот моя измененная версия ответа Рэя , который создает и прикрепляет фиктивные события только тогда, когда установлены триггеры источника, а не каждый раз. Я думал, что это будет лучше для моего сценария, поскольку я поднимаю событие на сотни предметов, а не один или два:

[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> { }

Используется так:

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

Линия

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

использует реактивную структуру и некоторые методы расширения, которые я написал, в основном нам нужно подписаться на событие .DataContextChanged целевого объекта, но нам нужно сделать это со слабой ссылкой. Если ваши объекты никогда не меняют свой datacontext, вам совсем не понадобится этот код

    
ответ дан Orion Edwards 04.08.2010 в 22:48
  • Эта модификация более эффективна, но обратите внимание, что ее можно использовать только тогда, когда: 1. Установлен только один ConditionalEventTrigger и 2. Коллекция Actions никогда не изменяется после ее инициализации. Обе эти проблемы разрешимы с помощью дополнительного кода. –  Ray Burns 05.08.2010 в 01:42
  • Дополнительный код, позволяющий использовать несколько ConditionalEventTriggers для одного объекта: поддерживать пул статических объектов RoutedEvent. Каждый раз, когда создается фиктивный триггер, выберите событие из пула, которое не используется ни в одном другом EventTrigger на объекте. Если такого события не существует, зарегистрируйте новое событие и добавьте его в пул. –  Ray Burns 05.08.2010 в 01:43
  • Дополнительный код, позволяющий изменять коллекцию действий: реализовать ее как ObservableCollection. Сохраните ссылку на dummyTrigger в объекте ConditionalEventTrigger. Когда коллекция Action уведомляет об изменении свойства, очистите и заново заполните список действий в dummyTrigger. –  Ray Burns 05.08.2010 в 01:45
0

в вашем случае вам нужно:

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

EDIT: Основываясь на ваших комментариях, вы ищете мультитатригер.

   <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>
    
ответ дан J Rothe 07.05.2010 в 22:44
  • Спасибо за ответ. Мой фактический запрос заключается в том, что анимация должна произойти для всех элементов, когда мышь покидает элемент управления. Но это не должно происходить, когда оно находится в состоянии Checed. –  Prabu 10.05.2010 в 08:32
  • Я обновил свой первоначальный ответ. Вам необходимо использовать многоточечное устройство и определить дополнительные условия. –  J Rothe 12.05.2010 в 16:18
  • Проблема с MultiDataTrigger заключается в том, что изменение состояния может вызвать анимацию. Например, в этом случае, если мышь пользователя находится в другом месте в пользовательском интерфейсе, и они меняют данные, в результате чего флажок будет установлен, анимация будет воспроизводиться, даже если мышь нигде не приближается. (Также обратите внимание, что вы опустили второе условие в своем MultiDataTrigger, которое, как я предполагаю, будет иметь значение <Условие SourceName=". Proerty=" IsChecked "Value=" True "/>) –  Ray Burns 12.05.2010 в 23:55
  • Правда, я оставил второе условие - я ожидал, что любые другие условия будут заполнены по мере необходимости, поскольку это только отправная точка. Тем не менее, я думаю, что ваше состояние инвертировано - он хочет, чтобы анимация играла для чего-либо, где флажок не установлен, и ваше условие выполняется, если оно истинно. Возможно, добавьте дополнительное свойство зависимостей, позволяющее инвертировать условие? Я полагаю, что он мог бы использовать ValueConverter, но это кажется посторонним для простой операции bool ... –  J Rothe 18.05.2010 в 17:11
0

Я знаю, что это старый пост, но вот что-то, что сработало для меня, когда я оказался здесь для ответов. В принципе, мне нужна панель, которая будет анимироваться с правой стороны экрана при наведении мыши, а затем вернуться назад, когда мышь останется. Но, только когда панель не была закреплена. Свойство IsShoppingCartPinned присутствует в моей модели ViewModel. Что касается вашего сценария, вы можете заменить свойство IsShoppingCartPinned свойством checkbox IsChecked и запустить любые анимации на EventTriggers.

Вот код:

<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>
    
ответ дан DannyLee89 12.05.2017 в 18:03