WPF CommandParameter Binding Problem

У меня возникли проблемы с пониманием того, как работает привязка параметров команды.

Когда я создаю экземпляр класса виджета перед вызовом InitializeComponent, он работает нормально. Модификации параметра (Виджет) в функции ExecuteCommand будут применены к _widget. Это поведение, которое я ожидал.

Если экземпляр _widget создается после InitializeComponent, я получаю нулевые ссылочные исключения для e.Parameter в функции ExecuteCommand.

Почему это? Как это сделать с шаблоном MVP, где связанный объект может создаваться после создания представления?

public partial class WidgetView : Window
{
    RoutedCommand _doSomethingCommand = new RoutedCommand();

    Widget _widget;

    public WidgetView()
    {
        _widget = new Widget();
        InitializeComponent();
        this.CommandBindings.Add(new CommandBinding(DoSomethingCommand, ExecuteCommand, CanExecuteCommand));
    }

    public Widget TestWidget
    {
        get { return _widget; }
        set { _widget = value; }
    }

    public RoutedCommand DoSomethingCommand
    {
        get { return _doSomethingCommand; }
    }

    private static void CanExecuteCommand(object sender, CanExecuteRoutedEventArgs e)
    {
        if (e.Parameter == null)
            e.CanExecute = true;
        else
        {
            e.CanExecute = ((Widget)e.Parameter).Count < 2;
        }
    }

    private static void ExecuteCommand(object sender, ExecutedRoutedEventArgs e)
    {
        ((Widget)e.Parameter).DoSomething();
    }
}



<Window x:Class="CommandParameterTest.WidgetView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WidgetView" Height="300" Width="300"
    DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <StackPanel>
        <Button Name="_Button" Command="{Binding DoSomethingCommand}"
             CommandParameter="{Binding TestWidget}">Do Something</Button>
    </StackPanel>
</Window>


public class Widget
{
    public int Count = 0;
    public void DoSomething()
    {
        Count++;
    }
}

Ответ 1

InitializeCompenent обрабатывает xaml, связанный с файлом. Именно в этот момент привязка CommandParameter сначала обрабатывается. Если вы инициализируете свое поле до InitializeCompenent, ваше свойство не будет равно null. Если вы создадите его после этого, это будет null.

Если вы хотите создать виджет после InitializeCompenent, вам нужно будет использовать свойство зависимости. Запуск зависимостей повысит уведомление, которое приведет к обновлению CommandParameter и, следовательно, оно не будет равно null.

Вот пример того, как сделать TestWidget зависимым.

public static readonly DependencyProperty TestWidgetProperty =
    DependencyProperty.Register("TestWidget", typeof(Widget), typeof(Window1), new UIPropertyMetadata(null));
public Widget TestWidget
{
    get { return (Widget) GetValue(TestWidgetProperty); }
    set { SetValue(TestWidgetProperty, value); }
}

Ответ 2

Даже с использованием свойства зависимостей вам все равно нужно вызвать CommandManager.InvalidateRequerySposed для принудительной проверки CanExecute команды.