3

I have a simple WPF example trying to bind a ListBox's Selected event to an ICommand in the view model.

XAML

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    <Grid>
        <ListBox ItemsSource="{Binding Items}" 
                 Selected="{Binding DoSomething}"/>

    </Grid>
</Window>

View Model

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new ViewModel();
        }
    }

    public class ViewModel : INotifyPropertyChanged
    {
        public ViewModel()
        {
            Items = new List<string>();
            Items.Add("A");
            Items.Add("B");
            Items.Add("C");

            DoSomething = new MyCommand();
        }

        public List<string> Items { get; set; }


        public event PropertyChangedEventHandler PropertyChanged;

        public ICommand DoSomething { get; set; }
    }

    public class MyCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter) { return true; }

        public void Execute(object parameter) { }
    }
}

The error occurs in the constructor on InitializeComponent.

XamlParseException: A 'Binding' cannot be set on the 'AddSelectedHandler' property of type 'ListBox'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

enter image description here

How can I have an ICommand called in my view model from the Selected event of a ListBox control?

John Livermore
  • 30,235
  • 44
  • 126
  • 216
  • 1
    Selected is a binding to data not an event to which you can tie a command. See answer to this question: https://stackoverflow.com/questions/4897775/wpf-binding-ui-events-to-commands-in-viewmodel – AQuirky Mar 26 '18 at 22:00

2 Answers2

4

Selected on a ListBox is an event. You have the SelectedItem which you can bind to a property of the same type as an element of the list on the viewmodel:

<Grid>
    <ListBox ItemsSource="{Binding Items}" 
             SelectedItem="{Binding MyItem}"/>
</Grid>

.

public class ViewModel : INotifyPropertyChanged
{
    public string MyItem { get; set; }
}

As for your command, you need a control which handles a CommandSource, like the Button:

<Button Command="{Binding DoSomething}" CommandParameter="{Binding}" />

Binding it like this, will enable WPF to recognize your ICommand. The CommandParameter is optional though.

Kolky
  • 2,917
  • 1
  • 21
  • 42
Seb
  • 620
  • 1
  • 5
  • 11
1

It was becouse of "Selected" are event, Actualy you can not bind your command to any event directly. But there are several posible ways to solve this issue. In the example below i used custom command realization for bind to WPF event and got EventArgs as parameter to command. You need System.Windows.Interactivity assembly.

<Window x:Class="Example.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:Example"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ListView
        ItemsSource="{Binding Country}">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <local:InteractiveCommand Command="{Binding SelectedCountryCommand}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </ListView>
   <Grid
       Grid.Column="1">
       <Grid.RowDefinitions>
           <RowDefinition Height="Auto"/>
           <RowDefinition/>
       </Grid.RowDefinitions>
       <Label
           HorizontalAlignment="Center"
           Content="SELECTED ITEMS:"/>
       <ListView
           Grid.Row="1"
           ItemsSource="{Binding SelectedCountry}"/>
   </Grid>
</Grid>

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }

}

public class MainViewModel:INotifyPropertyChanged
{
    public List<string> Country { get; set; } = new List<string>
    {
        "USA",
        "CANADA",
        "FRANCE",
        "GERMAN",
        "JAPAN",
        "ITALY",
        "UKARAINE",
        "POLAND",
        "GREAT BRITAIN",
        "TURKEY"
    };

    public ObservableCollection<string> SelectedCountry { get; set; } = new ObservableCollection<string>();

    public ICommand SelectedCountryCommand =>
        _selectedCountryCommand ?? (_selectedCountryCommand = new RelayCommand(
            param =>
            {
                SelectedCountry.Clear();
                SelectedCountry.Add((param as SelectionChangedEventArgs).AddedItems[0].ToString());
            }));

    private ICommand _selectedCountryCommand;

    //INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class RelayCommand : ICommand
{

    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;


    public RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }


    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute?.Invoke(parameter) ?? true;
    }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

}

public class InteractiveCommand : TriggerAction<DependencyObject>
{
    protected override void Invoke(object parameter)
    {
        if (AssociatedObject == null)
            return;
        var command = ResolveCommand();
        if (command != null && command.CanExecute(parameter))
        {
            command.Execute(parameter);
        }
    }

    private ICommand ResolveCommand()
    {
        ICommand command = null;
        if (Command != null)
        {
            return Command;
        }
        if (AssociatedObject != null)
        {
            foreach (var info in AssociatedObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (typeof(ICommand).IsAssignableFrom(info.PropertyType) && string.Equals(info.Name, CommandName, StringComparison.Ordinal))
                {
                    command = (ICommand)info.GetValue(AssociatedObject, null);
                }
            }
        }
        return command;
    }

    private string _commandName;
    public string CommandName
    {
        get
        {
            ReadPreamble();
            return _commandName;
        }
        set
        {
            if (CommandName == value)
                return;
            WritePreamble();
            _commandName = value;
            WritePostscript();
        }
    }

    #region Command
    public ICommand Command
    {
        get => (ICommand)GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }

    // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(InteractiveCommand), new UIPropertyMetadata(null));
    #endregion
}