BindableRun

posted 2007-03-20

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 FilesMicrosoft SDKsWindowsv6.0Bin 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.