Запуск события двойного щелчка из элемента WPF ListView с использованием MVVM - PullRequest
101 голосов
/ 24 июня 2009

В приложении WPF, использующем MVVM, у меня есть пользовательский контроль с элементом списка. Во время выполнения он будет использовать привязку данных, чтобы заполнить представление списка коллекцией объектов.

Как правильно прикрепить событие двойного щелчка к элементам в представлении списка, чтобы при двойном щелчке по элементу в представлении списка вызывалось соответствующее событие в модели представления, в котором была нажата ссылка на элемент?

Как это можно сделать чистым способом MVVM, т.е. без кода в представлении?

Ответы [ 9 ]

75 голосов
/ 02 октября 2009

Пожалуйста, код позади совсем не плохая вещь. К сожалению, довольно много людей в сообществе WPF поняли это неправильно.

MVVM - это не шаблон для устранения кода позади. Он должен отделять часть вида (внешний вид, анимацию и т. Д.) От логической части (рабочий процесс). Кроме того, вы можете выполнить модульное тестирование логической части.

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

Примеры приложений, которые используют код и все еще выполняют разделение MVVM, можно найти здесь:

WPF Application Framework (WAF) - http://waf.codeplex.com

69 голосов
/ 22 ноября 2013

Я могу заставить это работать с .NET 4.5. Кажется прямолинейным, и никакой третьей стороны или кода не требуется.

<ListView ItemsSource="{Binding Data}">
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid Margin="2">
                    <Grid.InputBindings>
                        <MouseBinding Gesture="LeftDoubleClick" Command="{Binding ShowDetailCommand}"/>
                    </Grid.InputBindings>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <Image Source="..\images\48.png" Width="48" Height="48"/>
                    <TextBlock Grid.Row="1" Text="{Binding Name}" />
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
44 голосов
/ 24 июня 2009

Мне нравится использовать Прикрепленное поведение команд и Команды. Марлон Греч имеет очень хорошую реализацию поведения Attached Command. Используя их, мы могли бы затем назначить стиль для свойства ListView ItemContainerStyle , который будет устанавливать команду для каждого ListViewItem.

Здесь мы устанавливаем команду для запуска по событию MouseDoubleClick, а CommandParameter будет объектом данных, по которому мы щелкаем. Здесь я путешествую по визуальному дереву, чтобы получить команду, которую я использую, но вы также можете легко создавать команды для всего приложения.

<Style x:Key="Local_OpenEntityStyle"
       TargetType="{x:Type ListViewItem}">
    <Setter Property="acb:CommandBehavior.Event"
            Value="MouseDoubleClick" />
    <Setter Property="acb:CommandBehavior.Command"
            Value="{Binding ElementName=uiEntityListDisplay, Path=DataContext.OpenEntityCommand}" />
    <Setter Property="acb:CommandBehavior.CommandParameter"
            Value="{Binding}" />
</Style>

Для команд вы можете либо напрямую реализовать ICommand , либо использовать некоторые помощники, подобные тем, которые входят в MVVM Toolkit .

13 голосов
/ 09 июня 2011

Я нашел очень простой и понятный способ сделать это с помощью триггеров Blend SDK Event. Чистый MVVM, многоразовый и без кода.

Возможно, у вас уже есть что-то вроде этого:

<Style x:Key="MyListStyle" TargetType="{x:Type ListViewItem}">

Теперь добавьте ControlTemplate для ListViewItem, например, если вы его еще не используете:

<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type ListViewItem}">
      <GridViewRowPresenter Content="{TemplateBinding Content}"
                            Columns="{TemplateBinding GridView.ColumnCollection}" />
    </ControlTemplate>
  </Setter.Value>
 </Setter>

GridViewRowPresenter будет визуальным корнем всех элементов «внутри», составляющих элемент строки списка. Теперь мы можем вставить туда триггер для поиска перенаправленных событий MouseDoubleClick и вызвать команду через InvokeCommandAction, например:

<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type ListViewItem}">
      <GridViewRowPresenter Content="{TemplateBinding Content}"
                            Columns="{TemplateBinding GridView.ColumnCollection}">
        <i:Interaction.Triggers>
          <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction Command="{Binding DoubleClickCommand}" />
          </i:EventTrigger>
        </i:Interaction.Triggers>
      </GridViewRowPresenter>
    </ControlTemplate>
  </Setter.Value>
 </Setter>

Если у вас есть визуальные элементы «над» GridRowPresenter (пробный, начиная с сетки), вы также можете поместить Триггер туда.

К сожалению, события MouseDoubleClick генерируются не для каждого визуального элемента (например, для элементов управления, но не для FrameworkElements). Обходной путь - извлечь класс из EventTrigger и найти MouseButtonEventArgs с ClickCount 2. Это эффективно отфильтровывает все не MouseButtonEvents и все MoseButtonEvents с ClickCount! = 2.

class DoubleClickEventTrigger : EventTrigger
{
    protected override void OnEvent(EventArgs eventArgs)
    {
        var e = eventArgs as MouseButtonEventArgs;
        if (e == null)
        {
            return;
        }
        if (e.ClickCount == 2)
        {
            base.OnEvent(eventArgs);
        }
    }
}

Теперь мы можем написать это ('h' - пространство имен класса помощника выше):

<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type ListViewItem}">
      <GridViewRowPresenter Content="{TemplateBinding Content}"
                            Columns="{TemplateBinding GridView.ColumnCollection}">
        <i:Interaction.Triggers>
          <h:DoubleClickEventTrigger EventName="MouseDown">
            <i:InvokeCommandAction Command="{Binding DoubleClickCommand}" />
          </h:DoubleClickEventTrigger>
        </i:Interaction.Triggers>
      </GridViewRowPresenter>
    </ControlTemplate>
  </Setter.Value>
 </Setter>
6 голосов
/ 09 сентября 2010

Я понимаю, что этому обсуждению уже год, но есть ли у .NET 4 какие-нибудь мысли по поводу этого решения? Я абсолютно согласен с тем, что смысл MVVM НЕ в том, чтобы устранить код, стоящий за файлом. Я также очень сильно чувствую, что если что-то сложно, это не значит, что оно лучше. Вот что я вставил в код:

    private void ButtonClick(object sender, RoutedEventArgs e)
    {
        dynamic viewModel = DataContext;
        viewModel.ButtonClick(sender, e);
    }
4 голосов
/ 23 мая 2011

Мне проще связать команду при создании представления:

var r = new MyView();
r.MouseDoubleClick += (s, ev) => ViewModel.MyCommand.Execute(null);
BindAndShow(r, ViewModel);

В моем случае BindAndShow выглядит следующим образом (updatecontrols + avalondock):

private void BindAndShow(DockableContent view, object viewModel)
{
    view.DataContext = ForView.Wrap(viewModel);
    view.ShowAsDocument(dockManager);
    view.Focus();
}

Хотя подход должен работать с любым методом открытия новых представлений.

4 голосов
/ 24 июня 2009

Вы можете использовать функцию действия Caliburn для сопоставления событий с методами в вашей ViewModel. Предполагая, что у вас есть ItemActivated метод на вашем ViewModel, соответствующий XAML будет выглядеть так:

<ListView x:Name="list" 
   Message.Attach="[Event MouseDoubleClick] = [Action ItemActivated(list.SelectedItem)]" >

Для получения более подробной информации вы можете ознакомиться с документацией и образцами Caliburn.

1 голос
/ 24 февраля 2015

Я видел решение из rushui с InuptBindings, но мне все еще не удавалось попасть в область ListViewItem, где не было текста - даже после установки прозрачного фона, поэтому я решил его с помощьюразные шаблоны.

Этот шаблон предназначен для случаев, когда ListViewItem выбран и активен:

<ControlTemplate x:Key="SelectedActiveTemplate" TargetType="{x:Type ListViewItem}">
   <Border Background="LightBlue" HorizontalAlignment="Stretch">
   <!-- Bind the double click to a command in the parent view model -->
      <Border.InputBindings>
         <MouseBinding Gesture="LeftDoubleClick" 
                       Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ItemSelectedCommand}"
                       CommandParameter="{Binding}" />
      </Border.InputBindings>
      <TextBlock Text="{Binding TextToShow}" />
   </Border>
</ControlTemplate>

Этот шаблон предназначен для случаев, когда ListViewItem выбран и неактивен:

<ControlTemplate x:Key="SelectedInactiveTemplate" TargetType="{x:Type ListViewItem}">
   <Border Background="Lavender" HorizontalAlignment="Stretch">
      <TextBlock Text="{Binding TextToShow}" />
   </Border>
</ControlTemplate>

Это стиль по умолчанию, используемый для ListViewItem:

<Style TargetType="{x:Type ListViewItem}">
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate>
            <Border HorizontalAlignment="Stretch">
               <TextBlock Text="{Binding TextToShow}" />
            </Border>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
   <Style.Triggers>
      <MultiTrigger>
         <MultiTrigger.Conditions>
            <Condition Property="IsSelected" Value="True" />
            <Condition Property="Selector.IsSelectionActive" Value="True" />
         </MultiTrigger.Conditions>
         <Setter Property="Template" Value="{StaticResource SelectedActiveTemplate}" />
      </MultiTrigger>
      <MultiTrigger>
         <MultiTrigger.Conditions>
            <Condition Property="IsSelected" Value="True" />
            <Condition Property="Selector.IsSelectionActive" Value="False" />
         </MultiTrigger.Conditions>
         <Setter Property="Template" Value="{StaticResource SelectedInactiveTemplate}" />
      </MultiTrigger>
   </Style.Triggers>
</Style>

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

Надеюсь, это кому-нибудь поможет!

0 голосов
/ 18 мая 2019

Вот поведение, которое выполняется на ListBox и ListView.

public class ItemDoubleClickBehavior : Behavior<ListBox>
{
    #region Properties
    MouseButtonEventHandler Handler;
    #endregion

    #region Methods

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.PreviewMouseDoubleClick += Handler = (s, e) =>
        {
            e.Handled = true;
            if (!(e.OriginalSource is DependencyObject source)) return;

            ListBoxItem sourceItem = source is ListBoxItem ? (ListBoxItem)source : 
                source.FindParent<ListBoxItem>();

            if (sourceItem == null) return;

            foreach (var binding in AssociatedObject.InputBindings.OfType<MouseBinding>())
            {
                if (binding.MouseAction != MouseAction.LeftDoubleClick) continue;

                ICommand command = binding.Command;
                object parameter = binding.CommandParameter;

                if (command.CanExecute(parameter))
                    command.Execute(parameter);
            }
        };
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.PreviewMouseDoubleClick -= Handler;
    }

    #endregion
}

Вот класс расширения, используемый для поиска родителя.

public static class UIHelper
{
    public static T FindParent<T>(this DependencyObject child, bool debug = false) where T : DependencyObject
    {
        DependencyObject parentObject = VisualTreeHelper.GetParent(child);

        //we've reached the end of the tree
        if (parentObject == null) return null;

        //check if the parent matches the type we're looking for
        if (parentObject is T parent)
            return parent;
        else
            return FindParent<T>(parentObject);
    }
}

Использование:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:coreBehaviors="{{Your Behavior Namespace}}"


<ListView AllowDrop="True" ItemsSource="{Binding Data}">
    <i:Interaction.Behaviors>
       <coreBehaviors:ItemDoubleClickBehavior/>
    </i:Interaction.Behaviors>

    <ListBox.InputBindings>
       <MouseBinding MouseAction="LeftDoubleClick" Command="{Binding YourCommand}"/>
    </ListBox.InputBindings>
</ListView>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...