A useful feature that was left out of the first version of WPF is the ability to databind the value of Run.Text. I was still around when this feature was (unfortunately) cut — but don’t despair! It’s not actually that hard to write yourself.

We’ll do this by creating a subclass of Run, which I’ve creatively named BindableRun.

using System;
using System.Windows;
using System.Windows.Documents;

namespace BindableText
{
  /// <summary>
  /// A subclass of the Run element that exposes a DependencyProperty property
  /// to allow data binding.
  /// </summary>
  public class BindableRun : Run
  {
    public static readonly DependencyProperty BoundTextProperty = DependencyProperty.Register("BoundText", typeof(string), typeof(BindableRun), new PropertyMetadata(new PropertyChangedCallback(BindableRun.onBoundTextChanged)));

    private static void onBoundTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      ((Run) d).Text = (string) e.NewValue;
    }

    public String BoundText
    {
      get { return (string)GetValue(BoundTextProperty); }
      set { SetValue(BoundTextProperty, value); }
    }
  }
}

The code is pretty straightforward, we create a new DependencyProperty in the usual manner. Then we add a PropertyChangedCallback that manually sets the Text property. That’s it! We rely on the base class to take care of everything else.

You can use this in DLL, by declaring an XML namespace and linking it to a CLR namespace, as in the example below:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:bt="clr-namespace:BindableText;assembly=BindableText">
  <FlowDocumentScrollViewer>
    <FlowDocument>
      <Paragraph>
        You can control the value of this text through the TextBox below: <bt:BindableRun FontWeight="Bold" BoundText="{Binding ElementName=tb, Path=Text}" />
      </Paragraph>
      <BlockUIContainer>
        <TextBox Name="tb" Text="This is text with spaces that wraps across a line … like this!"/>
      </BlockUIContainer>
    </FlowDocument>
  </FlowDocumentScrollViewer>
</Page>

If you’re running this in XamlPad, you’ll have to make sure that the BindableRun.dll file is in the same directory as XamlPad itself. You can do this by either creating a new copy of XamlPad.exe or copying the BindableRun.dll into the directory where you keep XamlPad (C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin in my case).

If you want to download this class, I’ve created a project (with the DLL) that you can download and use: BindableText.zip (13 KB).

Update 3/21: Paul Stovell mentions an alternate technique that creates an attached property instead of a subclass to achieve the same effect.

  • So how come it was cut, given it's quite straightforward? I've ended up having writing this thing (well, something very like it) a couple of times now on real projects, and it's always perplexed me that it didn't work like this out of the box.

    Given that so many of the properties are DPs on most elements, it seems almost perverse that an exception was made in this case.
  • You know how it is in software, trade-offs have to be made. In this case it was the test cost (lots of tricky cases when you bring in databinding). Given how easy it is to implement, we chose to cut this in order to gain other (harder for a developer to add, but easier to test) features.
  • I had the same thoughts as Ian when I read this post. I wrote a similar class earlier this year. It seems so simple, that's what makes you wonder why it's not in the framework.
  • Robert Zurer
    Your post has helped me on the way to solving a rather knotty problem. I have tried to implement two-way binding with no success using FrameworkPropertyMetadataOptions.BindsTwoWayByDefault when registering the BoundTextProperty. Any ideas? Thanks so much.
  • Hey Robert,
    Can you please share a reduced repro of your sample? Setting BindsTwoWayByDefault to true when registering the DP should be enough to make that DP two way data bound by default. I'm not aware of any gotchas or bugs in this scenario.
    Thanks!
    Bea
  • Great! I've been looking for something like that. Thanks for the tip!

    Laurent
  • Bea,
    Thanks so much for your comment and for your wonderful, indispensable blog.

    Two way binding does work as expected. When I set the BoundText procedurally, all was well.

    The problem was that I had placed the BindableRun in a Paragraph in a FlowDocument in a RichTextBox, thinking that, magically, the BindableRun would receive text input from the RichTextBox. I just have to figure out how to route the input.
  • Devaraj
    I get this error "{"Collection was modified; enumeration operation may not execute."}" when i use the BindableRun element more than once in my flow document - if i use only use it keeps quiet - any clues. ???
  • Brian
    I have the same question as Robert Zurer. Anyone have a sample of working 2-way binding example? Each of my BindableRun tags is in a Paragraph. I'm styling a ListBox like this:


















    Looks great and binds great, but none of the changes make it back to the Binding Source.

    Thanks.
  • I think we have a solution to the "Collection was modified" issue:

    http://code.logos.com/blog/2008/01/data_binding...
  • Patrick
    Thanks for sharing the BindableRun.

    Is there anyone who created a two-way binding successfully?
blog comments powered by Disqus