2

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!
Community
  • 1
  • 1
Ricibob
  • 7,505
  • 5
  • 46
  • 65
  • 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 Oct 26 '11 at 15:11
  • 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.width – Ricibob Oct 26 '11 at 15:19
  • 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 Oct 26 '11 at 15:30
  • 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 Oct 26 '11 at 16:07
  • 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 Oct 26 '11 at 16:09
  • Yes what I want to do is bind Fish.width to TextBlock.Width - but even if I made Fish a DependancyObject and width a DepednacyProperty there doesn't seem to be an easy way to do that binding (well not from in the DataTemplate). How to setup the binding that way round - is a DataTrigger the route here? – Ricibob Oct 26 '11 at 16:15
  • I know you can bind one xaml elements height and width to that of another xaml element (http://stackoverflow.com/questions/2153316/xaml-binding-width-and-height-properties-to-the-same-properties-of-other-contro), but binding something in your code to a property of a xaml element will be different. It'll have to be done in the code behind. Let me research it a bit. – Alain Oct 26 '11 at 16:21
  • Shot in the dark, but why don't you try removing the `get;` method and having only a `set;` method for the properties you bind to? i.e.: `private double width = 0; public double Width { set { width = value; } }` Then try to bind OneWayToSource to Width. – Alain Oct 26 '11 at 16:34
  • OK I get that you want to write out the value. The problem I see is where do you assign a value? That binding does not assign a value. Please post the binding for the ListBox that references that DataTemplate. If it is used in a ListBox then I am pretty sure the widths comes from the ListBox. On the display are not all the TextBlocks the same width? You may need to put a border on the TextBlocks to see the height and width. Why do need the rendered width elsewhere in code? – paparazzo Oct 26 '11 at 17:02

3 Answers3

3

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.

Community
  • 1
  • 1
Alain
  • 26,663
  • 20
  • 114
  • 184
  • +1 for such a detailed answer. Personally i like the container and Converter solution to be best. – Rohit Vats Oct 26 '11 at 19:49
  • @Alain I used the converter approach from ms connect link above - that worked for me. It makes a bit of a mess of the code as I already needed the TextBlock.Tag for set ContextMenu data context - so this and Width and Height all have to be packed into the MultiBinding - ugly but works. Thanks a lot for your help on this. – Ricibob Oct 27 '11 at 10:42
1

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.

Damian
  • 2,709
  • 2
  • 29
  • 40
  • This could be a workaround. Except it will only work when the user interacts with the control. When the window first loads, the height property will be missing, and that could be a problem. If the element is setup such that it resizes with the window or parent control, then this problem gets worse, as the height and width would get out of sync until the next time the user moused over, clicked, or typed into the control. It would be a nightmare trying to find an event that fires frequently enough to keep this up to date, not to mention inelegant. – Alain Oct 26 '11 at 17:07
  • @Alain -That's why I prefaced my initial suggestion with if. There are some situations where I have gotten away with the CommandParameter method as the readonly property was needed after I clicked a button for example. Otherwise short of using a custom attached property as you suggested, then I choose to use a combination of blend trigger's and actions. – Damian Oct 26 '11 at 17:23
  • @Alian I do agree that the cleanest solution (currently) is using a custom attached property. – Damian Oct 26 '11 at 17:24
  • @Damian Thanks for your input Damian - but in my case there was no action that I could hook into to use this approach. – Ricibob Oct 27 '11 at 10:44
1

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());
paparazzo
  • 44,497
  • 23
  • 105
  • 176
  • OneWayToSource *does* means *write* to the source – Alain Oct 26 '11 at 15:06
  • The "Target" is the TextBlock.Width (a DP) - the source is my Fish.width - but I need to inverse that and write target to source hence OnwayToSource. I.e OnewayToSource is the correct Mode. – Ricibob Oct 26 '11 at 15:06
  • Ive added the DataTemplate code to help clarify - I believe OnewayToSource is what I want. – Ricibob Oct 26 '11 at 15:27
  • Did you try OneWay? Your problem statement is "know the rendered width of the TextBlock". You need to bind to the UI element ActualWidth (not the object). That UI element is the SOURCE. The TextBlock to diplay the width is the the TARGET and the binding mode should be one way. Please post your binding for the ListBox and the TextBlock. – paparazzo Oct 26 '11 at 15:54
  • @BalamBalam Sorry - maybe I wasn't clear - The textblock does not display the width. The text box is in a ListBox (not really relivant to the problem). The textblock renders some dynamic content - this determines its rendered Width/AcutalWidth. I then need to push that rendered width value to my Fish object for use in a caluclation in code. Does that clarify. The Mode is not the issue here - OneWayToSource is correct - the issue is that its always NaN and/or that what I really want to do is get the ActualWidth but I cant bind to that (not even OneWayToSource) as it is readonly. – Ricibob Oct 26 '11 at 16:21