wpfxamldata-bindingattachedbehaviors

How do I bind to the X position of a MouseDragElementBehavior?


My goal is to display the X position of my control in a TextBlock as I drag it around.

xmlns:mb="http://schemas.microsoft.com/xaml/behaviors"

<cc:CardControl Name="SevenOfSpades" Canvas.Left="350" Canvas.Top="124" Width="60" Height="80" Face="S7">
    <mb:Interaction.Behaviors>
        <mb:MouseDragElementBehavior ConstrainToParentBounds="True"/>
    </mb:Interaction.Behaviors>
</cc:CardControl>
<TextBlock Text="{Binding ElementName=SevenOfSpades, Path=(mb:Interaction.Behaviors)[0].X}"/>

I'm struggling with the syntax of the Binding Path. At runtime I get an exception:

InvalidOperationException: Property path is not valid. 'Interaction' does not have a public property named 'Behaviors'.

The property is there because the drag works when the TextBlock is removed. I've tried various combinations of parentheses, I even tried x:static. Any help?

Edit

Having reread WPF Attached Property Data Binding, it does not solve my problem. Path= is in the Xaml and parentheses are included. The error is not a binding error it's a runtime error that occurs inside InitializeComponent.

MouseDragElementBehavior is part of the Microsoft.Xaml.Behaviors.Wpf Nuget package installed into my project.


Solution

  • Ah, ok. In that case, the code for MouseDragElementBehavior is most certainly available, and even if it wasn't you could just open up the assembly with JustDecompile or something and browse it that way.

    If you check the documentation for MouseDragElementBehavior you'll see this:

    XProperty Dependency property for the X position of the dragged element, relative to the left of the root element.

    So basically you're trying to bind one dependency property (TextBlock.Text) to another (MouseDragElementBehavior.X), but in order for this to work they have to be part of the same visual or logical tree (which they aren't, MouseDragElementBehavior is a behavior). If one of them was an attached property then you could bind them directly, but in your case you have to link them together with either a property in your DataContext that supports INPC, or some kind of proxy object.

    However, even if you do this, you're going to run into problems. If you click the "Go to Live Visual Tree" button while your application is running and look at the properties for your SevenOfSpades control you'll see this:

    enter image description here

    So far, so good. Now drag the control around a bit and repeat this process. Suddenly a RenderTransform field has appeared:

    enter image description here

    Looking back at the code for MouseDragElementBehavior reveals that sure enough, that behaviour does the drag by changing the render transform.

    So basically you're trying to set the position with Canvas.Top/Canvas.Left, but the behaviour is setting it by applying a render transform offset. Pick one. I personally use MVVM where everything is implemented in the view model layer, so it's easy to bind Canvas.Top/Canvas.Left to properties there. If you want to continue using MouseDragElementBehavior then you'll need to bind both the position of your cards, as well as your TextBlock text, to the render transform instead:

    <Canvas>
    
        <Rectangle Name="SevenOfSpades" Width="60" Height="80" Fill="Blue">
            <Rectangle.RenderTransform>
                <TranslateTransform X="350" Y="124" />
            </Rectangle.RenderTransform>
            <mb:Interaction.Behaviors>
                <mb:MouseDragElementBehavior ConstrainToParentBounds="True" />
            </mb:Interaction.Behaviors>
        </Rectangle>
    
        <TextBlock Text="{Binding ElementName=SevenOfSpades, Path=RenderTransform.Value.OffsetX}" />
    
    </Canvas>