WPF ListView:附加双击(在项目上)事件

87
我有以下的 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>

如何为每个已绑定的项目附加一个事件,在双击项目时触发该事件?

8个回答

113

从这里找到了解决方案: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
}

15
如果您不需要重新使用样式,可以直接将其放入<ListView.Resources/>部分并删除x:Key。 - David Schmitt
9
我也尝试了这个方法,谢谢! 顺便说一句,您可能希望通过设置 e.Handled = true 来停止您的处理程序中的双击事件的冒泡传播。 - Tom A
1
我遇到了一个问题。也就是说,我在窗口中使用没有x:Key的样式来为所有UI元素进行样式设置,包括在该窗口上使用的自定义控件中使用的ListViews。将此事件处理程序放置在自定义控件的xaml中会禁用窗口中应用的样式。 - Jeno Csupor
9
好的,我会尽力以最简洁明了的方式进行翻译。以下是需要翻译的内容:Just out of curiosity, is there another way to do this that doesn't violate MVVM?只是出于好奇,有没有另一种方法可以完成这个任务而不违反MVVM原则? - Dave
14
警告:如果EventSetter的处理程序的目标生命周期比ListViewItem长,可能会导致内存泄漏。我最近几天一直在调试一个严重的内存泄漏问题(每次20mb),最终发现ListViewItems及其关联的内存是通过EventSetter泄漏的。 - Zach Johnson
显示剩余2条评论

85

没有内存泄漏 (不需要取消订阅每个项),运行正常:

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!");
        }
    }

2
太棒了,不再需要担心内存泄漏问题,而且说实话,代码看起来更加整洁。 - ean5533
3
如果您的列表包含复杂对象,仅此还不足。您需要使用视觉树助手来查找父ListViewItem,然后从那里获取数据上下文。 - ravyoli
3
清晰简洁。谢谢。 - Eternal21
1
非常好和有用。在我的情况下,我有额外的选择按钮执行选择操作。因此,我使用双击如下: 'MouseDoubleClick="SelectBtn_Click"' 'private void SelectBtn_Click(object sender, RoutedEventArgs e) { }' - Kishore
4
这就是为什么你总是滑过被接受的答案。只是以防万一... - aggsol
1
如果你双击滚动条,这个方法可能行不通。 - Matt Fitzmaurice

10

我的解决方案基于@epox_sub的答案,您应该查看它以了解在XAML中放置事件处理程序的位置。因为我的ListViewItems是复杂对象,所以代码后台对我无效。@sipwiz的答案是一个很好的提示,可以指导您查看...

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

这样做的好处是你可以获得SelectedItem的数据上下文绑定(在本例中是Track)。选定项有效是因为双击的第一次单击会选择它。


2
这个功能很好,但不幸的是,当用户在ListView中双击空白区域时,双击事件也会被触发。由于当用户单击空白区域时SelectedItem未被清除,因此事件会在先前选择的项目上执行。 - somethingRandom

10
使用MVVM是可能的,只要你安装了Microsoft.Xaml.Behaviours.WPF包。
一旦你安装了这个包,你就可以在你的XAML中引用它,像这样:
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

您可以将以下内容添加到您的 ListView 中,将 View Model 中的命令绑定到鼠标双击事件上。
<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>
Command属性应该绑定到ViewModel中的一个命令实现。例如:
private CommandHandler<IList> _SelectTrackCommand;
public CommandHandler<IList> SelectTrackCommand => _SelectTrackCommand ?? (_SelectTrackCommand = new CommandHandler<IList>(items =>
            {
                SelectionChanged(items);
            }));

在上面的SelectionChanged方法中,对ListView的项进行了一些操作。

注意:ListView的项是通过XAML中的CommandParameter传递的。这是可选的,但如果您想要在命令中操作SelectedItems或SelectedItem,并且在ViewModel中没有绑定它们到其他地方,那么这将非常有用。


5

对于那些主要维护MVVM模式的人,我使用了Andreas Grech的答案来做一个变通。

基本流程:

用户双击项目 -> 代码后台中的事件处理程序 -> 视图模型中的ICommand

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。

在我的实例中,我有一个Project对象的集合,用于填充ListView。 这些对象包含比列表中显示的更多属性,并且我打开了一个ProjectDetailView(WPF Window)来显示它们。

事件处理程序的sender对象是所选的ListViewItem。 随后,我想要访问的Project包含在Content属性中。


2

epox_spb 的回答 基础上,我添加了一个检查来避免在 GridViewColumn 标题中双击时出现错误。

void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    var dataContext = ((FrameworkElement)e.OriginalSource).DataContext;
    if (dataContext is Track)
    {
        MessageBox.Show("Item's Double Click handled!");
    }
}

非常酷 - 适用于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; } }) --- 不需要强制转换,但在ISE中有助于获取完成。 - BananaAcid

1
在你的例子中,你是想捕获当ListView中的项目被选中还是当列标题被点击时?如果是前者,你需要添加一个SelectionChanged处理程序。
<ListView Name="TrackListView" SelectionChanged="MySelectionChanged">

如果是后者,您将不得不在GridViewColumn项上使用MouseLeftButtonUp或MouseLeftButtonDown事件的某种组合来检测双击并采取适当的操作。或者,您可以在GridView上处理事件,并从那里计算出鼠标下方的哪个列标题。

我想要在有界项目上触发事件,而不是标题。 - Andreas Grech
这对我来说是新的。感谢您提供答案(我会从我的代码中删除no DoubleClick事件语句)。 - sipsorcery

0
如果您是通过 ObservableCollection<ItemClass> Items 类来填充您的 Listview,而上面的任何答案都不适用于您(就像我遇到的那样),那么请使用以下方法:
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!");
    }
}

当然,ItemClass 就是你的 setter/getter 类名


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接