Thursday, December 14, 2006

ListView.ItemActivated event in WPF

A while ago I was looking [^] for a WPF equivalent to Windows Forms' ListView.ItemActivate [^] event. It seems that there is none. Whilst I am completely baffled by this seemingly gross oversight, I have a looming deadline to meet. Therefore, I have had to concoct my own solution.

My implementation subclasses the ListView control and adds an ItemActivated event, as well as an implementation of ICommandSource. The end result is that you can easily execute code or fire a WPF command whenever an item in the control is activated. Users can activate items in one of two ways: by double-clicking the item, or by selecting it and pressing the [Enter] key.

Without further ado, here it the pertinent code:

public class ListView : System.Windows.Controls.ListView, ICommandSource { /// <summary> /// Identifies the <see cref="Command"/> property. /// </summary> public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ListView)); /// <summary> /// Identifies the <see cref="CommandParameter"/> property. /// </summary> public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(ListView), new FrameworkPropertyMetadata(null)); /// <summary> /// Identifies the <see cref="CommandTarget"/> property. /// </summary> public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(ListView), new FrameworkPropertyMetadata(null)); /// <summary> /// Identifies the <see cref="ItemActivated"/> event. /// </summary> public static readonly RoutedEvent ItemActivatedEvent = EventManager.RegisterRoutedEvent("ItemActivated", RoutingStrategy.Bubble, typeof(EventHandler<ItemActivatedEventArgs>), typeof(ListView)); /// <summary> /// Gets or sets the <see cref="ICommand"/> to execute whenever an item is activated. /// </summary> public ICommand Command { get { return GetValue(CommandProperty) as ICommand; } set { SetValue(CommandProperty, value); } } /// <summary> /// Gets or sets the parameter to be passed to the executed <see cref="Command"/>. /// </summary> public object CommandParameter { get { return GetValue(CommandParameterProperty); } set { SetValue(CommandParameterProperty, value); } } /// <summary> /// Gets or sets the element on which to raise the specified <see cref="Command"/>. /// </summary> public IInputElement CommandTarget { get { return GetValue(CommandTargetProperty) as IInputElement; } set { SetValue(CommandTargetProperty, value); } } /// <summary> /// Occurs whenever an item in this <c>ListView</c> is activated. /// </summary> public event EventHandler<ItemActivatedEventArgs> ItemActivated { add { AddHandler(ItemActivatedEvent, value); } remove { RemoveHandler(ItemActivatedEvent, value); } } static ListView() { //register a handler for any double-clicks on ListViewItems EventManager.RegisterClassHandler(typeof(ListViewItem), ListViewItem.MouseDoubleClickEvent, new MouseButtonEventHandler(MouseDoubleClickHandler)); } protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); //hitting enter activates an item too if ((e.Key == Key.Enter) && (SelectedItem != null)) { OnItemActivated(SelectedItem); } } private void OnItemActivated(object item) { RaiseEvent(new ItemActivatedEventArgs(ItemActivatedEvent, item)); //execute the command if there is one if (Command != null) { RoutedCommand routedCommand = Command as RoutedCommand; if (routedCommand != null) { routedCommand.Execute(CommandParameter, CommandTarget); } else { Command.Execute(CommandParameter); } } } private static void MouseDoubleClickHandler(object sender, MouseEventArgs e) { ListViewItem listViewItem = sender as ListViewItem; Debug.Assert(listViewItem != null); ListView listView = FindListViewForItem(listViewItem); if (listView != null) { listView.OnItemActivated(listViewItem.Content); } } private static ListView FindListViewForItem(ListViewItem listViewItem) { DependencyObject parent = VisualTreeHelper.GetParent(listViewItem); while (parent != null) { if (parent is ListView) { return parent as ListView; } parent = VisualTreeHelper.GetParent(parent); } return null; }

It's a shame that the ItemsControl class does not itself contain the concept and implementation for item activation. Alas, maybe in a future version of WPF. Meanwhile, you can apply this same technique to any ItemsControl to add support for item activation.

You can download a sample solution here [^].



Sebastien Lambla said...

Thought you'd like to see a more general approach to this problem:


Rafael said...

Hi Kent,

Why Add command in context menu becomes enabled only after selecting a list item?