wpfdependency-propertiesdependencyobject

trainings DependencyObject - custom command


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


Solution

  • 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.