WPF ListView - обнаружение, когда выбран выбранный элемент
Я использую элемент управления ListView WPF, который отображает список элементов данных.
<ListView ItemsSource={Binding MyItems}>
<ListView.View>
<GridView>
<!-- declare a GridViewColumn for each property -->
</GridView>
</ListView.View>
</ListView>
Я пытаюсь получить поведение, подобное событию ListView.SelectionChanged
, только я хочу также определить, будет ли выбран текущий выбранный элемент. Событие SelectionChanged
не срабатывает, если один и тот же элемент снова щелкнут (очевидно).
Какой был бы лучший (самый чистый) способ приблизиться к этому?
Ответы
Ответ 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
}
}