Willie D.

Hi, I'm trying to construct a reusable compound control based on a RichTextBox.

To implement Data Binding, I've started with the work shared by Paul Stovell, adding an Attached Dependency Property to the RichTextBox.

The Source to Target binding is working fine but trying to go back the other way, Target to Source, is not.

I've been trying to use the BindingExpression.UpdateSource() method

Private Sub rtbxTarget_TextChanged(ByVal sender As Object, ByVal e As System.Windows.Controls.TextChangedEventArgs) Handles rtbxTarget.TextChanged

Dim bx As BindingExpression = rtbxTarget.GetBindingExpression(RichTextBoxBinder.ContentProperty)

bx.UpdateSource()

End Sub

but can't figure out what UpdateSource() is actually executing or how it might be set to one of my own routines.

Am I missing something obvious or attempting the something beyond the intended capabilities

Any suggestions would be greatly appreciated.

Thanks Will

Public Class RichTextBoxBinder

Public Shared Function GetContent(ByVal obj As DependencyObject) As String

Return CType(obj.GetValue(ContentProperty), String)

End Function

Public Shared Sub SetContent(ByVal obj As DependencyObject, ByVal value As String)

obj.SetValue(ContentProperty, value)

End Sub

Public Shared ReadOnly ContentProperty As DependencyProperty = _

DependencyProperty.RegisterAttached("Content", _

GetType(String), _

GetType(RichTextBoxBinder), _

New UIPropertyMetadata(AddressOf ContentProperty_PropertyChanged), _

AddressOf ContentProperty_Validate)

Private Shared Function ContentProperty_Validate(ByVal value As Object) As Boolean

Return True

End Function

Private Shared Sub ContentProperty_PropertyChanged(ByVal dependencyObject As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)

If Not IsNothing(TryCast(dependencyObject, RichTextBox)) Then

CType(dependencyObject, RichTextBox).Document = FlowDocHelpers.LoadBase64String(TryCast(e.NewValue, String))

End If

End Sub

End Class

<Window x:Class="Window2"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:clr="clr-namespaceTongue Tiedystem.Text;assembly=mscorlib"

xmlns:local="clr-namespace:FlowDocBinding"

Title="Waka Waka Waka Waka" Height="300" Width="300">

<StackPanel>

<StackPanel.Resources>

<clr:StringBuilder x:Key="embeddedContent" />

<!--<cvtr:TextToFlowDocumentConverter x:Key="cvtrTextToFlowDocumentConverter"/>-->

</StackPanel.Resources>

<TextBox x:Name="txtSumDumText" Text="{Binding Path=_textString}" />

<RichTextBox x:Name="rtbxTarget" Background="Bisque" TargetUpdated="rtbxTarget_TargetUpdated"

local:RichTextBoxBinder.Content="{Binding ElementName=txtSumDumText, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

<RichTextBox.Document>

<FlowDocument />

</RichTextBox.Document>

</RichTextBox>

</StackPanel>

</Window>



Re: Windows Presentation Foundation (WPF) Custom Two-Way Binding and BindingExpression.UpdateSource()

Dr. WPF

It looks to me like you have conflicting bindings. The TextBox named "txtSumDumText" already has a binding on its Text property. The RichTextBox is trying to create a TwoWay binding between its attached Content property and the

aforementioned Text property. What happens when you remove the binding from the TextBox's Text property

Have you tried attaching a dummy value converter to the binding on the Content property so that you can put a breakpoint in the ConvertBack routine This will allow you to determine exactly when the source is being updated.






Re: Windows Presentation Foundation (WPF) Custom Two-Way Binding and BindingExpression.UpdateSource()

Willie D.

Well Doc,

I took advice and removed the bogus binding on the TextBox. Sorry, I've tried so many different things that my code is a bit messy. Unfortunately, despite the fact that it was wrong, removing it didn't change the behavior.

Next I tried your suggestion of a dummy converter. Oddly (to me anyway), the value being received by the ConvertBack() method is that of the TextBox's Text property, not the value extracted from the RichTextBox's attached Content property.

I did see that the ConvertBack() was being called subsequent to the BindinExpression.UpdateSource() call although I'm still not comfortable with what exactly that mechanism is supposed to be.

Will





Re: Windows Presentation Foundation (WPF) Custom Two-Way Binding and BindingExpression.UpdateSource()

Dr. WPF

Willie D. wrote:

Oddly (to me anyway), the value being received by the ConvertBack() method is that of the TextBox's Text property, not the value extracted from the RichTextBox's attached Content property.

Perhaps I'm missing something in your scenario. According to your code, the value of the Content property *is* the value of the TextBox's Text property... they are data bound. So unless you are somehow changing the Content property through coersion or some such thing, it will always be the same.






Re: Windows Presentation Foundation (WPF) Custom Two-Way Binding and BindingExpression.UpdateSource()

Willie D.

First, my code is just a stripped down attempt to work out a strategy, a "proof of concept" to overwork that already tired expression. I used the TextBox.Text as a Souce because it would show me changes without having to mess with the debugger as much.

In the real deal, the Source will be a String (Base 64) property of a class instance and the Target will be the Document property of a RichTextBox.

It's the standard TwoWay senario: when the Window is initialized, the RichTextBox's Document will be populated with the value of the Source/String and when the RichTextBox's Document is edited the changes must be reflected back in the Source/String and eventually serialized.

Because the Document property is NOT a "Dependency Property" i.e. you can't bind to it, I'm "Attach"ing one. Now, I need to complete the plumbing so that changes are detected and reflected in both directions.





Re: Windows Presentation Foundation (WPF) Custom Two-Way Binding and BindingExpression.UpdateSource()

Dr. WPF

I understand what you're trying to accomplish. The challenge is going to be knowing when to update your attached DP. How are you going to detect when changes occur deep within the document The Blocks collection is not observable and even if it were, you wouldn't know about changes deep within blocks. And the ContentStart and ContentEnd properties are not DPs.

I'm sure its possible, but it will likely require accessing internal FlowDocument members.






Re: Windows Presentation Foundation (WPF) Custom Two-Way Binding and BindingExpression.UpdateSource()

Willie D.

Hey Doc,

That's not a problem because the RichTextBox does include a TextChanged event. In my original post I included:

Private Sub rtbxTarget_TextChanged(ByVal sender As Object, ByVal e As System.Windows.Controls.TextChangedEventArgs) Handles rtbxTarget.TextChanged

Dim bx As BindingExpression = rtbxTarget.GetBindingExpression(RichTextBoxBinder.ContentProperty)

bx.UpdateSource()

End Sub

It is firing. I have set a break point in it. I see it execute the line bx.UpdateSource(). Then, per your suggestion, it goes into the ConvertBack() method before returning from bx.UpdateSource().

MSDN documentation says of the ConvertBack() method:

"The data binding engine calls this method when it propagates a value from the binding target to the binding source."

It's all working just great except that in ConvertBack(), the value does not appear to be the current contents of the Target . . . or does it Perhaps the my assumption that the Target is the RichTextBox's Document property is wrong. Maybe it's some attribute of the Attached Dependency Property, "Content" I'll look into this.

Will





Re: Windows Presentation Foundation (WPF) Custom Two-Way Binding and BindingExpression.UpdateSource()

Dr. WPF

Okay, then what you really want to do is use your TextChanged handler to update the value of your attached Content property.

In your current scenario, the attached Content property is the target. You don't want to set a binding directly on this property at all because you will be updating the property from your TextChanged handler (and any such binding would be overwritten when you set the Content value). Rather, you want other elements to bind *to* your Content property.

There should be no need to ever call UpdateSource in your scenario. You shouldn't care about bindings at all. You just need to make sure the Content property is always accurate (e.g., it is updated whenever the Text property changes).

Unfortunately, this sounds like a very non-performant scenario. A RichTextBox could have an extremely large amount of data within its document and you are going to update your Content property for every key stroke. Perhaps you should only update your Content property on LostFocus or at least support an additional attached property that allows your users to specify how often the Content property is updated. It could even use the same enum as UpdateSourceTrigger (although the name might be a little confusing... it's really an UpdateContentTrigger).






Re: Windows Presentation Foundation (WPF) Custom Two-Way Binding and BindingExpression.UpdateSource()

Willie D.

I agree. I never intended to use UpdateTrigger=PropertyChanged in the final implementation, only for testing. And since the TextChanged() event handler gets fired on every keystroke (etc) it is inappropriate.

My first iteration was to pass an update callback delegate to the constructor (in the codebehind) and call it on the LostFocus() event handler. I felt that that would be a little too clunky for others using the control but I may have to fall back on that.

My solution (so far) is definately a KLUDGE but I don't see a more elegant one yet. I read in a Kevin Moore blog :"In WPF, there are ten ways to do everything: two that are amazing, 3 that are good, 1 that is bad, and 4 that suck." I'd say I have one of the 4 that suck at the moment.

How does this approach sound :

1. Set UpdateSourceTrigger=Explicit and create an IsDirty property.

2. Connect the TextChanged() event handler.

3. The first time the TextChanged() event handler fires, disconnects itself, and sets the isDirty property to True.

4. When the LostFocus() event handler fires, check the isDirty property. If it's True, push the contents to the Source (call UpdateSource() ), reset the IsDirty property to False, and reconnect the TextChanged() event handler.

I feel like the ingredients are there but I don't know how to exploit them yet. I've been working with WPF for 4 months and am at the point where I'm starting to get how much more there is to learn.

Maybe this shows why the RichTextBox wasn't implemented with data binding support in the first place.

BTW: Thanks for your help,

Will





Re: Windows Presentation Foundation (WPF) Custom Two-Way Binding and BindingExpression.UpdateSource()

Dr. WPF

That sounds like a really solid approach. The only thing I'd change is #4.

4. When the LostFocus() event handler fires, check the isDirty property. If it's True, update the attached Content property.

You don't need to call UpdateSource because your code shouldn't know about any bindings. If another control is bound to your Content property, it will automatically update whenever you update the Content property. So in your proposed solution, you are only updating the property on LostFocus when the control is dirty. Sounds good.

I guess #1 would need to change also since you don't need to set UpdateSourceTrigger... you just need to create the IsDirty property (which could be a readonly attached property on the RTB). And step #0 would be to set the handler on LostFocus. Smile