Commanding Problem on ListView Context Menu

Aug 8, 2008 at 12:18 AM
Edited Aug 21, 2008 at 1:37 PM
I apologize in advance for the amount of code included here, but I didn't I didn't have any place to link it from so I just cut and paste both the .xaml and .cs file.

Here is the problem I am having.  I have a listview from which I would like to include a context menu on. I have also added a button to ensure that the things are working the way I anticipate.  The button works exactly as I would think and hope.  The context menu does not.  The parameter being passed through is always null (which is why I check for is null in the can execute code). I don't understand why the command parameter would not contain a value as the command is bound in the context menu in the exact same way it is bound on the button.  I posted a similar question on the msdn wpf forums yesterday and thought I had resolved it this morning, but after struggling with it for the better part of the day I thought I would bring it up here in case it was a problem I was having with Composite WPF.

Please feel free to tell me if I am doing something incredibly wrong or using something in a way that is not intended.  I have to do a "Composite WPF sales pitch" to my co-workers next week which has already been pushed back a week to determine if we are going to use it on our next project or not.

Here is the code:
Windows1.xaml
<Window x:Class
="WpfApplication2.Window1"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication2"  Height="300" Width
="300">
    <Grid
>
        <Grid.RowDefinitions
>
            <RowDefinition Height
="75*"/>
            <RowDefinition Height
="25*"/>
        </Grid.RowDefinitions
>
        <ListView Grid.Row="0" x:Name="lstPersons" ItemsSource="{Binding Persons}"  IsSynchronizedWithCurrentItem="True" SelectionChanged
="lstPersons_SelectionChanged">
            <ListView.ContextMenu
>
                <ContextMenu
>
                    <MenuItem Header="Retirement" Command="{Binding RetirementCommand}" CommandParameter="{Binding ElementName=lstPersons, Path
=SelectedItem}"/>
                </ContextMenu
>
            </ListView.ContextMenu
>
            
<ListView.View
>
                <GridView
>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name
}"/>
                    <GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age
}"/>
                </GridView
>
            </ListView.View
>
        </ListView
>
        <Button Grid.Row="1" Content="Click Me" Command="{Binding RetirementCommand}" CommandParameter="{Binding ElementName=lstPersons, Path
=SelectedItem}"/>
    </Grid
>
</
Window
>


Window1.xaml.cs


using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using cal = Microsoft.Practices.Composite.Wpf.Commands;

namespace
WpfApplication2
{
    public partial class Window1 : Window
    {
        public ObservableCollection<Person> Persons { get; set; }
        public cal.DelegateCommand<Person> RetirementCommand { get; set; }

        public
Window1()
        {
            Persons =
new ObservableCollection<Person>();
            Persons.Add(
new Person() { Name = "OleBob", Age = 73 });
            Persons.Add(
new Person() { Name = "OleLou", Age = 82 }); 
            Persons.Add(
new Person() { Name = "LilJohn", Age = 20 });
            Persons.Add(
new Person() { Name = "LilJames", Age = 32 });

            RetirementCommand =
new cal.DelegateCommand<Person>(Execute, CanExecute);
            InitializeComponent();
            DataContext =
this;
        }

        public
void Execute(Person p) 
        { 
            
MessageBox.Show(((Person)p).Name); 
        }

        public
bool CanExecute(Person p)
        {
            if ((p != null) && (p is Person))
                return ((Person)p).Age >= 65;      

            return
false;
        }

        private
void lstPersons_SelectionChanged(object sender, SelectionChangedEventArgs e) 
        {
             RetirementCommand.RaiseCanExecuteChanged(); 
        }
    }

    public
class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}
Aug 8, 2008 at 9:16 PM
This is in fact a problem with the binding, that is not really coming from Composite WPF. You can see the Binding Exception is:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=lstPersons'. BindingExpression:Path=SelectedItem; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'CommandParameter' (type 'Object')

ContextMenu is a little tricky to bind correctly to another source, because the logical tree behaves differently there. I'm not sure which would be the correct way to bind to a relative source, but you can check the StockTrader Reference Implementation. The PositiongGrid.xaml binds a command parameter of a context menu item using the SelectedItem of a ListView, as in your case.
You may also want to put some objects (for example the command) in a resource to make it easier to get to.

Thanks,
Julian Dominguez
http://blogs.southworks.net/jdominguez
Aug 9, 2008 at 12:21 AM

@CodeHulk, in light of Julian's news here is a workaround:

 

public bool CanExecute(Person p)
{
      p = lstPersons.SelectedItem
as Person;  // <= ADD this line
      if ((p != null) && (p is Person))
            return ((Person)p).Age >= 65;
      return false;
}

 

Aug 9, 2008 at 3:24 AM
Thanks for the replies.

Julian: I will have to look closer at the PositionGrid.xaml.  I tried to copy what was done there into a simple example but I did not have much success.

BillKrat:  The workaround works fine and had occurred to me but the problem I had with it is that it required me to create delegate commands at the view level.  If I wanted to push that further back on the chain I can't.  It's not a big deal, but I was hoping I could reuse the same code base for all the different places I was going to use the commands.  That way if I have a save command on a person but I have multiple different ways of saving it (context menu on the lists, a details view, etc) I don't run into any duplicates for SaveAll commands.
Aug 9, 2008 at 4:31 PM

@CodeHulk "Julian: I will have to look closer at the PositionGrid.xaml.  I tried to copy what was done there into a simple example but I did not have much success."

Doesn't look like it is going to work, I was hoping it would because Googled messages/blogs suggest there isn't a way to make it work (bug in WPF)...

I updated the OrdersController to implement CanExecute and it only fires once (for all list items); in the example below the Buy list item is disabled for all items.

StockTraderRI.Modules.Position/Controllers/OrdersController.cs

        public OrdersController(IRegionManager regionManager,
                                IUnityContainer container, StockTraderRICommandProxy commandProxy,
                                IAccountPositionService accountPositionService)
        {
            _regionManager = regionManager;
            _container = container;
            _accountPositionService = accountPositionService;
            this.commandProxy = commandProxy;
            BuyCommand = new DelegateCommand<string>(OnBuyExecuted, CanExecute);
            SellCommand = new DelegateCommand<string>(OnSellExecuted);
            SubmitAllVoteOnlyCommand = new DelegateCommand<object>(null, SubmitAllCanExecute);
            OrderModels = new List<IOrderCompositePresentationModel>();
            commandProxy.SubmitAllOrdersCommand.RegisterCommand(SubmitAllVoteOnlyCommand);
           
        }

        public static bool toggleCanExecute = true;
        public bool CanExecute(string info)
        {
            toggleCanExecute = !toggleCanExecute;
            return toggleCanExecute;
        }



 

Aug 11, 2008 at 11:41 PM
Edited Aug 11, 2008 at 11:43 PM
I have managed to match the functionality but I still run into a problem if I add a CanExecute method.  If this isn't a Composite WPF problem please let me know.

In OrderController.cs change 

BuyCommand =

new DelegateCommand<string>(OnBuyExecuted);

to

 

BuyCommand =

new DelegateCommand<string>(OnBuyExecuted, OnBuyCanExecute);

and add the following method

 

 

bool OnBuyCanExecute(string parameter)
{
    if (parameter == null)
        return false;
    return parameter.EndsWith("0");
}

in PositionSummaryPresentationModel.cs add the following line to View_TickerSymbolSelected.
BuyCommand.RaiseCanExecuteChanged(); 

Add a breakpoint on the above line and on the if statement of OnBuyCanExecute.   When the application is run, you will notice that the breakpoing on View_TickerSymbolSelected is hit and has the value "STOCK0" or its argument as one would expect.  But the OnBuyCanExecute breakpoint is never hit.

Remove all breakpoints and restart the application.  When the application loads right click on the initially selected stock and buy is greyed out even though it should be selectable.  You can left click and right click on "STOCK0" and the functionality it remains greyed out.  Now left or right click on any other stock and then back  on stock0 and you will find everything works fine and as expected. 

I don't understand this behavior as it is apparrent that the SelectedItem in the positionGrid list is not null and I am manually refreshing the canexecute.  Does anybody have any thoughts?

Thanks,
~Justin

 

 

 

Aug 19, 2008 at 4:45 PM
Edited Aug 19, 2008 at 4:53 PM
Hulk,

I have struggled with this issue myself in the past. You should not have to reimplement you original code, just some minor refactoring. The main issue is that there is no relationship between the property (ListView.ContextMenu) and the owner (ListView) which makes it difficult for the property value (ContextMenu) to bind logically.

Below I illustrate changes to your binding and minor changes to your command declaration and implemetation.

The binding would work if you retrieved from the context menu via the PlacementTarget property, so that items within can bind to the default context.

//xaml

 <
ListView.ContextMenu>
    <ContextMenu 
        
xmlns:commands="clr-namespace:WpfApplication2"
        DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}"
        
>
        <
MenuItem 
            Header
="Retirement"
 
            
Command="{x:Static commands.WpfApplication2.RetirementCommand
}" 
            
CommandParameter="{Binding
 Path=SelectedItem}"/>
    </ContextMenu>
</ListView.ContextMenu>

I would suggest, for this example, to statically declare you command. I do not know if you can reference your command with simply Command="{Binding RetirementCommand}"  because the default binding source would be null or from the ContextMenu or the Window class if no context ever tunneled down. This static implementation allows for otheris in this project to reference it if they need to retire a person. The next obvious step would be to move this implementation somewhere central.

//code

public DelegateCommand<Person> RetirementCommand = new DelegateCommand<Person>(Execute, CanExecute);

static void Execute(Person p) 

    if(p != null)
        
MessageBox.Show(p.Name); 
}

static
bool CanExecute(Person p)
{
    return p != null ? p.Age >= 65 : false;
}


Additionally, I would look into a solution involving you last post. Implement a handler to your ListView.SelectionChanged event and raise the CanExecute method on your command.

static void SelectionChanged(object sender, RoutedEventArgs e

    
RetirementCommand.RaiseCnaExecuteChanged();

}


I also used this when all else fails. The drawback is that it calls your CanExecute implementation for all consumers.

Hope this helps.
Aug 20, 2008 at 3:28 PM
Hello Andres thanks for your reply.

I tried your solution however it doesn't resolve my problem.  I end up with the same problem where it doesn't work on the initially selected item.  Only when I select something else does it start registering.

~CodeHulk
Aug 20, 2008 at 7:05 PM
I must be missing something else because I am getting the same error when not binding to a list or with a context menu.  Maybe this will be an easier example.

Controller.cs

 

public class Controller
{
    public DelegateCommand<Person> RetirementCommand = new DelegateCommand<Person>(OnRetirementExecuted, OnRetirementCanExecute);
    
    private
static bool OnRetirementCanExecute(Person arg) { return (arg != null) && (arg.Age >= 65); }

    private
static void OnRetirementExecuted(Person obj) { MessageBox.Show(obj.Name + " retired."); }
}


PresentationModel.cs


 

 

public class PresentationModel
{
    public DelegateCommand<Person> RetirementCommand { get; set; }
    public Person Person { get; set; }
    public
Window1 View { get; set; }
    
    private
readonly Controller _controller = new Controller();

    public
PresentationModel(Window1 view)
    {
        Person =
new Person() {Age = 69, Name = "Jacklyn"};
        View = view;
        View.Model =
this;
        RetirementCommand = _controller.RetirementCommand;
    }
}

Window1.xaml


 

 

 

<StackPanel>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Grid.Column="0" Grid.Row="0" Content="Name"/>
        <Label Grid.Column="0" Grid.Row="1" Content="Age"/>
        <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Path=Person.Name}" TextChanged="TextBox_TextChanged"/>
        <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Path=Person.Age}" TextChanged="TextBox_TextChanged"/>
    </Grid>
    <Button Command="{Binding RetirementCommand}" CommandParameter="{Binding Person}" Content="Retire"/>
</StackPanel>

Window1.cs


 

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        Model =
new PresentationModel(this);
    }

    public
PresentationModel Model
    {
        get { return DataContext as PresentationModel; }
        set { DataContext = value; }
    }

    private
void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        Model.RetirementCommand.RaiseCanExecuteChanged();
    }
}


Person is just a class with a public getter setter string property Name and a public getter setter int property Age.  When the application first loads the button is deactivated and by putting a breakpoint in the can execute method it shows that the value is null initially despite the fact that the values successfully bind to the text boxes.  If you type in either of the textboxes the button immediately activates and the same breakpoint shows that the person object present in the can execute method as expected.  I don't understand why null is passed in the first time through.  I tried adding a RetirementCommand the end of PresentationModel's constructor, the end of Window1's constructor and to the end of the setter of Model in Window1.  The behavior seems very similar to the problem I am having with the context menu's and the lists so I think if I can resolve one of the the solution will present itself for both.

Thanks,
~Justin

 

Aug 21, 2008 at 3:39 AM
@Hulk, I have a different solution to your original code :)

I made the following change to the XAML:

<ContextMenu>
     <MenuItem x:Name="cmMenuItem" Header="Retirement" Command="{Binding RetirementCommand}" />
</ContextMenu>

BTW, can you add quotes to your namespaces for your XAML code (first post) - so it'll be a cut, paste and compile for future users?

Code behind was updated as follows (solution in SelectedChange delegate):

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using cal = Microsoft.Practices.Composite.Wpf.Commands;
using Microsoft.Practices.Composite.Wpf.Commands;
using System.Windows.Data;

namespace WpfApplication2
{
    public partial class Window1 : Window
    {
        public ObservableCollection<Person> Persons { get; set; }
        public DelegateCommand<Person> RetirementCommand { get; set; }

        public Window1()
        {
            Persons = new ObservableCollection<Person>();
            Persons.Add(new Person() { Name = "OleBob", Age = 73 });
            Persons.Add(new Person() { Name = "OleLou", Age = 82 });
            Persons.Add(new Person() { Name = "LilJohn", Age = 20 });
            Persons.Add(new Person() { Name = "LilJames", Age = 32 });

            InitializeComponent();

            RetirementCommand = new DelegateCommand<Person>(Execute, CanExecute);
            DataContext = this;

            lstPersons.SelectionChanged += delegate {
                cmMenuItem.CommandParameter = lstPersons.SelectedValue;
                RetirementCommand.RaiseCanExecuteChanged();
            };

        }

        public void Execute(Person p)
        {
            MessageBox.Show(((Person)p).Name);
        }

        public bool CanExecute(object p)
        {
            if ((p != null) && (p is Person))
                return ((Person)p).Age >= 65;

            return false;
        }
    }

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

 

I'm reading/studying the DataBinding section of the WPF Unleashed book, by Adam Nathan; I have some ideas cooking for a possible XAML solution.  To be cont...

Aug 21, 2008 at 1:44 PM
Edited Aug 21, 2008 at 2:26 PM

Hey Hulk,

Sorry about the late reply. In my original post to your issue I didn't get a chance to test it, which is a horrible statement, so my apologies, but I noticed that the syntax in my example was not accurate. I did manage to resolve you issue with the initial item selection. I will first show the code and then describe what I did. I stayed with your original implementation.

[XAML]

<Window
  x:Class="WpfApplication1.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:WpfApplication1"  Height="300" Width="300">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="75*"/>
      <RowDefinition Height="25*"/>
    </Grid.RowDefinitions>
    <ListView Grid.Row="0" x:Name="lstPersons" ItemsSource="{Binding Persons}"  IsSynchronizedWithCurrentItem="True" SelectionChanged="lstPersons_SelectionChanged">
      <ListView.ContextMenu>
        <ContextMenu Opened="ContextMenu_Opened"
        DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}"
        >
          <MenuItem
            Header="Retirement"
            Command="{x:Static local:Window1.RetirementCommand}"
            CommandParameter="{Binding Path=SelectedItem}"/>
        </ContextMenu>
      </ListView.ContextMenu>
      <ListView.View>
        <GridView>
          <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
          <GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}"/>
        </GridView>
      </ListView.View>
    </ListView>
    <Button Grid.Row="1" Content="Click Me" 
            Command="{x:Static local:Window1.RetirementCommand}" 
            CommandParameter="{Binding ElementName=lstPersons, Path=SelectedItem}"/>
  </Grid>
</Window>

[CODE]

namespace WpfApplication1
{
  using System.Collections.ObjectModel;
  using System.Windows;
  using System.Windows.Controls;
  using Microsoft.Practices.Composite.Wpf.Commands;

  /// <summary>
  /// Interaction logic for Window1.xaml
  /// </summary>
  public partial class Window1: Window
  {
    public ObservableCollection<Person> Persons { get; set; }
    public static DelegateCommand<Person> RetirementCommand = new DelegateCommand<Person>(Execute, CanExecute);

    public Window1()
    {
      Persons = new ObservableCollection<Person>();
      Persons.Add(new Person() { Name = "OleBob", Age = 73 });
      Persons.Add(new Person() { Name = "OleLou", Age = 82 });
      Persons.Add(new Person() { Name = "LilJohn", Age = 20 });
      Persons.Add(new Person() { Name = "LilJames", Age = 32 });

      //RetirementCommand = new DelegateCommand<Person>(Execute, CanExecute);
      InitializeComponent();
      DataContext = this;
    }

    static void Execute(Person p)
    {
      if(p != null)
        MessageBox.Show(p.Name);
    }

    static bool CanExecute(Person p)
    {
      return p != null ? p.Age >= 65 : false;
    }

    private void lstPersons_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
      RetirementCommand.RaiseCanExecuteChanged();
    }

    private void ContextMenu_Opened(object sender, RoutedEventArgs e)
    {
      RetirementCommand.RaiseCanExecuteChanged();
    }
  }

  public class Person
  {
    public string Name { get; set; }
    public int Age { get; set; }
  }
}

I did a few things:
1. Corrected the syntax from my first posting, and saw that it still did not resolve you original problem.
2. I simply added the same RaiseCanExecuteChanged method call on the Opened event handler of the context menu. At this point it has the correct binding to the ListView, and the MenuItem completes the binding by binding to the SelectedItem.
3. I added the same Command binding from the menu item to the button.
4. I tested it.

Aug 21, 2008 at 1:46 PM

Thanks for the response Bill.

I'm not so concerned about work arounds as the delegates in the DelegateCommand can always have the selectedItem added to it.  I'm more so curious as to why it happens as it appears to me that everything is set up right especially since it works after one of the properties are changed.

I'm going to try to keep working on it as it is now more a personal mission than a necessity :)

Aug 21, 2008 at 2:42 PM
Hey Andres,

I must have been typing my reply during when you submitted yours.  I have tried the solution and it does work, I am still very curious as to why there needs to be a work around for this as it works on every effort after the first.

Thanks for the time spent on this guys.  I certainly appreciate it.

~Justin
Aug 21, 2008 at 4:56 PM
Hulk,

Another thing: as far as your "Maybe this will be an easier example." solution, I also saw the problem here and provide an alternative.

[XAML]

<Window
  x:Class="WpfApplication2.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:WpfApplication2" 
  Title="Window1" Height="300" Width="300"
  Loaded="Model_Changed"
  >
  <StackPanel>
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
      </Grid.RowDefinitions>
      <Label Grid.Column="0" Grid.Row="0" Content="Name"/>
      <Label Grid.Column="0" Grid.Row="1" Content="Age"/>
      <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" TextChanged="Model_Changed"/>
      <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Path=Age, UpdateSourceTrigger=PropertyChanged}" TextChanged="Model_Changed"/>
    </Grid>
    <Button Command="{x:Static local:Controller.RetirementCommand}"
            CommandParameter="{Binding}"
            Content="Retire"/>
  </StackPanel>

</Window>

In the UI I removed the need for the Model property since I could just just the DataContext from the Window. I also added a handler to the Window's Loaded event to call our RaiseCanExecuteChanged method (this will handle an inital check), and I upgraded your textbox bindings to be more responsive on change. The TextChanged handler is the same as the Window's Loaded event handler. lastly, the Button makes the same static binding reference as the MenuItem.

[CODE - Window1]

namespace WpfApplication2
{
  using System.Windows;

  /// <summary>
  /// Interaction logic for Window1.xaml
  /// </summary>
  public partial class Window1
  {
    static Window1()
    {
      DataContextProperty.OverrideMetadata(typeof(Window1), 
        new FrameworkPropertyMetadata(null, OnDataContextChanged));
    }

    public Window1()
    {
      InitializeComponent();
      DataContext = new Person {Age = 69, Name = "Jacklyn"};
    }

    void Model_Changed(object sender, RoutedEventArgs e)
    {
      Controller.RetirementCommand.RaiseCanExecuteChanged();
    }

    static void OnDataContextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      Controller.RetirementCommand.RaiseCanExecuteChanged();
    }
  }
}

In the code behind, I minimized a lot since the need for a PresentationModel was, I felt, unnecessary. The things I did here were:
1. Assign my model to the DataContext property
2. Handle it's change through a Callback declared in the static constructor for the DataContextProperty

[CODE - Infrastructure]

namespace WpfApplication2
{
  using System.Windows;
  using Microsoft.Practices.Composite.Wpf.Commands;

  // Controller
  public static class Controller
  {
    public static DelegateCommand<Person> RetirementCommand =
      new DelegateCommand<Person>(OnRetirementExecuted, OnRetirementCanExecute);

    static bool OnRetirementCanExecute(Person arg)
    {
      return (arg != null) && (arg.Age >= 65);
    }

    static void OnRetirementExecuted(Person obj)
    {
      MessageBox.Show(obj.Name + " retired.");
    }
  }

  // Person
  public class Person
  {
    public string Name { get; set; }
    public int Age { get; set; }
  }
}

Finally, I added the model and the command controller into the same class file. I made the controller and the DelegateCommand static.

The problem is reacting to change. Unfortunately, you must anticipate when this would be, for both use cases decribed in this post. The impression is that the DelegateCommand implemetation would do this some how...or even the framwork, but it does not in some cases as we see.

Hope this helps.

Feb 13, 2014 at 6:29 PM
@Tinkerfa - And just how exactly is your article on a WinForms ListView relevant to WPF in general and to this question specifically? Your article makes no mention at all of ContextMenu. Pretty sad that you are spamming 5 year old posts.