Подтвердить что ты не робот

WPF ListView - обнаружение, когда выбран выбранный элемент

Я использую элемент управления ListView WPF, который отображает список элементов данных.

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

Я пытаюсь получить поведение, подобное событию ListView.SelectionChanged, только я хочу также определить, будет ли выбран текущий выбранный элемент. Событие SelectionChanged не срабатывает, если один и тот же элемент снова щелкнут (очевидно).

Какой был бы лучший (самый чистый) способ приблизиться к этому?

4b9b3361

Ответ 1

Используйте свойство 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>

Code-за:

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

Ответ 2

Вы можете обработать событие 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)
    {
        ...
    }
}

Ответ 3

Все это большие предложения, но если бы я был вами, я бы сделал это в вашей модели. В вашей модели просмотра вы можете создать команду ретрансляции, которую затем можно привязать к событию клика в шаблоне элемента. Чтобы определить, был ли выбран тот же элемент, вы можете сохранить ссылку на выбранный элемент в своей модели просмотра. Мне нравится использовать MVVM Light для обработки привязки. Это упростит ваш проект в будущем и позволит вам установить привязку в Blend.

Когда все будет сказано и сделано, ваш XAML будет выглядеть так, как предложил Сергей. Я бы не стал использовать код в вашем представлении. Я собираюсь избежать написания кода в этом ответе, потому что есть масса примеров.

Вот один из них: Как использовать RelayCommand с инфраструктурой MVVM Light

Если вам нужен пример, прокомментируйте, и я добавлю его.

~ Приветствия

Я сказал, что не буду делать пример, но я. Здесь вы идете.

1) В вашем проекте добавьте только светлые библиотеки MVVM.

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

В вашей модели просмотра добавьте команду 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);
    }

В коде, который вы видите, создайте свойство для вашей модели viewmodel и установите datacontext для вашего представления в viewmodel (учтите, что есть другие способы сделать это, но я пытаюсь сделать это простым примером. )

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

Я добавил "i" и "Custom".

Вот список ListView:

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

Вот элемент ItemTemplate для 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>

Запустите приложение и проверьте окно вывода. Вы можете использовать конвертер для обработки стиля выбранного элемента.

Это может показаться действительно сложным, но это облегчает жизнь в будущем, когда вам нужно отделить свое представление от ViewModel (например, разработать ViewModel для нескольких платформ.) Кроме того, это облегчает работу в Blend 10x. После того как вы разработаете ViewModel, вы можете передать его дизайнеру, который может сделать его очень эффектным:). MVVM Light добавляет некоторые функции, чтобы Blend распознал вашу ViewModel. По большей части вы можете сделать практически все, что хотите, в ViewModel, чтобы повлиять на представление.

Если кто-нибудь прочтет это, я надеюсь, вы найдете это полезным. Если у вас есть вопросы, пожалуйста, дайте мне знать. В этом примере я использовал MVVM Light, но вы можете сделать это без MVVM Light.

~ Приветствия

Ответ 4

Вы можете обработать щелчок по элементу вида списка следующим образом:

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

Ответ 5

Я бы также предложил отменить выбор элемента после его нажатия и использовать событие 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);
    }
}

Ответ 6

Я не мог заставить принятый ответ работать так, как я хотел (см. Комментарий Фарруха).

Я придумал немного другое решение, которое также кажется более естественным, потому что оно выбирает элемент при нажатии кнопки мыши, а затем вы можете реагировать на него, когда кнопка мыши отпущена:

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