MVVM Передача EventArgs в качестве параметра команды

Я использую Microsoft Expression Blend 4
У меня есть браузер..,

[XAML] ConnectionView "Пустой код позади"

        <WebBrowser local:AttachedProperties.BrowserSource="{Binding Source}">
            <i:Interaction.Triggers>
                <i:EventTrigger>
                    <i:InvokeCommandAction Command="{Binding LoadedEvent}"/>
                </i:EventTrigger>
                <i:EventTrigger EventName="Navigated">
                    <i:InvokeCommandAction Command="{Binding NavigatedEvent}" CommandParameter="??????"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </WebBrowser>  

[С#] Класс AttachedProperties

public static class AttachedProperties
    {
        public static readonly DependencyProperty BrowserSourceProperty = DependencyProperty . RegisterAttached ( "BrowserSource" , typeof ( string ) , typeof ( AttachedProperties ) , new UIPropertyMetadata ( null , BrowserSourcePropertyChanged ) );

        public static string GetBrowserSource ( DependencyObject _DependencyObject )
        {
            return ( string ) _DependencyObject . GetValue ( BrowserSourceProperty );
        }

        public static void SetBrowserSource ( DependencyObject _DependencyObject , string Value )
        {
            _DependencyObject . SetValue ( BrowserSourceProperty , Value );
        }

        public static void BrowserSourcePropertyChanged ( DependencyObject _DependencyObject , DependencyPropertyChangedEventArgs _DependencyPropertyChangedEventArgs )
        {
            WebBrowser _WebBrowser = _DependencyObject as WebBrowser;
            if ( _WebBrowser != null )
            {
                string URL = _DependencyPropertyChangedEventArgs . NewValue as string;
                _WebBrowser . Source = URL != null ? new Uri ( URL ) : null;
            }
        }
    }

[С#] Класс ConnectionViewModel

public class ConnectionViewModel : ViewModelBase
    {
            public string Source
            {
                get { return Get<string> ( "Source" ); }
                set { Set ( "Source" , value ); }
            }

            public void Execute_ExitCommand ( )
            {
                Application . Current . Shutdown ( );
            }

            public void Execute_LoadedEvent ( )
            {
                MessageBox . Show ( "___Execute_LoadedEvent___" );
                Source = ...... ;
            }

            public void Execute_NavigatedEvent ( )
            {
                MessageBox . Show ( "___Execute_NavigatedEvent___" );
            }
    }

[С#] класс ViewModelBase здесь

Наконец:
Связывание с командами работает хорошо, а показания MessageBoxes


Мой вопрос:
Как передать NavigationEventArgs в качестве параметров команды при перемещении события?

Ответ 1

Это нелегко поддерживать. Здесь статья с инструкциями о том, как передать EventArgs в качестве параметров команды.

Возможно, вы захотите изучить MVVMLight - он напрямую поддерживает EventArgs; ваша ситуация будет выглядеть примерно так:

 <i:Interaction.Triggers>
    <i:EventTrigger EventName="Navigated">
        <cmd:EventToCommand Command="{Binding NavigatedEvent}"
            PassEventArgsToCommand="True" />
    </i:EventTrigger>
 </i:Interaction.Triggers>

Ответ 2

Я пытаюсь свести мои зависимости к минимуму, поэтому я сам реализовал это сам, вместо того, чтобы идти с EventToCommand MVVMLight. Работает для меня до сих пор, но отзывы приветствуются.

Xaml:

<i:Interaction.Behaviors>
    <beh:EventToCommandBehavior Command="{Binding DropCommand}" Event="Drop" PassArguments="True" />
</i:Interaction.Behaviors>

ViewModel:

public ActionCommand<DragEventArgs> DropCommand { get; private set; }

this.DropCommand = new ActionCommand<DragEventArgs>(OnDrop);

private void OnDrop(DragEventArgs e)
{
    // ...
}

EventToCommandBehavior:

/// <summary>
/// Behavior that will connect an UI event to a viewmodel Command,
/// allowing the event arguments to be passed as the CommandParameter.
/// </summary>
public class EventToCommandBehavior : Behavior<FrameworkElement>
{
    private Delegate _handler;
    private EventInfo _oldEvent;

    // Event
    public string Event { get { return (string)GetValue(EventProperty); } set { SetValue(EventProperty, value); } }
    public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null, OnEventChanged));

    // Command
    public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } }
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null));

    // PassArguments (default: false)
    public bool PassArguments { get { return (bool)GetValue(PassArgumentsProperty); } set { SetValue(PassArgumentsProperty, value); } }
    public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(false));


    private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var beh = (EventToCommandBehavior)d;

        if (beh.AssociatedObject != null) // is not yet attached at initial load
            beh.AttachHandler((string)e.NewValue);
    }

    protected override void OnAttached()
    {
        AttachHandler(this.Event); // initial set
    }

    /// <summary>
    /// Attaches the handler to the event
    /// </summary>
    private void AttachHandler(string eventName)
    {
        // detach old event
        if (_oldEvent != null)
            _oldEvent.RemoveEventHandler(this.AssociatedObject, _handler);

        // attach new event
        if (!string.IsNullOrEmpty(eventName))
        {
            EventInfo ei = this.AssociatedObject.GetType().GetEvent(eventName);
            if (ei != null)
            {
                MethodInfo mi = this.GetType().GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);
                _handler = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);
                ei.AddEventHandler(this.AssociatedObject, _handler);
                _oldEvent = ei; // store to detach in case the Event property changes
            }
            else
                throw new ArgumentException(string.Format("The event '{0}' was not found on type '{1}'", eventName, this.AssociatedObject.GetType().Name));
        }
    }

    /// <summary>
    /// Executes the Command
    /// </summary>
    private void ExecuteCommand(object sender, EventArgs e)
    {
        object parameter = this.PassArguments ? e : null;
        if (this.Command != null)
        {
            if (this.Command.CanExecute(parameter))
                this.Command.Execute(parameter);
        }
    }
}

ActionCommand:

public class ActionCommand<T> : ICommand
{
    public event EventHandler CanExecuteChanged;
    private Action<T> _action;

    public ActionCommand(Action<T> action)
    {
        _action = action;
    }

    public bool CanExecute(object parameter) { return true; }

    public void Execute(object parameter)
    {
        if (_action != null)
        {
            var castParameter = (T)Convert.ChangeType(parameter, typeof(T));
            _action(castParameter);
        }
    }
}

Ответ 3

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

Существует несколько способов сделать это:

1. Использование инструментов WPF. Самый простой.

Добавить пространства имен:

  • System.Windows.Interactivitiy
  • Microsoft.Expression.Interactions

XAML:

Используйте EventName для вызова нужного события, затем укажите имя Method в MethodName.

<Window>
    xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">

    <wi:Interaction.Triggers>
        <wi:EventTrigger EventName="SelectionChanged">
            <ei:CallMethodAction
                TargetObject="{Binding}"
                MethodName="ShowCustomer"/>
        </wi:EventTrigger>
    </wi:Interaction.Triggers>
</Window>

код:

public void ShowCustomer()
{
    // Do something.
}

2. Использование MVVMLight. Самое сложное.

Установите пакет GalaSoft NuGet.

enter image description here

Получить пространства имен:

  • System.Windows.Interactivity
  • GalaSoft.MvvmLight.Platform

XAML:

Используйте EventName для вызова нужного события, а затем укажите свое имя Command в вашей привязке. Если вы хотите передать аргументы метода, пометьте PassEventArgsToCommand на true.

<Window>
    xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:cmd="http://www.galasoft.ch/mvvmlight">

    <wi:Interaction.Triggers>
       <wi:EventTrigger EventName="Navigated">
           <cmd:EventToCommand Command="{Binding CommandNameHere}"
               PassEventArgsToCommand="True" />
       </wi:EventTrigger>
    </wi:Interaction.Triggers>
</Window>

Код Реализация делегатов: Источник

Для этого вы должны получить пакет Prism MVVM NuGet.

enter image description here

using Microsoft.Practices.Prism.Commands;

// With params.
public DelegateCommand<string> CommandOne { get; set; }
// Without params.
public DelegateCommand CommandTwo { get; set; }

public MainWindow()
{
    InitializeComponent();

    // Must initialize the DelegateCommands here.
    CommandOne = new DelegateCommand<string>(executeCommandOne);
    CommandTwo = new DelegateCommand(executeCommandTwo);
}

private void executeCommandOne(string param)
{
    // Do something here.
}

private void executeCommandTwo()
{
    // Do something here.
}

Код без DelegateCommand: Источник

using GalaSoft.MvvmLight.CommandWpf

public MainWindow()
{
    InitializeComponent();

    CommandOne = new RelayCommand<string>(executeCommandOne);
    CommandTwo = new RelayCommand(executeCommandTwo);
}

public RelayCommand<string> CommandOne { get; set; }

public RelayCommand CommandTwo { get; set; }

private void executeCommandOne(string param)
{
    // Do something here.
}

private void executeCommandTwo()
{
    // Do something here.
}

3. Использование Telerik EventToCommandBehavior. Это вариант.

Вам нужно будет загрузить его пакет NuGet.

XAML:

<i:Interaction.Behaviors>
    <telerek:EventToCommandBehavior
         Command="{Binding DropCommand}"
         Event="Drop"
         PassArguments="True" />
</i:Interaction.Behaviors>

код:

public ActionCommand<DragEventArgs> DropCommand { get; private set; }

this.DropCommand = new ActionCommand<DragEventArgs>(OnDrop);

private void OnDrop(DragEventArgs e)
{
    // Do Something
}

Ответ 4

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

Что я сделал для решения этой проблемы, так это создание ULTRA минимального, EXTREMELY адаптивного пользовательского действия триггера, которое позволило бы мне связать команду и предоставить конвертер событий args передать аргументы командам CanExecute и Execute. Вы не хотите передавать аргументы args аргументам, так как это приведет к тому, что типы слоев вида будут отправлены на уровень модели представления (что никогда не должно происходить в MVVM).

Вот класс EventCommandExecuter, с которым я столкнулся:

public class EventCommandExecuter : TriggerAction<DependencyObject>
{
    #region Constructors

    public EventCommandExecuter()
        : this(CultureInfo.CurrentCulture)
    {
    }

    public EventCommandExecuter(CultureInfo culture)
    {
        Culture = culture;
    }

    #endregion

    #region Properties

    #region Command

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommandExecuter), new PropertyMetadata(null));

    #endregion

    #region EventArgsConverterParameter

    public object EventArgsConverterParameter
    {
        get { return (object)GetValue(EventArgsConverterParameterProperty); }
        set { SetValue(EventArgsConverterParameterProperty, value); }
    }

    public static readonly DependencyProperty EventArgsConverterParameterProperty =
        DependencyProperty.Register("EventArgsConverterParameter", typeof(object), typeof(EventCommandExecuter), new PropertyMetadata(null));

    #endregion

    public IValueConverter EventArgsConverter { get; set; }

    public CultureInfo Culture { get; set; }

    #endregion

    protected override void Invoke(object parameter)
    {
        var cmd = Command;

        if (cmd != null)
        {
            var param = parameter;

            if (EventArgsConverter != null)
            {
                param = EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.InvariantCulture);
            }

            if (cmd.CanExecute(param))
            {
                cmd.Execute(param);
            }
        }
    }
}

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

Этот класс позволяет вам адаптировать аргументы событий, чтобы они могли потребляться логикой команды модели представления. Однако, если вы хотите просто передать аргументы события args на verbatim, просто не указывайте конвертер событий args.

Простейшее использование этого триггерного действия в XAML выглядит следующим образом:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="NameChanged">
        <cmd:EventCommandExecuter Command="{Binding Path=Update, Mode=OneTime}" EventArgsConverter="{x:Static c:NameChangedArgsToStringConverter.Default}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

Если вам нужен доступ к источнику события, вы должны привязываться к владельцу события

<i:Interaction.Triggers>
    <i:EventTrigger EventName="NameChanged">
        <cmd:EventCommandExecuter 
            Command="{Binding Path=Update, Mode=OneTime}" 
            EventArgsConverter="{x:Static c:NameChangedArgsToStringConverter.Default}"
            EventArgsConverterParameter="{Binding ElementName=SomeEventSource, Mode=OneTime}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

(это предполагает, что XAML node, к которому вы прикрепляете триггеры, был назначен x:Name="SomeEventSource"

Этот XAML полагается на импорт некоторых требуемых пространств имен

xmlns:cmd="clr-namespace:MyProject.WPF.Commands"
xmlns:c="clr-namespace:MyProject.WPF.Converters"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

и создавая IValueConverter (называемый NameChangedArgsToStringConverter в этом случае) для обработки реальной логики преобразования. Для базовых преобразователей я обычно создаю экземпляр преобразователя по умолчанию static readonly, который затем я могу ссылаться непосредственно в XAML, как я это сделал выше.

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

Примечание

это несколько похоже на ответ от @adabyron, но он использует триггеры событий вместо поведения. Это решение также обеспечивает способность конверсии аргументов событий, а не то, что решение @adabyron не может этого сделать. У меня действительно нет веских причин, почему я предпочитаю триггеры поведения, просто личный выбор. ИМО любая стратегия является разумным выбором.

Ответ 5

Для того, чтобы люди просто находили этот пост, вы должны знать, что в более новых версиях (не уверен в точной версии, поскольку официальные документы тонкие в этом разделе) поведение по умолчанию InvokeCommandAction, если не указано в CommandParameter, заключается в передаче args события, к которому он привязан как CommandParameter. Поэтому оригинал плаката XAML можно просто написать как:

<i:Interaction.Triggers>
  <i:EventTrigger EventName="Navigated">
    <i:InvokeCommandAction Command="{Binding NavigatedEvent}"/>
  </i:EventTrigger>
</i:Interaction.Triggers>

Затем в вашей команде вы можете принять параметр типа NavigationEventArgs (или любой тип аргументов событий) и он будет автоматически предоставлен.

Ответ 6

Чтобы добавить к тому, что уже сказал joshb, это работает отлично для меня. Обязательно добавьте ссылки на Microsoft.Expression.Interactions.dll и System.Windows.Interactivity.dll и в своем xaml do:

    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

В итоге я использовал что-то вроде этого для своих нужд. Это показывает, что вы также можете передать пользовательский параметр:

<i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">

                <i:InvokeCommandAction Command="{Binding Path=DataContext.RowSelectedItem, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" 
                                       CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}" />
            </i:EventTrigger>
</i:Interaction.Triggers>

Ответ 7

Я не думаю, что вы можете сделать это легко с помощью InvokeCommandAction - я бы посмотрел на EventToCommand от MVVMLight или аналогично.

Ответ 8

С поведением и действиями в Blend для Visual Studio 2013 вы можете использовать InvokeCommandAction. Я попробовал это с событием Drop, и, хотя в XAML не было указано CommandParameter, к моему удивлению, параметр Execute Action содержал DragEventArgs. Я предполагаю, что это произойдет для других событий, но не проверило их.

Ответ 9

Я использую InvokeCommandAction для привязки управляющего загруженного события к команде в модели представления, дайте управляющий топор: Name в Xaml и передайте его как CommandParameter, а затем в упомянутые загруженные команды обработчиков модели просмотра крюка до событий где мне нужно получить аргументы события.

Ответ 10

Вот версия ответа @adabyron, которая предотвращает просачивающуюся абстракцию EventArgs.

Сначала изменен класс EventToCommandBehavior (теперь общий абстрактный класс и отформатирован с помощью очистки кода ReSharper). Обратите внимание на новый виртуальный метод GetCommandParameter и его реализацию по умолчанию:

public abstract class EventToCommandBehavior<TEventArgs> : Behavior<FrameworkElement>
    where TEventArgs : EventArgs
{
    public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior<TEventArgs>), new PropertyMetadata(null, OnEventChanged));
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior<TEventArgs>), new PropertyMetadata(null));
    public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior<TEventArgs>), new PropertyMetadata(false));
    private Delegate _handler;
    private EventInfo _oldEvent;

    public string Event
    {
        get { return (string)GetValue(EventProperty); }
        set { SetValue(EventProperty, value); }
    }

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public bool PassArguments
    {
        get { return (bool)GetValue(PassArgumentsProperty); }
        set { SetValue(PassArgumentsProperty, value); }
    }

    protected override void OnAttached()
    {
        AttachHandler(Event);
    }

    protected virtual object GetCommandParameter(TEventArgs e)
    {
        return e;
    }

    private void AttachHandler(string eventName)
    {
        _oldEvent?.RemoveEventHandler(AssociatedObject, _handler);

        if (string.IsNullOrEmpty(eventName))
        {
            return;
        }

        EventInfo eventInfo = AssociatedObject.GetType().GetEvent(eventName);

        if (eventInfo != null)
        {
            MethodInfo methodInfo = typeof(EventToCommandBehavior<TEventArgs>).GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);

            _handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, methodInfo);
            eventInfo.AddEventHandler(AssociatedObject, _handler);
            _oldEvent = eventInfo;
        }
        else
        {
            throw new ArgumentException($"The event '{eventName}' was not found on type '{AssociatedObject.GetType().FullName}'.");
        }
    }

    private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behavior = (EventToCommandBehavior<TEventArgs>)d;

        if (behavior.AssociatedObject != null)
        {
            behavior.AttachHandler((string)e.NewValue);
        }
    }

    // ReSharper disable once UnusedMember.Local
    // ReSharper disable once UnusedParameter.Local
    private void ExecuteCommand(object sender, TEventArgs e)
    {
        object parameter = PassArguments ? GetCommandParameter(e) : null;

        if (Command?.CanExecute(parameter) == true)
        {
            Command.Execute(parameter);
        }
    }
}

Далее, пример производного класса, который скрывает DragCompletedEventArgs. Некоторые люди выразили озабоченность по поводу утечки абстракции EventArgs в их сборку модели представления. Чтобы этого не произошло, я создал интерфейс, представляющий ценности, которые нам интересны. Интерфейс может работать в сборке модели просмотра с частной реализацией в сборке пользовательского интерфейса:

// UI assembly
public class DragCompletedBehavior : EventToCommandBehavior<DragCompletedEventArgs>
{
    protected override object GetCommandParameter(DragCompletedEventArgs e)
    {
        return new DragCompletedArgs(e);
    }

    private class DragCompletedArgs : IDragCompletedArgs
    {
        public DragCompletedArgs(DragCompletedEventArgs e)
        {
            Canceled = e.Canceled;
            HorizontalChange = e.HorizontalChange;
            VerticalChange = e.VerticalChange;
        }

        public bool Canceled { get; }
        public double HorizontalChange { get; }
        public double VerticalChange { get; }
    }
}

// View model assembly
public interface IDragCompletedArgs
{
    bool Canceled { get; }
    double HorizontalChange { get; }
    double VerticalChange { get; }
}

Введите параметр команды IDragCompletedArgs, аналогичный ответу @adabyron.

Ответ 11

Как адаптация ответа @Mike Fuchs, здесь решение еще меньше. Я использую Fody.AutoDependencyPropertyMarker чтобы уменьшить часть плиты котла.

Класс

public class EventCommand : TriggerAction<DependencyObject>
{
    [AutoDependencyProperty]
    public ICommand Command { get; set; }

    protected override void Invoke(object parameter)
    {
        if (Command != null)
        {
            if (Command.CanExecute(parameter))
            {
                Command.Execute(parameter);
            }
        }
    }
}

EventArgs

public class VisibleBoundsArgs : EventArgs
{
    public Rect VisibleVounds { get; }

    public VisibleBoundsArgs(Rect visibleBounds)
    {
        VisibleVounds = visibleBounds;
    }
}

XAML

<local:ZoomableImage>
   <i:Interaction.Triggers>
      <i:EventTrigger EventName="VisibleBoundsChanged" >
         <local:EventCommand Command="{Binding VisibleBoundsChanged}" />
      </i:EventTrigger>
   </i:Interaction.Triggers>
</local:ZoomableImage>

ViewModel

public ICommand VisibleBoundsChanged => _visibleBoundsChanged ??
                                        (_visibleBoundsChanged = new RelayCommand(obj => SetVisibleBounds(((VisibleBoundsArgs)obj).VisibleVounds)));

Ответ 12

Prism InvokeCommandAction будет по умолчанию передавать аргументы событий, если CommandParameter не установлен.

https://docs.microsoft.com/en-us/previous-versions/msp-np/gg405494(v=pandp.40)#passing-eventargs-parameters-to-the-command

Вот пример, приведенный в ссылке выше. Обратите внимание на использование prism:InvokeCommandAction вместо i:InvokeCommandAction.

<ListBox Grid.Row="1" Margin="5" ItemsSource="{Binding Items}" SelectionMode="Single">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <!-- This action will invoke the selected command in the view model and pass the parameters of the event to it. -->
            <prism:InvokeCommandAction Command="{Binding SelectedCommand}" TriggerParameterPath="AddedItems" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>