WPF ListView - detect when selected item is clicked
Use the ListView.ItemContainerStyle property to give your ListViewItems an EventSetter that will handle the PreviewMouseLeftButtonDown event. Then, in the handler, check to see if the item that was clicked is selected.
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
}
}
You can handle the ListView's PreviewMouseLeftButtonUp event. The reason not to handle the PreviewMouseLeftButtonDown event is that, by the time when you handle the event, the ListView's SelectedItem may still be null.
XAML:
<ListView ... PreviewMouseLeftButtonUp="listView_Click"> ...
Code behind:
private void listView_Click(object sender, RoutedEventArgs e)
{
var item = (sender as ListView).SelectedItem;
if (item != null)
{
...
}
}
These are all great suggestions, but if I were you, I would do this in your view model. Within your view model, you can create a relay command that you can then bind to the click event in your item template. To determine if the same item was selected, you can store a reference to your selected item in your view model. I like to use MVVM Light to handle the binding. This makes your project much easier to modify in the future, and allows you to set the binding in Blend.
When all is said and done, your XAML will look like what Sergey suggested. I would avoid using the code behind in your view. I'm going to avoid writing code in this answer, because there is a ton of examples out there.
Here is one: How to use RelayCommand with the MVVM Light framework
If you require an example, please comment, and I will add one.
~Cheers
I said I wasn't going to do an example, but I am. Here you go.
-
In your project, add MVVM Light Libraries Only.
-
Create a class for your view. Generally speaking, you have a view model for each view (view: MainWindow.xaml && viewModel: MainWindowViewModel.cs)
-
Here is the code for the very, very, very basic view model:
All included namespace (if they show up here, I am assuming you already added the reference to them. MVVM Light is in 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;
Now add a basic public class:
/// <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;
}
}
Now create your 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); }
}
}
In your viewmodel, add a relaycommand. Please note, I made this async and had it pass a parameter.
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);
}
In the code behind for you view, create a property for you viewmodel and set the datacontext for your view to the viewmodel (please note, there are other ways to do this, but I am trying to make this a simple example.)
public partial class MainWindow : Window
{
public MainWindowViewModel MyViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
MyViewModel = new MainWindowViewModel();
this.DataContext = MyViewModel;
}
}
In your XAML, you need to add some namespaces to the top of your 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">
I added "i" and "Custom."
Here is the ListView:
<ListView
Grid.Row="0"
Grid.Column="0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding ModelsCollection}"
ItemTemplate="{DynamicResource BasicModelDataTemplate}">
</ListView>
Here is the ItemTemplate for the 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>
Run your application, and check out the output window. You can use a converter to handle the styling of the selected item.
This may seem really complicated, but it makes life a lot easier down the road when you need to separate your view from your ViewModel (e.g. develop a ViewModel for multiple platforms.) Additionally, it makes working in Blend 10x easier. Once you develop your ViewModel, you can hand it over to a designer who can make it look very artsy :). MVVM Light adds some functionality to make Blend recognize your ViewModel. For the most part, you can do just about everything you want to in the ViewModel to affect the view.
If anyone reads this, I hope you find this helpful. If you have questions, please let me know. I used MVVM Light in this example, but you could do this without MVVM Light.