0

I try to create Command which inherit from DependencyObject and ICommand. I have the following code:

    public class CustomCommand : DependencyObject, ICommand
{
    public static readonly DependencyProperty CommandProperty;
    public static readonly DependencyProperty AfterCommandProperty;
    static CustomCommand()
    {
        var ownerType = typeof(CustomCommand);
        CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(Action), ownerType, new PropertyMetadata(null));
        AfterCommandProperty = DependencyProperty.RegisterAttached("AfterCommand", typeof(Action), ownerType, new PropertyMetadata(null));
    }

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

    public Action AfterCommand
    {
        get => (Action)GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }

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

    public void Execute(object parameter)
    {
        // Command & AfterCommand are always null
    }
}

and

    <Button Content="Test">
    <Button.Command>
        <command:CustomCommand  Command="{Binding Copy}" AfterCommand="{Binding AfterCopy}" />
    </Button.Command>
</Button>

When I press Test button Command and AfterCommand are null. Do you have an idea ? What is the best way cause I can't add ICommand reference to my ViewModel.

Thanks

Cribanus
  • 3
  • 1
  • Change to this: `Command="{Binding Copy, PresentationTraceSources.TraceLevel=High}"` and see what you see in your debug output. I suspect you'll find that there's no context because it's not in the visual tree. You could try creating your command as a resource, and/or you could try various binding proxy strategies. – 15ee8f99-57ff-4f92-890c-b56153 May 09 '17 at 17:55
  • Incidentally, you've got a bug in the `AfterCommand` property: It's passing `CommandProperty` to GetValue/SetValue, not `AfterCommandProperty`. – 15ee8f99-57ff-4f92-890c-b56153 May 09 '17 at 17:58

1 Answers1

0

Your CustomCommand instance isn't in the visual tree, so binding is a problem. It's got no way to get a DataContext. Try putting a trace on the binding:

<Button.Command>
    <local:CustomCommand
        Command="{Binding TestAction, PresentationTraceSources.TraceLevel=High}"
        />
</Button.Command>

"Framework mentor not found" is the error you'll see in the debug output. "Ya can't get there from here" is how they'd say that Down East. Context in XAML is a matter of parent-to-parent, but this thing has, in the sense that matters here, no parent.

But it's an easy fix. Use a binding proxy. Here's a town bike implementation that I've stolen several times from various questions and answers on Stack Overflow:

public class BindingProxy : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

Define an instance as a resource in some containing scope that has the DataContext where the desired Action property lives. {Binding} with no path just returns DataContext, which will be the window's viewmodel in the case below.

<Window.Resources>
    <local:BindingProxy
        x:Key="MainViewModelBindingProxy"
        Data="{Binding}"
        />
</Window.Resources>

And use it like so. The Data property of BindingProxy is bound to the viewmodel, so use a path of Data.WhateverPropertyYouWant. I called my Action property TestAction.

<Button
    Content="Custom Command Test"
    >
    <Button.Command>
        <local:CustomCommand
            Command="{Binding Data.TestAction, Source={StaticResource MainViewModelBindingProxy}}"
            />
    </Button.Command>
</Button>

N.B.

You've also got a bug in your AfterCommand property: It's passing CommandProperty to GetValue/SetValue, not AfterCommandProperty.

Community
  • 1
  • 1