Firing a double click event from a WPF ListView it

2019-01-03 04:34发布

In a WPF application using MVVM, I have a usercontrol with a listview item. In run time, it will use databinding to fill the listview with a collection of objects.

What is the correct way to attach a double click event to the items in the listview so that when an item in the list view is doubleclicked, A corresponding event in the view model is fired and has a reference to the item clicked?

How can it be done in a clean MVVM way i.e. no code behind in the View?

标签: wpf mvvm
8条回答
\"骚年 ilove
2楼-- · 2019-01-03 04:56

I realize that this discussion is a year old, but with .NET 4, are there any thoughts on this solution? I absolutely agree that the point of MVVM is NOT to eliminate a code behind file. I also feel very strongly that just because something is complicated, doesn't mean it's better. Here is what I put in the code behind:

    private void ButtonClick(object sender, RoutedEventArgs e)
    {
        dynamic viewModel = DataContext;
        viewModel.ButtonClick(sender, e);
    }
查看更多
我只想做你的唯一
3楼-- · 2019-01-03 04:57

I like to use Attached Command Behaviors and Commands. Marlon Grech has a very good implementation of the Attached Command Behaviors. Using these, we could then assign a style to the ListView's ItemContainerStyle property that will set the command for each ListViewItem.

Here we set the command to be fired on the MouseDoubleClick event, and the CommandParameter, will be the data object that we click on. Here I'm traveling up the visual tree to get the command that I'm using, but you could just as easily create application wide commands.

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

For the commands, you can either implement an ICommand directly, or use some of the helpers like those that come in the MVVM Toolkit.

查看更多
在下西门庆
4楼-- · 2019-01-03 05:04

I am finding it simpler to link the command when the view is created:

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

In my case BindAndShow looks like this (updatecontrols+avalondock):

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

Though the approach should work with whatever method you have of opening new views.

查看更多
beautiful°
5楼-- · 2019-01-03 05:06

I have found a very easy and clean way to do this with the Blend SDK Event triggers. Clean MVVM, reusable and no code-behind.

You probably already have something like this:

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

Now include a ControlTemplate for the ListViewItem like this if you don't already use one:

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

The GridViewRowPresenter will be the visual root of all elements "inside" making up a list row element. Now we could insert a trigger there to look for MouseDoubleClick routed events and call a command via InvokeCommandAction like this:

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

If you have visual elements "above" the GridRowPresenter (probalby starting with a grid) you can also put the Trigger there.

Unfortunately MouseDoubleClick events are not generated from every visual element (they are from Controls, but not from FrameworkElements for example). A workaround is to derive a class from EventTrigger and look for MouseButtonEventArgs with a ClickCount of 2. This effectively filters out all non-MouseButtonEvents and all MoseButtonEvents with a 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);
        }
    }
}

Now we can write this ('h' is the Namespace of the helper class above):

<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楼-- · 2019-01-03 05:06

You can use Caliburn's Action feature to map events to methods on your ViewModel. Assuming you have an ItemActivated method on your ViewModel, then corresponding XAML would look like:

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

For further details you can examine Caliburn's documentation and samples.

查看更多
SAY GOODBYE
7楼-- · 2019-01-03 05:08

I am able to get this to work with .NET 4.5. Seems straight forward and no third party or code behind needed.

<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>
查看更多
登录 后发表回答