2
votes

Ive a business object - call it Fish (not derived from anything ie not a DependancyObject) that is displayed in a ListBox using a DataTemplate. Else where in code I need to know the rendered width of the TextBlock part of the Fish DataTemplate through a reference to a Fish. No problem I thought. I added a width and height properties to Fish class and in my data template I bound the TextBlock width/height to these using Mode=OnwayToSource.
Problem: the Width/Height are always NaN when setting my Fish.width/heigh properties. I tried this workaround:
OneWayToSource Binding seems broken in .NET 4.0
but it doesnt work either (value is always NaN).
I cant bind to ActualWidth/ActualHeight because they are read only (why can't I bind OnwayToSource on a readonly property!!)
What alternatives do I have? Do I have to derive Fish from DependancyObject and make my properties DPs?
XAML:

<DataTemplate DataType="{x:Type p:Fish}">
<Border BorderBrush="Black" BorderThickness="2" >
<TextBlock FontSize="14" TextAlignment="Center" VerticalAlignment="Center"
           Width="{Binding Path=width, Mode=OneWayToSource}"
           Height="{Binding Path=height, Mode=OneWayToSource}" ...

Code:

class Fish {
    public double width { get; set; } // From DataTemplate TextBlock.Width.
    public double height { get; set; } // From DataTemplate TextBlock.Height
}

...
double rendered_width = my_fish.width; // Use the rendered width!
3
You're using the wrong binding mode. See BalamBalam's answer. The question you linked to has to do with the way the binding used to not get the bound property after an update, nothing to do with this question. You want to READ only - use Mode=OneWay. You want to WRITE only - use Mode=OneWayToSource.Alain
Either Im confussed on unclear (either possible!). The TextBlock renders and has a Width/ActualWidth. I want to push that value to my business (Fish) objects width property - so I can read that from a fish in code. So the binding in my template that is on the TextBlock needs to be OnewayToSource - write the TextBlock.Width to Fish.widthRicibob
It is my understanding that this is precisely the reason why OneWayToSource exists - to bind to properties on objects that are not dependancy properties by reversing the binding by putting it on the source object instead of the target.Ricibob
Oh I see what you're saying now. The real reason OneWayToSource exists is so that you can set a value in the UI that changes the bound value in the VM, but changes to the value in the VM doesn't affect the element in the UI. The reason the binding to read-only property doesn't work is because it's still trying to do a Get when the UI element (width/height) changes - as per that question you linked to.Alain
It looks like you can't use the binding backwards the way you want to. What you really should be doing is binding the fish width to the textblock width, not 'reverse-binding' the textblock width to the fish width. If your properties (fish.Width / fish.Height) exist only in your .cs code, then your bindings should be defined in the .cs code.Alain

3 Answers

3
votes

I've finally realized what you're trying to do, and you're right that it should work. WPF, however, disagrees. I see that it's a problem that others have had before, but that is apparently by design. You can't set up a binding on a read only property, even if you're just wanting to bind OneWayToSource.

Here is a question with the same problem: OneWayToSource binding from readonly property in XAML Their workaround was to put a container (which has read/write width/height) around the xaml element and set up the binding on that container. This might work for you.

There is an unresolved issue related to this on Microsoft Connect where it is claimed to be behaviour by design: http://connect.microsoft.com/VisualStudio/feedback/details/540833/onewaytosource-binding-from-a-readonly-dependency-property. Someone claims a workaround in the related thread which uses a converter. You can try it, but I'm not sure it'll work in your case, as their binding was to a custom control, not a built in framework element.

Even Better

In This Solution, Boogaart came up with an implementation defining a new attached property (Similar to DockPanel.Dock="Top") which allows any element to provide its width and height for observation:

<TextBlock ...
     SizeObserver.Observe="True"
     SizeObserver.ObservedWidth="{Binding Width, Mode=OneWayToSource}"
     SizeObserver.ObservedHeight="{Binding Height, Mode=OneWayToSource}"

Try it on and see if it fits.

1
votes

If you consume these properties after some sort of action i.e. a button press or click on a hyperlink, then you can pass in the the ActualWidth and Height via a CommandParameter of a command. Otherwise I would suggest using triggers such as the ones available here:

http://expressionblend.codeplex.com/wikipage?title=Behaviors%20and%20Effects&referringTitle=Documentation

I agree that it appears counter intuitive that OneWayToSource bindings don't work on read only dependency properties.

1
votes

Try binding OneWay. I think OneWayToSource is means wants to write to the source.

http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx

I did a test and sure enough Width = NaN until width is Assigned (set). I understand this is not the behavior you want. Try this. Where the Width is assigned it is reported (as 200). Where the Width is not assigned it is reported as NaN. But ActualWidth IS correct. ActualWidth is there but clearly the way you are trying to get it is not working.

    <StackPanel Orientation="Vertical">
            <Border BorderThickness="1" BorderBrush="Red">
                <TextBlock Name="tbwidthA" Text="{Binding Path=Howdy}" HorizontalAlignment="Left" Width="200"/>
            </Border>
            <TextBlock Name="tbwidthAw" Text="{Binding ElementName=tbwidthA, Path=Width}"  HorizontalAlignment="Left"/>
            <TextBlock Name="tbwidthAaw" Text="{Binding ElementName=tbwidthA, Path=ActualWidth}" HorizontalAlignment="Left" />
            <Border BorderThickness="1" BorderBrush="Red">
                <TextBlock Name="tbwidthB" Text="{Binding Path=Howdy}" HorizontalAlignment="Left" />
            </Border>
            <TextBlock Name="tbwidthBw" Text="{Binding ElementName=tbwidthB, Path=Width}" HorizontalAlignment="Left" />
            <TextBlock Name="tbwidthAbw" Text="{Binding ElementName=tbwidthB, Path=ActualWidth}" HorizontalAlignment="Left" />
            <Button Content="TBwidth" Click="Button_Click_1" Width="60" HorizontalAlignment="Left" />
        </StackPanel>

What is interesting is the Button does report the correct ActualWidth but Width is NaN.

        Debug.WriteLine(tbwidthB.Width.ToString());
        Debug.WriteLine(tbwidthB.ActualWidth.ToString());