Noisy88
Noisy88

Reputation: 183

Why binded command in listbox template can't run?

Why buttons in listbox can't run binded command? I bind this command for simple button(not template) and it's working perfectly. Main.xaml

<Window x:Class="WpfApplication2.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:WpfApplication2"
    mc:Ignorable="d"
    Title="MainWindow" Height="800" Width="525">
<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>
<Window.Resources>
    <DataTemplate x:Key="lbTemp">
        <StackPanel>
            <TextBlock Height="50" Text="{Binding}"/>
            <Button Height="20" Content="click" Command="{Binding Path=TestCommand}" CommandParameter="hello"/>
        </StackPanel>
    </DataTemplate>
</Window.Resources>
<Grid>
    <ListBox x:Name="listBox" Width="500" Height="300" ItemTemplate="{StaticResource lbTemp}" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=DataContext.MyData}"/>
    <Button Command="{Binding Path=TestCommand}" CommandParameter="hello" Width="200" Height="40"/>
</Grid>

ViewModel.cs

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel() { }

    public ObservableCollection<string> MyData {
        get
        {
            return _MyData;
        }
        set
        {
            if (!_MyData.SequenceEqual(value))
            {
                _MyData = value;
            }
            OnPropertyChanged();
        }
    }
    private ObservableCollection<string> _MyData = new ObservableCollection<string>();

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string caller="")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
    }

    private ICommand _testCommand;
    public ICommand TestCommand
    {
        get
        {
            return _testCommand;
        }
        set
        {
            if (_testCommand != value)
            {
                _testCommand = value;
                OnPropertyChanged();
            }
        }
    }
}

And Command.cs

public class RelayCommand : ICommand
{
    public RelayCommand(Action action, Func<object, bool> canExecutePredicate)
    {
        if (action == null || canExecutePredicate == null)
            throw new ArgumentNullException("Can't be null");
        this._action = action;
        this._canExecutePredicate = canExecutePredicate;
    }

    public RelayCommand(Action action) : this(action, (obj) => true) { }

    Action _action;
    Func<object, bool> _canExecutePredicate;

    public event EventHandler CanExecuteChanged;
    protected virtual void OnCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

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

    public void Execute(object parameter)
    {
        _action?.Invoke();
    }
}

Can you say valid xaml example for this solution?

Upvotes: 0

Views: 53

Answers (2)

user1562155
user1562155

Reputation:

I think it's because the TestCommand is a property on ViewModel - not on the elements (strings) of the MyData collection.

You'll have to make a class:

class MyItemClass : INotifyPropertyChanged
{
  public string Text {get;set;}
   private ICommand _testCommand;
    public ICommand TestCommand
    {
        get
        {
            return _testCommand;
        }
        set
        {
            if (_testCommand != value)
            {
                _testCommand = value;
                OnPropertyChanged();
            }
        }
    }

   public event PropertyChangedEventHandler PropertyChanged;
   protected virtual void OnPropertyChanged([CallerMemberName]string caller="")
   {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
    }
  }

You should then fill your MyData collection with items of that type where the TextBlocks binding should be changed to Text in the DataTemplate.

BTW: you don't have to have a relativesource reference in the itemssource binding of the list box. It inherits the DataContext from the Window, so it should be sufficient just to use {Binding MyData}.

Upvotes: 1

Simon
Simon

Reputation: 326

The DataContext of the two Buttons are different. Let's take a look at the DataContext for some of the elements in your view.

  • The Window's DataContext is your ViewModel class.
  • The ListBox's DataContext is the same as the Window's. There should be no need for the use of RelativeSource in the ItemsSource binding you have set up.
  • The Button outside the DataTemplate also has the same DataContext as the Window. That's why this Command binding works fine.
  • The Button inside the DataTemplate has a DataContext of the particular item it is representing from the MyData collection you have created inside your ViewModel class. Importantly, it is not the ViewModel class itself.

Here is where I would use RelativeSource.

<Button Height="20" Content="click" Command="{Binding DataContext.TestCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" CommandParameter="hello"/>

Please let me know if this does not work for you.

Upvotes: 2

Related Questions