WPF ListView - 检测单击所选项目的时间

scr*_*pni 56 wpf listview

我正在使用WPF ListView控件,它显示数据绑定项的列表.

<ListView ItemsSource={Binding MyItems}>
    <ListView.View>
        <GridView>
            <!-- declare a GridViewColumn for each property -->
        </GridView>
    </ListView.View>
</ListView>
Run Code Online (Sandbox Code Playgroud)

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

什么是最好(最干净)的方法来解决这个问题?

Chi*_*ura 69

使用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>
Run Code Online (Sandbox Code Playgroud)

代码隐藏:

private void ListViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var item = sender as ListViewItem;
    if (item != null && item.IsSelected)
    {
        //Do your stuff
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 实际上,您可以直接在`ListView`上设置处理程序,不需要EventSetter. (11认同)
  • item.IsSelected 在第一次单击时无效,所以我必须双击。if (item != null) { } 工作正常。 (2认同)

lzn*_*znt 21

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

XAML:

<ListView ... PreviewMouseLeftButtonUp="listView_Click"> ...
Run Code Online (Sandbox Code Playgroud)

代码背后:

private void listView_Click(object sender, RoutedEventArgs e)
{
    var item = (sender as ListView).SelectedItem;
    if (item != null)
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这不是正确的答案,因为用户可以单击ListView中的空白区域,如果有当前选定的项目,则将触发该操作,就像用户单击所选项目一样. (6认同)
  • @Rogala 代码隐藏本身没有任何问题。您的业​​务逻辑不应依赖于您的应用程序代码,您的应用程序代码不应依赖于您的 UI 代码。如果您的业务逻辑适用于任何应用程序代码,并且您的应用程序代码适用于任何 UI,那么您已经实现了 MVVM。在不考虑上下文的​​情况下应用绝对的、人为的限制本质上限制了代码质量。有些 UI 行为没有 C# 代码是不可能的,调用 ViewModel.SuperNeatCommand.Execute(param) 与绑定到它完全相同。附加属性在这里工作得很好,但它并不总是最好的,因为 YAGNI。 (3认同)
  • @Daniel 实际上,背后代码的内在错误在于倾向于向其添加业务逻辑,而不是在 VM 或服务等业务层中编写该业务逻辑。我建议不要使用后面的代码,因为它很容易弄脏水。使用诸如 http://www.mvvmlight.net/ 之类的东西,您可以创建一个可以在其中存储属性和方法的 VM,这样您就可以在 XAML 中绑定到它们。另一个主要好处是您可以将您的项目交给可以在 Blend 中连接对象的 UX 人员。 (2认同)
  • @Rogala 你刚刚证实了我所说的话。未能将视图与视图模型分开是程序员的缺陷,而不是代码隐藏所固有的。在视图中只编写视图代码与在 Person 类中只编写 Person 代码一样容易。如果你可以信任一个程序员来做一个,你可以信任他们做另一个假设他们正确理解 MVVM。如果你不能,那你就有更大的问题。我同意在 XAML 使其冗余的任何地方都应该避免代码隐藏,但代码隐藏不会窃取我们的灵魂。有很多关于绝对的明智的说法。 (2认同)

Rog*_*ala 16

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

完成所有操作后,您的XAML将看起来像谢尔盖建议的那样.我会避免在你的视图中使用后面的代码.我将避免在这个答案中编写代码,因为那里有大量的例子.

这是一个: 如何将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;
Run Code Online (Sandbox Code Playgroud)

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

/// <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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在创建你的viewmodel:

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); }
    }        
}
Run Code Online (Sandbox Code Playgroud)

在viewmodel中,添加relaycommand.请注意,我做了这个异步并让它传递参数.

    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);
    }
Run Code Online (Sandbox Code Playgroud)

在您查看的代码中,为您的viewmodel创建一个属性,并将视图的datacontext设置为viewmodel(请注意,还有其他方法可以执行此操作,但我尝试将此作为一个简单的示例.)

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

        MyViewModel = new MainWindowViewModel();
        this.DataContext = MyViewModel;
    }
}
Run Code Online (Sandbox Code Playgroud)

在您的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">
Run Code Online (Sandbox Code Playgroud)

我添加了"i"和"Custom".

这是ListView:

<ListView 
        Grid.Row="0" 
        Grid.Column="0" 
        HorizontalContentAlignment="Stretch"
        ItemsSource="{Binding ModelsCollection}"
        ItemTemplate="{DynamicResource BasicModelDataTemplate}">
    </ListView>
Run Code Online (Sandbox Code Playgroud)

这是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>
Run Code Online (Sandbox Code Playgroud)

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

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

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

〜干杯


小智 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">
    ...
Run Code Online (Sandbox Code Playgroud)