How do you bind a command to a ListViewItem?

Jun 12, 2008 at 1:02 AM
Okay, maybe not a question for Composite WPF and more of a general WPF question, but I've found this is the place where most are using the DataTemplate method and thought someone else might have solved this.

If I have a DataTemplate in a resource file, how in the world do you bind the double click to either the list box itself or the list box item?

First I thought I could do something like

            <ListBox ItemsSource="{Binding Path=Items}">
                <ListBox.InputBindings>
                    <MouseBinding
                        Command="{x:Static v:ModuleCommands.ItemDoubleClickedCommand}"
                        MouseAction="LeftDoubleClick"
                        />
                </ListBox.InputBindings>
            </ListBox>

But the event doesn't get fired when I double click an item, just the control itself.

Then I thought about creating a data template for the actual item, but that doesn't work either because you can't create input bindings for a DataTemplate itself.

Now I've just discovered DataTemplate.Triggers, maybe I should be looking more towards this instead?

-Brett
Jun 12, 2008 at 5:49 PM
Okay, still not there, but this is the best I've got so far:

    <DataTemplate x:Key="xTmpl">
        <ListBoxItem Content="{Binding .}"
                     HorizontalContentAlignment="Stretch"
                     HorizontalAlignment="Stretch"
                     Background="LightBlue">
            <ListBoxItem.InputBindings>
                <MouseBinding Command="{x:Static v:ModuleCommands.ItemDoubleClickedCommand}"
                              MouseAction="LeftDoubleClick"/>
            </ListBoxItem.InputBindings>
        </ListBoxItem>
    </DataTemplate>
   
    <DataTemplate DataType="{x:Type v:ListBoxModuleItem}">
        <DockPanel>
            <ListBox ItemsSource="{Binding Items}"
                     ItemTemplate="{StaticResource xTmpl}">
            </ListBox>
        </DockPanel>
    </DataTemplate>

I do have a couple of problems however.

1). I can't bind within an input binding, I want to bind the CommandParamater to Self but that's not working.
2). My ListBoxItem isn't the width of the list box itself, is this an oversight on my behalf? Have I done something wrong there?

I tried a trick where I put a Button into the item template and used the "Command" and "CommandParameter" properties with a completely restyled button, this works, however it gets fired whenever I click once on an item, which is not the desired behaviour. Replace the <ListViewItem> in the above with the following to see what I've done:

        <Button Command="{x:Static v:ModuleCommands.ItemDoubleClickedCommand}"
                CommandParameter="{Binding .}">
            <Button.Template>
                <ControlTemplate>
                    <ContentPresenter
                        HorizontalAlignment="Left"
                        VerticalAlignment="Center"
                        Content="{Binding .}"/>
                </ControlTemplate>
            </Button.Template>
        </Button>


Can anyone tell me if I'm heading even remotely close to the right direction? I'm starting to think that DataTemplate's are a hell of a lot of work where you need to capture events or commands and it may just be much easier to stick to UserControl's. What is everyones take on this?

-Brett
Oct 13, 2008 at 8:25 AM
Hi Brett,

This problem stems from the fact that ListViewItem does not implement the ICommandSource interface (which in all honestly just plain sucks). Kent Boogaart has a solution to this issue, detailed here. It's the best solution I could find to this exact same problem.

Cheers

Justin
Oct 13, 2008 at 6:42 PM

Hello Brett,

This really is not a Prism issue, but I understand your obstacle. I resolved this issue by implementing my own custom ListBoxItem.

  public class SampleListBoxItem : ListBoxItem
  {
    public static readonly DependencyProperty MouseDoubleClickCommandProperty =
      DependencyProperty.Register("MouseDoubleClickCommand", typeof(ICommand), typeof(SampleListBoxItem));

    public static readonly DependencyProperty MouseDoubleClickCommandParameterProperty =
      DependencyProperty.Register("MouseDoubleClickCommandParameter", typeof(object), typeof(SampleListBoxItem));

    protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
    {
      base.OnMouseDoubleClick(e);

      if (MouseDoubleClickCommand != null)
        MouseDoubleClickCommand.Execute(MouseDoubleClickCommandParameter);
    }

    public ICommand MouseDoubleClickCommand
    {
      get { return (ICommand) GetValue(MouseDoubleClickCommandProperty); }
      set { SetValue(MouseDoubleClickCommandProperty, value); }
    }

    public object MouseDoubleClickCommandParameter
    {
      get { return GetValue(MouseDoubleClickCommandParameterProperty); }
      set { SetValue(MouseDoubleClickCommandParameterProperty, value); }
    }
  }

I did this so that I can create my own binding implementation. This will allow me to send a parameter value when the command is executed.

<Window
  x:Name="root"
  x:Class="SampleApp.Shell4"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:self="clr-namespace:SampleApp"
  Title="Shell4" Height="300" Width="300"
  >
  <Window.Resources>
    <DataTemplate x:Key="xTmpl">
      <self:SampleListBoxItem
        HorizontalContentAlignment="Stretch"
        HorizontalAlignment="Stretch"
        Background="LightBlue"
        Content="{Binding DisplayName}"
        MouseDoubleClickCommand="{Binding ItemDoubleClickedCommand, ElementName=root}"
        MouseDoubleClickCommandParameter="{Binding DisplayName}"
        >
      </self:SampleListBoxItem>
    </DataTemplate>
  </Window.Resources>
  <DockPanel DataContext="{Binding ElementName=root}">
    <ListBox ItemsSource="{Binding Items}" ItemTemplate="{StaticResource xTmpl}">
    </ListBox>
  </DockPanel>
</Window>

I also moved the command into the Window for ease of sampling; DelegateCommand could live anywhere as long as you have access to it. The Items property and a custom data model I will leave up to you.

  using System.Collections;
  using System.Windows;
  using System.Windows.Controls;
  using System.Windows.Input;
  using Microsoft.Practices.Composite.Wpf.Commands;

  /// <summary>
  /// Interaction logic for Shell4.xaml
  /// </summary>
  public partial class Shell4 : Window
  {
    public Shell4()
    {
      ItemDoubleClickedCommand = new DelegateCommand<object>(ExecuteCommand); // Initialize command
      InitializeComponent();
    }

    public DelegateCommand<object> ItemDoubleClickedCommand { get; set; }

    public IEnumerable Items
    {
      get { return ItemDisplayCollection.GetItems(); } // Implement items retrieval
    }

    void ExecuteCommand(object value)
    {
      var message = "Value is {0}";
      MessageBox.Show(string.Format(message, value ?? "null"));   
    }

This is just an approach, and possibly not the final implementation of a solution, but serves as a good starting point.

Hopes this helps,

Andres Olivares

Nov 27, 2008 at 3:17 PM
There is a far easier approach to address this issue that lets you bind to InputBindings directly.

Try this:

http://wpfmentor.blogspot.com/2008/11/adding-bindings-to-clr-properties-in.html