Bind SelectedItem in a ListBox to CommandParameter

2019-08-16 16:26发布

问题:

Although this question is very similar to others posted, it has a few twist! I have an ObservableCollection of items in a ListBox and use a DataTemplate to display some of the collection members. However, I have a ContextMenu defined so I can execute DelegateCommands defined in my ViewModel using the Command parameter.

All my Bindings are working up to here, except that I need to provide an argument for the Command in the ViewModel using CommandParameter of the item selected in the ListBox so that the command knows which item to act upon. But this is not possible because I changed the ContextMenu DataContext so I can get at the DelegateCommands in my ViewModel.

I get the following error: System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=instrumentListView'. BindingExpression:Path=SelectedItem;

The Command in the ViewModel is called as I desire, but its argument is always null.

I have also tried to Bind to items inside the collection, but they are not visible because I had to switch my DataContext.

The following is abbreviated XAML file:

            <ListBox x:Name="instrumentListView" ItemsSource="{Binding Instruments}" >              
                <ListBox.ItemTemplate>
                    <DataTemplate>
                       <StackPanel x:Name="instrumentStackPanel" HorizontalAlignment="Left" Orientation="Horizontal" Height="30" UseLayoutRounding="True" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ListBox}}">                            
                        <Image Source="{Binding InstrumentIcon}" Margin="0,0,10,0"></Image>
                        <Label Content="{Binding Instrument}"></Label>
                        <StackPanel.ContextMenu >
                            <ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
                                <MenuItem Header="Add Instrument" Command="{Binding Path=AddInstrumentToTest}" CommandParameter="{Binding Instrument}"/>
                                <MenuItem Header="Remove Instrument" Command="{Binding Path=RemoveInstrumentFromTest}" CommandParameter="{Binding Instrument}"/>
                            </ContextMenu>
                        </StackPanel.ContextMenu>
                        </StackPanel>                            
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

Here is a copy of my ViewModel:

     public class ViewModel : INotifyPropertyChanged
    {
        public DelegateCommand<string> AddInstrumentToTest { get; set; }
        public DelegateCommand<string> RemoveInstrumentFromTest { get; set; }
        private ObservableCollection<IInstrument> instruments = new ObservableCollection<IInstrument>();

        public ViewModel()
        {
            this.AddInstrumentToTest = new DelegateCommand<string >(this.OnaddInstrumentToTest, this.canAddInstrument);
            this.RemoveInstrumentFromTest = new DelegateCommand<string >(this.OnremoveInstrumentFromTest, this.canRemoveInstrument);      
        }

        public ObservableCollection<IInstrument> Instruments
        {
            ...
        }

        public void OnaddInstrumentToTest(string inst) {...}
        public void OnremoveInstrumentFromTest(string inst) {...}
        public bool canRemoveInstrument(string inst) {...}
        public bool canAddInstrument(string inst) {...}

        ...
}

And here is the IInstrument interface:

        public interface IInstrument
        {        
            string Model {get;set;}       
            string SerialNumber {get;set;}    
            InstrumentCommInterface.InstrumentInterface InstrumentComm {get;set;} 
            InstrumentClassification.InstrumentClass InstrumentClass {get;set;} 
            string FirmwareVersion {get;set;}  
            string Address {get;set;}
            string Port {get;set;}
            Lazy<IInstrumentFactory, IInstrumentPlugInMetaData> PlugInType {get;set;}
            string Instrument {get;set;}
            BitmapImage InstrumentIcon {get;set;}
            bool SelectedForTest {get;set;}
            ObservableCollection<string> Channels {get;set;}        
        }

回答1:

I would retain the DataContext and tunnel through the context menu tag.

<!-- Keep complete ListBox, allows acces of selected item and higher up DataContext -->
<StackPanel Tag="{Binding RelativeSource={RelativeSource AncestorType=ListBox}}">
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"
             Tag="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">

So now the DataContext is still your item, if you need the selected item use:

Tag.SelectedItem, RelativeSource={RelativeSource AncestorType=ContextMenu}