87

I have the following ListView:

<ListView Name="TrackListView">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Title" Width="100" 
                            HeaderTemplate="{StaticResource BlueHeader}" 
                            DisplayMemberBinding="{Binding Name}"/>

            <GridViewColumn Header="Artist" Width="100"  
                            HeaderTemplate="{StaticResource BlueHeader}"  
                            DisplayMemberBinding="{Binding Album.Artist.Name}" />
        </GridView>
    </ListView.View>
</ListView>

How can I attach an event to every bound item that will fire on double-clicking the item?

Kjartan
  • 18,591
  • 15
  • 71
  • 96
Andreas Grech
  • 105,982
  • 98
  • 297
  • 360

8 Answers8

113

Found the solution from here: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/3d0eaa54-09a9-4c51-8677-8e90577e7bac/


XAML:

<UserControl.Resources>
    <Style x:Key="itemstyle" TargetType="{x:Type ListViewItem}">
        <EventSetter Event="MouseDoubleClick" Handler="HandleDoubleClick" />
    </Style>
</UserControl.Resources>

<ListView Name="TrackListView" ItemContainerStyle="{StaticResource itemstyle}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Title" Width="100" HeaderTemplate="{StaticResource BlueHeader}" DisplayMemberBinding="{Binding Name}"/>
            <GridViewColumn Header="Artist" Width="100" HeaderTemplate="{StaticResource BlueHeader}" DisplayMemberBinding="{Binding Album.Artist.Name}" />
        </GridView>
    </ListView.View>
</ListView>

C#:

protected void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
    var track = ((ListViewItem) sender).Content as Track; //Casting back to the binded Track
}
svick
  • 236,525
  • 50
  • 385
  • 514
Andreas Grech
  • 105,982
  • 98
  • 297
  • 360
  • 15
    If you don't need to re-use the style, you can put it directly into the section and remove the x:Key. – David Schmitt Jun 05 '09 at 12:24
  • 9
    This worked for me, too. Thanks! BTW, you will probably want to stop the bubbling of the doubleClick event within your handler by setting: e.Handled = true; – Tom A Jun 23 '09 at 22:25
  • 1
    I have a problem with this. That is, I use x:Key-less styles in the window to style all the UI elements, including the ListViews used in a custom control on that window. Putting this event-handler in the custom control's xaml disables the style applied in the window. – Jeno Csupor Dec 02 '09 at 16:06
  • 9
    Just out of curiosity, is there another way to do this that doesn't violate MVVM? – Dave Feb 21 '10 at 07:44
  • Yes, in place of retrieving the selected item into the Content property of the sender parameter of the event, you can bind the SelectedItem of the listview and take this value into the handler of the double click event. – Coolweb Mar 02 '10 at 10:53
  • 14
    As a warning: using an `EventSetter` can lead to memory leaks if its handler's target lives longer than the `ListViewItem`. I spent the last few days debugging a serious memory leak (20mb at a time), only to find out that `ListViewItem`s and their associated memory were being leaked through an `EventSetter`. – Zach Johnson Sep 13 '10 at 22:03
  • @Dave (and those who upvoted his comment): See [my answer](http://stackoverflow.com/a/35735737/3549027) to [this similar question](http://stackoverflow.com/q/4360998/3549027) for a more MVVM-y way of achieving this (short answer, turn the code into an attached behavior). – dlf Mar 08 '16 at 15:40
85

No memory leaks (no need to unsubscribe each item), works fine:

XAML:

<ListView MouseDoubleClick="ListView_MouseDoubleClick" ItemsSource="{Binding TrackCollection}" />

C#:

    void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        var item = ((FrameworkElement) e.OriginalSource).DataContext as Track;
        if (item != null)
        {
            MessageBox.Show("Item's Double Click handled!");
        }
    }
epox
  • 9,236
  • 1
  • 55
  • 38
  • 2
    Excellent, no more need to worry about memory leaks, and frankly it's just a hell of a lot cleaner. – ean5533 Nov 12 '12 at 23:37
  • 3
    This is not enough if your list contains a complex object. You need to use a visual tree helper to find the parent ListViewItem and from there you can take the datacontext – ravyoli May 21 '14 at 20:50
  • 3
    Clean and simple. Thanks. – Eternal21 Jul 18 '14 at 14:05
  • 1
    Very nice and helpful. In my case I have the additional select button that does the select action. So I used the double click as follows: 'MouseDoubleClick="SelectBtn_Click"' 'private void SelectBtn_Click(object sender, RoutedEventArgs e) { }' – Kishore Mar 24 '16 at 19:07
  • 4
    This is why you always scroll past the accepted answer. Just in case... – aggsol Sep 05 '19 at 08:23
  • 1
    Don't think this works if you double click on the scrollbar. – Matt Fitzmaurice Aug 08 '23 at 08:09
10

My solution was based on @epox_sub's answer which you should look at for where to put the Event Handler in the XAML. The code-behind didn't work for me because my ListViewItems are complex objects. @sipwiz's answer was a great hint for where to look...

void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    var item = ListView.SelectedItem as Track;
    if (item != null)
    {
      MessageBox.Show(item + " Double Click handled!");
    }
}

The bonus with this is you get the SelectedItem's DataContext binding (Track in this case). Selected Item works because the first click of the double-click selects it.

epox
  • 9,236
  • 1
  • 55
  • 38
CAD bloke
  • 8,578
  • 7
  • 65
  • 114
  • 2
    This works great but, unfortunately, the double click event is also fired when the user double clicks empty space in the listview and, since the SelectedItem is not cleared when the user clicks on empty space, the event is executed on the previously selected item. – somethingRandom Aug 06 '21 at 06:29
10

Using MVVM is possible if you install the Microsoft.Xaml.Behaviours.WPF package.

Once you have installed the package then you can reference it in your XAML like so:

xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

You can then add the following to your ListView, which binds a command in your View Model to the Mouse Double Click event.

<ListView 
    x:Name="ListView" 
    ItemsSource="{Binding SelectedTrack}" SelectedItem="{Binding SelectedTrack}" >
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction 
                Command="{Binding SelectTrackCommand}"
                CommandParameter={Binding ElementName=ListView, Path=SelectedItems}/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
        ...........
        ...........
</ListView>

The Command attribute should bind to a Command implementation in your ViewModel. For example:

private CommandHandler<IList> _SelectTrackCommand;
public CommandHandler<IList> SelectTrackCommand => _SelectTrackCommand ?? (_SelectTrackCommand = new CommandHandler<IList>(items =>
            {
                SelectionChanged(items);
            }));

Where the SelectionChanged method above is doing something with the ListView items.

NOTE: that the ListView items are passed in using the CommandParameter in XAML. This is optional but can be useful if you want to operate on the SelectedItems or SelectedItem from within your Command and are not binding these elsewhere in your ViewModel.

mpj
  • 33
  • 6
Code Name Jack
  • 2,856
  • 24
  • 40
5

For those interested in mostly maintaining the MVVM pattern, I used Andreas Grech's answer to make a work-around.

Basic flow:

User double-clicks item -> Event handler in code behind -> ICommand in view model

ProjectView.xaml:

<UserControl.Resources>
    <Style TargetType="ListViewItem" x:Key="listViewDoubleClick">
        <EventSetter Event="MouseDoubleClick" Handler="ListViewItem_MouseDoubleClick"/>
    </Style>
</UserControl.Resources>

...

<ListView ItemsSource="{Binding Projects}" 
          ItemContainerStyle="{StaticResource listViewDoubleClick}"/>

ProjectView.xaml.cs:

public partial class ProjectView : UserControl
{
    public ProjectView()
    {
        InitializeComponent();
    }

    private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        ((ProjectViewModel)DataContext)
            .ProjectClick.Execute(((ListViewItem)sender).Content);
    }
}

ProjectViewModel.cs:

public class ProjectViewModel
{
    public ObservableCollection<Project> Projects { get; set; } = 
               new ObservableCollection<Project>();

    public ProjectViewModel()
    {
        //Add items to Projects
    }

    public ICommand ProjectClick
    {
        get { return new DelegateCommand(new Action<object>(OpenProjectInfo)); }
    }

    private void OpenProjectInfo(object _project)
    {
        ProjectDetailView project = new ProjectDetailView((Project)_project);
        project.ShowDialog();
    }
}

DelegateCommand.cs can be found here.

In my instance, I have a collection of Project objects that populate the ListView. These objects contain more properties than are shown in the list, and I open a ProjectDetailView (a WPF Window) to display them.

The sender object of the event handler is the selected ListViewItem. Subsequently, the Project that I want access to is contained within the Content property.

Kjartan
  • 18,591
  • 15
  • 71
  • 96
Micah Vertal
  • 564
  • 1
  • 7
  • 16
2

Building on epox_spb's answer, I added in a check to avoid errors when double clicking in the GridViewColumn headers.

void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    var dataContext = ((FrameworkElement)e.OriginalSource).DataContext;
    if (dataContext is Track)
    {
        MessageBox.Show("Item's Double Click handled!");
    }
}
Community
  • 1
  • 1
Kramer
  • 519
  • 1
  • 5
  • 13
  • very cool - works with PowerShell- `$myListView.Add_MouseDoubleClick({ Param($sender, $ev); $e = [System.Windows.Input.MouseButtonEventArgs]$ev; $itemData = ([System.Windows.FrameworkElement]$e.OriginalSource).DataContext }); if ($item -ne $null) { Write-Host $itemData; } })` --- Casting is not required but helps in ISE to get completion – BananaAcid May 24 '19 at 15:43
1

In your example are you trying to catch when an item in your ListView is selected or when a column header is clicked on? If it's the former you would add a SelectionChanged handler.

<ListView Name="TrackListView" SelectionChanged="MySelectionChanged">

If it's the latter you would have to use some combination of MouseLeftButtonUp or MouseLeftButtonDown events on the GridViewColumn items to detect a double click and take appropriate action. Alternatively you could handle the events on the GridView and work out from there which column header was under the mouse.

sipsorcery
  • 30,273
  • 24
  • 104
  • 155
0

if you are populating your Listview through a
ObservableCollection<ItemClass> Items class ,
and non of the answer above works for you "as what happen to me" , then use :

private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    var item = ((FrameworkElement)e.OriginalSource).DataContext as ItemClass; //<< your class name here

    if (item != null)
    {
        MessageBox.Show(item.UserName + " : item Double Click handled!");
    }
}

of course ItemClass would be your setter/getter class name

The Doctor
  • 636
  • 1
  • 7
  • 23