Search code examples
c#wpfxamlattached-properties

WPF Attached Property's Type is being mangled by framework


Consider pretty much standard Attached Property for some derived type:

public class DerivedKeyBinding : KeyBinding
{
    public string Name; //shortened, make this propdp
}

public class KeyBindingExtenstions
{
    public static DerivedKeyBinding GetCommand(DependencyObject obj) {
        return (DerivedKeyBinding)obj.GetValue(CommandProperty);
    }

    public static void SetCommand(DependencyObject obj, DerivedKeyBinding value) {
        obj.SetValue(CommandProperty, value);
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command", typeof(DerivedKeyBinding), typeof(KeyBindingExtenstions), new UIPropertyMetadata(null, CommandChanged));

    private static void CommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        var commandValue = GetCommand(d);
    }
}

which is later set in XAML:

<Grid>
    <Grid.Style>
        <Style>
            <Setter Property="local:KeyBindingExtenstions.Command">
                <Setter.Value>
                    <local:DerivedKeyBinding Name="{Binding Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
                </Setter.Value>
            </Setter>
        </Style>
    </Grid.Style>
</Grid>

This will, however, crash on the var commandValue = GetCommand(d); line due to local:DerivedKeyBinding lost to KeyBinding somewhere in process.
For some weird reason the property of DerivedKeyBinding type is being set with value of type KeyBinding (even though value is explicitly set to DerivedKeyBinding in XAML).

System.InvalidCastException: 'Unable to cast object of type 'System.Windows.Input.KeyBinding' to type 'AttachedPropertyInStyle.DerivedKeyBinding'.'

Why is this happening and how can I fix the issue?


The issue seems to be connected to the Name binding - if it is static value, the code executes flawlessly.


Solution

  • Override Key​Binding.​Create​Instance​Core. From Microsoft Docs:

    Notes to Inheritors

    Every Freezable derived class must implement this method. A typical implementation is to simply call the default constructor and return the result.


    public class DerivedKeyBinding : KeyBinding
    {
        ...
    
        protected override Freezable CreateInstanceCore()
        {
            return new DerivedKeyBinding();
        }
    }
    

    In the PropertyChangedCallback you may also write:

    var commandValue = (DerivedKeyBinding)e.NewValue;