0

When I have:

<UserControl.Resources>

    <Style x:Key="itemstyle" TargetType="{x:Type ListViewItem}">
        <EventSetter Event="MouseDoubleClick" Handler="HandleDoubleClick" />
        <Setter Property="HorizontalContentAlignment" Value="Center" />
    </Style>
</UserControl.Resources>
<Grid>
    <ListView  ItemContainerStyle="itemstyle"  Name="listView1" >
        <ListView.View>
             <GridView>
                  ... etc

Code behind

    protected void HandleDoubleClick(object sender, MouseButtonEventArgs e)
    {
        T item = (T)(((ListViewItem)sender).Content);   
        // item is the row item that was double clicked on                    
    }

Everything works great.


Now I need to do the same thing on code behind. This is what I have worked out:

 public Constructor(){

    listview.AddHandler(
        ListViewItem.MouseDoubleClickEvent, 
        new MouseButtonEventHandler(HandleDoubleClick)
    );
 }


 protected void HandleDoubleClick(object sender, MouseButtonEventArgs e)
 {
        T item = (T)(((ListViewItem)sender).Content);       
        // error cause the sender is the listview                           
 }

that event fires when I double click any part of the listview not just the listviewItem. Also I expect sender to be ListViewItem and it is not. the sender is actually the listview. Things that I have tried:

1) Because the sender is the listview I tried creating the event as:

         listview.AddHandler(
            // I use PreviewMouseDoubleClickEvent instead of MouseDoubleClickEvent because of the way the events bubles
            ListViewItem.PreviewMouseDoubleClickEvent,   
            new MouseButtonEventHandler(HandleDoubleClick)
         );

I get the same error the sender is the listview

2) Instead of doing:

  T item = (T)((ListViewItem)sender).Content;

I do:

  T item = (T)(listview.selectedItem);

the problem with this is that if the user double clicks anything on the listview that is not the row it will return the current selected item

why is my code not working? what am I doing wrong?

Tono Nam
  • 34,064
  • 78
  • 298
  • 470

4 Answers4

3

Attached Behavior Pattern


I think you should use attached behavior because

  1. MVVM ♥ Attached Behavior
  2. It is reusable: once you create the attached behavior and the commands for a double click, you can attach it to any other control type (so you could use the same code to double click an image etc)
  3. Abstracts the dirty implementation so the xaml and the view model are both much more legible
  4. The way they work is awesome, and I love using clever code.

Heres what you do:

enter image description here


Create the ListViewItemDoubleClickCommandBehavior

As you can see from the above diagram, this CommandBehavior class links the command to the control.

  • In the case above its for a buttonbase, but we are gonna make a ListViewItem one

  • You are going to need prism for this one, as the CommandBehaviorBase, which this inherits from, is part of that library. You can get it all here (but for this example you only need to Prism.Commands dll)

  • Naming convention says you should name this CommandBehavior in the format

     [X][Y]CommandBehavior
    

    X - The name of the control to which we are binding the command (in this case ListViewItem)
    Y - The Action the user performs that will cause the command to happen (in this case DoubleClick)

Heres the code:

    public class ListViewItemDoubleClickCommandBehaviour : CommandBehaviorBase<ListViewItem>
    {
        public ListViewItemDoubleClickCommandBehaviour(ListViewItem controlToWhomWeBind)
        : base(controlToWhomWeBind)
        {
            controlToWhomWeBind.MouseDoubleClick += OnDoubleClick;
        }

        private void OnDoubleClick(object sender, System.Windows.RoutedEventArgs e)
        {

            ExecuteCommand();

        }
    }

Create a DoubleClick Static class

This class will house all the double click command stuff.

  • Its called doubleclick because it represents the actual act of double clicking. Each other kind of action (say you wanted a command for button down in a textbox) will have its own class with its command, commandparameter and behavior can we can then access.

  • The commands are dependency properties of type ICommand (I'm assuming you have implemented this interface, because you kinda need it for MVVM)

  • One dependency property for the command itself and one for the parameters the command needs (in this case you will probably use the selected item as a parameter)

  • This class has an instance of the ListViewItemDoubleClickCommandBehavior as a dependency property. This is how the link is created between the command you bind to your control and the double click event in the ListViewItemDoubleClickCommandBehaviour. We use some of the CommandBehaviorBase's magic methods to create the behavior and pass it the command to execute.

  • The OnSetCommand and OnSetCommandParameter callbacks are used to wire the Behavior to the command. Every time the command changes, we set that as the new command for the behavior to run. These callbacks are registered to the DependencyProperties in the PropertyMetadata part of the constructor. These get fired whenever the dependency property is changed.

Here is that class's code:

public static class DoubleClick
{

    public static readonly DependencyProperty CommandProperty =
                          DependencyProperty.RegisterAttached(
                                  "Command",
                                  typeof(ICommand),
                                  typeof(DoubleClick),
                                  new PropertyMetadata(OnSetCommandCallback));

    public static readonly DependencyProperty CommandParameterProperty =
                            DependencyProperty.RegisterAttached(
                                    "CommandParameter",
                                    typeof(object),
                                    typeof(DoubleClick),
                                    new PropertyMetadata(OnSetCommandParameterCallback));



    private static readonly DependencyProperty DoubleClickCommandBehaviorProperty =
                                  DependencyProperty.RegisterAttached(
                                          "DoubleClickCommandBehavior",
                                          typeof(ListViewItemDoubleClickCommandBehaviour),
                                          typeof(DoubleClick),
                                          null);


    public static void SetCommand(ListViewItem controlToWhomWeBind, ICommand value)
    {
        controlToWhomWeBind.SetValue(CommandProperty, value);
    }

    public static ICommand GetCommand(ListViewItem controlToWhomWeBind)
    {
         return (ICommand)controlToWhomWeBind.GetValue(CommandProperty);
    }

    public static void SetCommandParameter(ListViewItem controlToWhomWeBind, ICommand value)
    {
        controlToWhomWeBind.SetValue(CommandParameterProperty, value);
    }

    public static ICommand GetCommandParameter(ListViewItem controlToWhomWeBind)
    {
        return (ICommand)controlToWhomWeBind.GetValue(CommandParameterProperty);
    }



    private static void OnSetCommandCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
    {
        ListViewItem controlToWhomWeBind= dependencyObject as ListViewItem;
        if (controlToWhomWeBind!= null)
        {
            ListViewItemDoubleClickCommandBehaviour behavior = GetOrCreateBehavior(controlToWhomWeBind);
            behavior.Command = e.NewValue as ICommand;
        }
    }

    private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        ListViewItem controlToWhomWeBind= dependencyObject as ListViewItem;
        if (controlToWhomWeBind!= null)
        {
            ListViewItemDoubleClickCommandBehaviour behavior = GetOrCreateBehavior(controlToWhomWeBind);
            behavior.CommandParameter = e.NewValue;
        }
    }

    private static ListViewItemDoubleClickCommandBehaviour GetOrCreateBehavior(
                                                               ListViewItem controlToWhomWeBind)
    {
        ListViewItemDoubleClickCommandBehaviour behavior =
            controlToWhomWeBind.GetValue(DoubleClickCommandBehaviorProperty) as
                 ListViewItemDoubleClickCommandBehaviour;
        if (behavior == null)
        {
            behavior = new ListViewItemDoubleClickCommandBehaviour (controlToWhomWeBind);
            controlToWhomWeBind.SetValue(DoubleClickCommandBehaviorProperty, behavior);
        }

        return behavior;
    }
}

Note:

I know this looks like alot of complex code, but its a simple pattern once you use it often enough, and it abstracts all the complexity out of your view model, so that the binding of the command to the control looks remarkably simple.


Create the command in your viewmodel and bind to it in the xaml

Now that the grunt work is over, we do the easy part.

Creating the Command

I assume that you are familiar with commands, you create the command as you always do:

    public ICommand DisplaySelectedItemCmd { get; protected set; }

    //This method goes in your constructor
    private void InitializeCommands()
    {
        //Initializes the command
        this.DisplaySelectedItemCmd = new RelayCommand(
            (param) =>
            {
                this.DisplaySelectedItem((object)param);
            },
            (param) => { return this.CanDisplaySelectedItem; }
        );
    }      
    //The parameter should be your listview's selected item. I have no idea what type it is so I made it an object
    public void DisplaySelectedPolicy(object selectedListViewItem)
    {
        //Code to perform when item is double clicked
    }

    private bool CanDisplaySelectedPolicy
    {
        get
        {
            return true; //Change this bool if you have any reason to disable the double clicking, as this bool basically is linked to the double click command firing.
        }
    }

Creating the binding in xaml

And now for the beautiful part. First add the xml namespace:

 xmlns:commands="clr-namespace:CommandHandling" 

and then bind on your ListViewItem

 <Style TargetType="{x:Type ListViewItem}" x:Key="{x:Type ListViewItem}" >
       <Setter Property="commands:DoubleClick.Command" Value="{Binding Path=bleh}"/>
 </Style>

and done.

If this doesn't work let me know. (And anyone reading this in the future, you can ask me for help if you like)

u_u

Jason Ridge
  • 1,868
  • 15
  • 27
0

Figure it out!! I am sure it should be the same with the double click...

In xaml I have:

<ListView IsSynchronizedWithCurrentItem="True" Name="listView" Margin="32,158,66,0" VerticalAlignment="Top">
        <ListView.ItemContainerStyle>                 
             <Style TargetType="ListViewItem">
                <EventSetter Event="PreviewMouseUp" Handler="itemClicked"></EventSetter>
            </Style>                 
        </ListView.ItemContainerStyle>            
        <ListView.View>
        ... etc

and I can create the same thing with c# on code behind as:

    EventSetter ev = new EventSetter();
    ev.Event = ListViewItem.PreviewMouseUpEvent;
    ev.Handler = new MouseButtonEventHandler(itemClicked);

    Style myStyle = new Style();
    myStyle.TargetType = typeof(ListViewItem);

    myStyle.Setters.Add(ev);


    listView.ItemContainerStyle = myStyle;

....

void itemClicked(object sender, MouseButtonEventArgs e)
{
     // item was licked in listview implement behavior in here
}
Tono Nam
  • 34,064
  • 78
  • 298
  • 470
  • Would this really work? The problem is that the ListViewItem doesn't have a double click event built in. That's why you have to create the behaviour yourself. Your code here just looks like it is subscribing to an event that is already created. – Jason Ridge Apr 16 '12 at 05:54
0

In xaml you attach the event handler to the ListViewItem while in code behind you attach it to the ListView itself. That is why you get different behaviour. If you want to do the same in code behind you will have to loop al items in your items collection and bind the DoubleClick event of each one to your handler.

If there is no real reason to do this in code behind I would go for the xaml approach which fits the MVVM pattern better where you try to keep as few code as possible in the code behind.

GuyVdN
  • 690
  • 4
  • 14
0

Don't see why you are doing this in code behind unless you are wanting named content in your UserControl so you are changing it to a custom control. Try repeating your xaml code in code behind, ie create a style for ListViewItem and setting it as ItemContainerStyle for your listview.

Lap
  • 254
  • 2
  • 9