Why does ViewModel get null when try pass Property from View through Command?

52 Views Asked by At

So I try pass MyProperty from code-behind of View through Command to ViewModel. its looks like this:

View: code-behind .xaml.cs

public partial class MainWindow : Window
{
    public string MyProperty { get; set; }
    
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Test_Click(object sender, RoutedEventArgs e)
    {
        MyProperty = "test click";
    }
}

View: .xaml

  <MenuItem Header="Test" CommandParameter="{Binding ElementName=MainWindow, Path=MyProperty}" Command="{Binding Test}"  Click="Test_Click"></MenuItem>

ViewModel:

private RelayCommand _test;

public RelayCommand Test { 
    get 
    {
        if (_test != null)
        {
            return _test;
        }

        return _test = new RelayCommand(obj =>
        {
            MessageBox.Show($"{obj}"); // here obj = null why? 
        });
    }
}

RelayCommand:

internal class RelayCommand : ICommand
{
    private Action<object> _execute;
    private Func<object, bool> _canExecute;

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

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute(parameter);
    }

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

also tried instead of ElementName use RelativeSource but doesnt work:

<MenuItem Command="{Binding DataContext.Test, RelativeSource={RelativeSource AncestorType=views:MainWindow}}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=views:MainWindow}, Path=MyProp}" Click="Test_Click"></MenuItem>

My question is:

  1. why it doesnt work?
  2. Which order of binding? When I run debugging first will Event_Click, then CanExecute, and then Execute - here MyProperty already setted from Event_Click but anyway Command get null.
2

There are 2 best solutions below

3
Sir Rufo On BEST ANSWER

Binding only works on properties if you notify when the property value has changed. A simple property does not do any notification. Within a View you can declare a DependencyProperty which will do the notification for the binding

public partial class MainWindow : Window
{
    public string MyProperty
    {
        get { return (string)GetValue( MyPropertyProperty ); }
        set { SetValue( MyPropertyProperty, value ); }
    }

    // Using a DependencyProperty as the backing store for MyPropertyB.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MyPropertyProperty =
        DependencyProperty.Register( "MyProperty", typeof( string ), typeof( MainWindow ), new PropertyMetadata( null ) );

    private void Button_Click( object sender, RoutedEventArgs e )
    {
        MyProperty = "From Button_Click";
    }
}
<Window
   ...
   x:Name="Root"
   ...>
    ...
    <Button Command="{Binding Test}"
            Content="Test"
            Click="Button_Click"
            CommandParameter="{Binding ElementName=Root, Path=MyProperty}" />
    ...
</Window>
2
David Barzi On

So, for the first approach, when binding using ElementName the element that you are trying to bind to must have a name declared using x:Name="name". DisplayName is not used in bindings. So ensure that you are using the actual name of the element.

Also, the name can't be the same as the type, so a window of type MainWindow can't have x:Name="MainWindow"

For your second attempt, you are missing FindAncestor, the correct syntax for relative bindings is:

CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=views:MainWindow}, Path=MyProperty}"

Later edit: if you are not seeing the updated value of your property, it needs to either be a DependencyProperty, or you can implement INotifyPropertyChanged and emit PropertyChanged when setting its value.