Search code examples
c#wpfxamldependency-propertiesattached-properties

Attached property cannot be bound


We have a WPF application which has a query count result displayed on the screen. We initially defined the result as a button so that when it was clicked, the application would display a detailed list of the query results. However, for reasons unrelated to this question, we now need this to be a border (basically, just the template for the original button). So far, I have set up my attached property:

public static class AttachedCommandBehavior
{
    #region Command

    public static DependencyProperty PreviewMouseLeftButtonUpCommandProperty = DependencyProperty.RegisterAttached(
        "PreviewMouseLeftButtonUpCommand",
        typeof(ICommand),
        typeof(AttachedCommandBehavior),
        new FrameworkPropertyMetadata(PreviewPreviewMouseLeftButtonUpChanged));

    public static void SetPreviewMouseLeftButtonUpChanged(DependencyObject target, ICommand value)
    {
        target.SetValue(PreviewMouseLeftButtonUpCommandProperty, value);
    }

    public static ICommand GetPreviewMouseLeftButtonUpChanged(DependencyObject target)
    {
        return (ICommand)target.GetValue(PreviewMouseLeftButtonUpCommandProperty);
    }

    private static void PreviewPreviewMouseLeftButtonUpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UIElement element)
        {
            if (e.NewValue != null && e.OldValue == null)
            {
                element.PreviewMouseLeftButtonUp += element_PreviewMouseLeftButtonUp;
            }
            else if (e.NewValue == null && e.OldValue != null)
            {
                element.PreviewMouseLeftButtonUp -= element_PreviewMouseLeftButtonUp;
            }
        }
    }

    private static void element_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (sender is UIElement element)
        {
            if (element.GetValue(PreviewMouseLeftButtonUpCommandProperty) is ICommand command)
                command.Execute(CommandParameterProperty);
        }
    }

    #endregion

    #region CommandParameter

    public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
        "CommandParameter",
        typeof(object),
        typeof(AttachedCommandBehavior),
        new FrameworkPropertyMetadata(CommandParameterChanged));

    public static void SetCommandParameter(DependencyObject target, object value)
    {
        target.SetValue(CommandParameterProperty, value);
    }

    public static object GetCommandParameter(DependencyObject target)
    {
        return target.GetValue(CommandParameterProperty);
    }

    private static void CommandParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UIElement element)
        {
            element.SetValue(CommandParameterProperty, e.NewValue);
        }
    }

    #endregion
}

And then in my XAML, I am trying to bind my command to the attached DependencyProperty:

<Border Background="{Binding BackgroundColor, Converter={StaticResource ColorNameToBrushConverter}}"
        Cursor="{x:Static Cursors.Hand}"
        local:AttachedCommandBehavior.PreviewMouseLeftButtonUpChanged="{Binding QueryClickedCommand}">
   <Grid>...</Grid>
</Border>

However, my little blue squiggly line is telling me "A 'Binding' cannot be used within a 'Border' collection. A 'Binding' can only be set on a DependencyProperty of a DependencyObject." Being the daring programmer that I am, I boldly ignore the little blue squiggly and try to run anyway. At which point I get an exception:

System.Windows.Markup.XamlParseException: 'A 'Binding' cannot be set on the 'SetPreviewMouseLeftButtonUpChanged' property of type 'Viewbox'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.'


Solution

  • It turns out this was a naming convention problem. In copying/pasting/renaming/general indecision, I messed up the names of my getter and setter for the command property. Once I changed them all to match the correct pattern, my code runs.

    #region Command
    
    public static DependencyProperty PreviewMouseLeftButtonUpCommandProperty = DependencyProperty.RegisterAttached(
                "PreviewMouseLeftButtonUpCommand",
                typeof(ICommand),
                typeof(AttachedCommandBehavior),
                new FrameworkPropertyMetadata(PreviewMouseLeftButtonUpChanged));
    
    public static void SetPreviewMouseLeftButtonUpCommand(DependencyObject target, ICommand value)
    {
        target.SetValue(PreviewMouseLeftButtonUpCommandProperty, value);
    }
    
    public static ICommand GetPreviewMouseLeftButtonUpCommand(DependencyObject target)
    {
        return (ICommand)target.GetValue(PreviewMouseLeftButtonUpCommandProperty);
    }
    
    private static void PreviewMouseLeftButtonUpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UIElement element)
        {
            if (e.NewValue != null && e.OldValue == null)
            {
                element.PreviewMouseLeftButtonUp += element_PreviewMouseLeftButtonUp;
            }
            else if (e.NewValue == null && e.OldValue != null)
            {
                element.PreviewMouseLeftButtonUp -= element_PreviewMouseLeftButtonUp;
            }
        }
    }
    
    private static void element_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (sender is UIElement element)
        {
            if (element.GetValue(PreviewMouseLeftButtonUpCommandProperty) is ICommand command)
                command.Execute(CommandParameterProperty);
        }
    }
    
    #endregion