65

我正在使用显示数据绑定项目列表的 WPF ListView 控件。

<ListView ItemsSource={Binding MyItems}>
    <ListView.View>
        <GridView>
            <!-- declare a GridViewColumn for each property -->
        </GridView>
    </ListView.View>
</ListView>

我正在尝试获得与ListView.SelectionChanged事件类似的行为,只是我还想检测是否单击了当前选定的项目。SelectionChanged如果再次单击相同的项目(显然),则不会触发该事件。

解决此问题的最佳(最干净)方法是什么?

4

7 回答 7

91

使用 ListView.ItemContainerStyle 属性为您的 ListViewItems 提供一个 EventSetter,它将处理 PreviewMouseLeftButtonDown 事件。然后,在处理程序中,检查是否选择了单击的项目。

XAML:

<ListView ItemsSource={Binding MyItems}>
    <ListView.View>
        <GridView>
            <!-- declare a GridViewColumn for each property -->
        </GridView>
    </ListView.View>
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListViewItem_PreviewMouseLeftButtonDown" />
        </Style>
    </ListView.ItemContainerStyle>
</ListView>

代码隐藏:

private void ListViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var item = sender as ListViewItem;
    if (item != null && item.IsSelected)
    {
        //Do your stuff
    }
}
于 2012-04-18T22:41:27.860 回答
28

您可以处理 ListView 的 PreviewMouseLeftButtonUp 事件。不处理 PreviewMouseLeftButtonDown 事件的原因是,当您处理该事件时,ListView 的 SelectedItem 可能仍为 null。

XAML:

<ListView ... PreviewMouseLeftButtonUp="listView_Click"> ...

后面的代码:

private void listView_Click(object sender, RoutedEventArgs e)
{
    var item = (sender as ListView).SelectedItem;
    if (item != null)
    {
        ...
    }
}
于 2013-12-06T23:25:40.887 回答
19

这些都是很好的建议,但如果我是你,我会在你的视图模型中这样做。在您的视图模型中,您可以创建一个中继命令,然后您可以将其绑定到项目模板中的单击事件。要确定是否选择了相同的项目,您可以在视图模型中存储对所选项目的引用。我喜欢使用 MVVM Light 来处理绑定。这使您的项目将来更容易修改,并允许您在 Blend 中设置绑定。

一切都说完了,您的 XAML 将看起来像 Sergey 建议的那样。我会避免在您的视图中使用后面的代码。我将避免在此答案中编写代码,因为那里有大量示例。

这是一个: 如何将 RelayCommand 与 MVVM Light 框架一起使用

如果您需要示例,请发表评论,我会添加一个。

~干杯

我说我不打算做一个例子,但我是。干得好。

  1. 在您的项目中,仅添加 MVVM Light Libraries。

  2. 为您的视图创建一个类。一般来说,每个视图都有一个视图模型(视图:MainWindow.xaml && viewModel: MainWindowViewModel.cs)

  3. 这是非常非常基本的视图模型的代码:

所有包含的命名空间(如果它们出现在这里,我假设您已经添加了对它们的引用。MVVM Light 在 Nuget 中)

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

现在添加一个基本的公共类:

/// <summary>
/// Very basic model for example
/// </summary>
public class BasicModel 
{
    public string Id { get; set; }
    public string Text { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="text"></param>
    public BasicModel(string text)
    {
        this.Id = Guid.NewGuid().ToString();
        this.Text = text;
    }
}

现在创建您的视图模型:

public class MainWindowViewModel : ViewModelBase
{
    public MainWindowViewModel()
    {
        ModelsCollection = new ObservableCollection<BasicModel>(new List<BasicModel>() {
            new BasicModel("Model one")
            , new BasicModel("Model two")
            , new BasicModel("Model three")
        });
    }

    private BasicModel _selectedBasicModel;

    /// <summary>
    /// Stores the selected mode.
    /// </summary>
    /// <remarks>This is just an example, may be different.</remarks>
    public BasicModel SelectedBasicModel 
    {
        get { return _selectedBasicModel; }
        set { Set(() => SelectedBasicModel, ref _selectedBasicModel, value); }
    }

    private ObservableCollection<BasicModel> _modelsCollection;

    /// <summary>
    /// List to bind to
    /// </summary>
    public ObservableCollection<BasicModel> ModelsCollection
    {
        get { return _modelsCollection; }
        set { Set(() => ModelsCollection, ref _modelsCollection, value); }
    }        
}

在您的视图模型中,添加一个中继命令。请注意,我做了这个异步并让它传递了一个参数。

    private RelayCommand<string> _selectItemRelayCommand;
    /// <summary>
    /// Relay command associated with the selection of an item in the observablecollection
    /// </summary>
    public RelayCommand<string> SelectItemRelayCommand
    {
        get
        {
            if (_selectItemRelayCommand == null)
            {
                _selectItemRelayCommand = new RelayCommand<string>(async (id) =>
                {
                    await selectItem(id);
                });
            }

            return _selectItemRelayCommand;
        }
        set { _selectItemRelayCommand = value; }
    }

    /// <summary>
    /// I went with async in case you sub is a long task, and you don't want to lock you UI
    /// </summary>
    /// <returns></returns>
    private async Task<int> selectItem(string id)
    {
        this.SelectedBasicModel = ModelsCollection.FirstOrDefault(x => x.Id == id);
        Console.WriteLine(String.Concat("You just clicked:", SelectedBasicModel.Text));
        //Do async work

        return await Task.FromResult(1);
    }

在您查看的代码中,为您的视图模型创建一个属性并将您的视图的数据上下文设置为视图模型(请注意,还有其他方法可以做到这一点,但我试图将其作为一个简单的示例。)

public partial class MainWindow : Window
{
    public MainWindowViewModel MyViewModel { get; set; }
    public MainWindow()
    {
        InitializeComponent();

        MyViewModel = new MainWindowViewModel();
        this.DataContext = MyViewModel;
    }
}

在您的 XAML 中,您需要在代码顶部添加一些命名空间

<Window x:Class="Basic_Binding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:Custom="clr-namespace:GalaSoft.MvvmLight;assembly=GalaSoft.MvvmLight"
    Title="MainWindow" Height="350" Width="525">

我添加了“我”和“自定义”。

这是列表视图:

<ListView 
        Grid.Row="0" 
        Grid.Column="0" 
        HorizontalContentAlignment="Stretch"
        ItemsSource="{Binding ModelsCollection}"
        ItemTemplate="{DynamicResource BasicModelDataTemplate}">
    </ListView>

这是 ListView 的 ItemTemplate:

<DataTemplate x:Key="BasicModelDataTemplate">
        <Grid>
            <TextBlock Text="{Binding Text}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseLeftButtonUp">
                        <i:InvokeCommandAction 
                            Command="{Binding DataContext.SelectItemRelayCommand, 
                                RelativeSource={RelativeSource FindAncestor, 
                                        AncestorType={x:Type ItemsControl}}}"
                            CommandParameter="{Binding Id}">                                
                        </i:InvokeCommandAction>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </TextBlock>
        </Grid>
    </DataTemplate>

运行您的应用程序,然后查看输出窗口。您可以使用转换器来处理所选项目的样式。

这可能看起来很复杂,但是当您需要将视图与 ViewModel 分开时(例如,为多个平台开发 ViewModel),它会使生活变得更加轻松。此外,它还使在 Blend 10x 中的工作变得更加容易。一旦你开发了你的 ViewModel,你就可以把它交给一个可以让它看起来很有艺术感的设计师 :)。MVVM Light 添加了一些功能以使 Blend 识别您的 ViewModel。在大多数情况下,您可以在 ViewModel 中执行几乎所有您想要影响视图的操作。

如果有人读到这篇文章,我希望你觉得这很有帮助。如果您有任何问题,请告诉我。我在这个例子中使用了 MVVM Light,但是你可以在没有 MVVM Light 的情况下做到这一点。

于 2015-04-27T00:44:52.767 回答
6

您可以像这样处理单击列表视图项:

<ListView.ItemTemplate>
  <DataTemplate>
     <Button BorderBrush="Transparent" Background="Transparent" Focusable="False">
        <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <i:InvokeCommandAction Command="{Binding DataContext.MyCommand, ElementName=ListViewName}" CommandParameter="{Binding}"/>
                </i:EventTrigger>
        </i:Interaction.Triggers>
      <Button.Template>
      <ControlTemplate>
         <Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
    ...
于 2015-04-03T09:14:12.523 回答
2

这对我有用。

单击一行会触发代码隐藏。

XAML:

<ListView x:Name="MyListView" MouseLeftButtonUp="MyListView_MouseLeftButtonUp">
    <GridView>
        <!-- Declare GridViewColumns. -->
    </GridView>
</ListView.View>

代码隐藏:

private void MyListView_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    System.Windows.Controls.ListView list = (System.Windows.Controls.ListView)sender;
    MyClass selectedObject = (MyClass)list.SelectedItem;
    // Do stuff with the selectedObject.
}
于 2019-08-01T16:12:53.997 回答
0

我无法按照我想要的方式获得公认的答案(请参阅 Farrukh 的评论)。

我想出了一个稍微不同的解决方案,它也感觉更原生,因为它选择了鼠标按钮按下的项目,然后你可以在鼠标按钮被释放时对其做出反应:

XAML:

<ListView Name="MyListView" ItemsSource={Binding MyItems}>
<ListView.ItemContainerStyle>
    <Style TargetType="ListViewItem">
        <EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListViewItem_PreviewMouseLeftButtonDown" />
        <EventSetter Event="PreviewMouseLeftButtonUp" Handler="ListViewItem_PreviewMouseLeftButtonUp" />
    </Style>
</ListView.ItemContainerStyle>

后面的代码:

private void ListViewItem_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    MyListView.SelectedItems.Clear();

    ListViewItem item = sender as ListViewItem;
    if (item != null)
    {
        item.IsSelected = true;
        MyListView.SelectedItem = item;
    }
}

private void ListViewItem_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    ListViewItem item = sender as ListViewItem;
    if (item != null && item.IsSelected)
    {
        // do stuff
    }
}
于 2019-04-06T18:34:26.233 回答
0

我还建议在单击某个项目后取消选择它并使用 MouseDoubleClick 事件

private void listBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    try {
        //Do your stuff here
        listBox.SelectedItem = null;
        listBox.SelectedIndex = -1;
    } catch (Exception ex) {
        System.Diagnostics.Debug.WriteLine(ex.Message);
    }
}
于 2018-12-29T14:12:56.407 回答