web-dev-qa-db-fra.com

WPF ListView - détecter quand un élément sélectionné est cliqué

J'utilise un contrôle WPF ListView qui affiche une liste d'éléments liés aux données.

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

J'essaie d'obtenir un comportement similaire à l'événement ListView.SelectionChanged. Seulement, je souhaite également détecter si l'élément sélectionné est cliqué. L'événement SelectionChanged ne se déclenche pas si le même élément est à nouveau cliqué (évidemment).

Quelle serait la meilleure façon (la plus propre) d’aborder ceci?

48
scripni

Utilisez la propriété ListView.ItemContainerStyle pour donner à votre ListViewItems un EventSetter qui gérera l'événement PreviewMouseLeftButtonDown. Ensuite, dans le gestionnaire, vérifiez si l'élément sur lequel vous avez cliqué est sélectionné.

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>

Code-behind:

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

Vous pouvez gérer l'événement PreviewMouseLeftButtonUp du ListView. La raison de ne pas gérer l'événement PreviewMouseLeftButtonDown est que, au moment où vous gérez l'événement, SelectedItem du ListView peut toujours être null. 

XAML: 

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

Code derrière: 

private void listView_Click(object sender, RoutedEventArgs e)
{
    var item = (sender as ListView).SelectedItem;
    if (item != null)
    {
        ...
    }
}
18
lznt

Ce sont toutes d'excellentes suggestions, mais si j'étais vous, je le ferais selon votre modèle. Dans votre modèle de vue, vous pouvez créer une commande de relais que vous pouvez ensuite lier à l'événement click dans votre modèle d'élément. Pour déterminer si le même élément a été sélectionné, vous pouvez stocker une référence à l'élément sélectionné dans votre modèle d'affichage. J'aime utiliser MVVM Light pour gérer la liaison. Cela rend votre projet beaucoup plus facile à modifier à l'avenir et vous permet de définir la liaison dans Blend. 

En fin de compte, votre XAML ressemblera à ce que Sergey a suggéré. Je voudrais éviter d'utiliser le code derrière à votre avis. Je vais éviter d'écrire du code dans cette réponse, car il existe une tonne d'exemples.

En voici un: Comment utiliser RelayCommand avec le framework MVVM Light

Si vous avez besoin d'un exemple, veuillez commenter, et je vais en ajouter un. 

~ A bientôt

J'ai dit que je n'allais pas faire un exemple, mais je le suis. Voici.

1) Dans votre projet, ajoutez les bibliothèques MVVM Light uniquement.

2) Créez une classe pour votre vue. En règle générale, vous disposez d'un modèle de vue pour chaque vue (vue: MainWindow.xaml && viewModel: MainWindowViewModel.cs)

3) Voici le code pour le modèle de vue très très très basique:

Tous les espaces de noms inclus (s'ils apparaissent ici, je suppose que vous y avez déjà ajouté la référence. MVVM Light est dans 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;

Maintenant, ajoutez une classe publique de base:

/// <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;
    }
}

Maintenant, créez votre modèle de vue:

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

Dans votre modèle de vue, ajoutez une commande relay. S'il vous plaît noter, j'ai fait cet async et lui ai passé un paramètre. 

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

Dans le code qui se trouve derrière la vue, créez une propriété pour votre modèle de vue et définissez le contexte de données de votre vue sur le modèle de vue (veuillez noter qu'il existe d'autres moyens de procéder, mais j'essaie d'en faire un exemple simple.)

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

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

Dans votre XAML, vous devez ajouter des espaces de noms en haut de votre code.

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

J'ai ajouté "i" et "Personnalisé".

Voici le ListView:

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

Voici le ItemTemplate pour ListView:

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

Exécutez votre application et consultez la fenêtre de sortie. Vous pouvez utiliser un convertisseur pour gérer le style de l'élément sélectionné. 

Cela peut sembler très compliqué, mais cela simplifie grandement la tâche lorsque vous devez séparer votre vue de votre ViewModel (par exemple, développer un ViewModel pour plusieurs plates-formes). De plus, cela facilite le travail dans Blend 10x. Une fois que vous développez votre ViewModel, vous pouvez le confier à un concepteur qui peut lui donner un aspect très artistique :). MVVM Light ajoute certaines fonctionnalités pour que Blend reconnaisse votre ViewModel. Pour l'essentiel, vous pouvez faire à peu près tout ce que vous voulez dans le ViewModel pour affecter la vue. 

Si quelqu'un lit ceci, j'espère que vous le trouverez utile. Si vous avez des questions, faites-le-moi savoir. J'ai utilisé MVVM Light dans cet exemple, mais vous pouvez le faire sans MVVM Light.

~ A bientôt

13
Rogala

Vous pouvez gérer cliquer sur un élément de la liste comme ceci: 

<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">
    ...
4
Sergey Sukhinin

Je suggère également de désélectionner un élément après avoir cliqué dessus et d'utiliser l'événement 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);
    }
}
0
Ivan Silkin

Je ne pouvais pas obtenir la réponse acceptée pour travailler comme je le voulais (voir le commentaire de Farrukh).

J'ai proposé une solution légèrement différente, qui semble également plus native, car elle sélectionne l'élément sur lequel le bouton de la souris est enfoncé, puis vous pouvez y réagir lorsque vous relâchez le bouton de la souris:

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>

Code derrière:

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
    }
}
0
Stacksatty