views:

201

answers:

1

Using WPF, I want to apply a converter within the binding of the Text property within all my TextBoxes.
The following works for a single TextBox:

<TextBox Style="{StaticResource TextBoxStyleBase2}" 
                             Text="{Binding Text, Converter={StaticResource MyConverter}}">
                    </TextBox>

However, our TextBoxes uses a style with a Control Template that looks like this:

<Grid>
            <Border x:Name="Border"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    CornerRadius="{StaticResource DefaultCornerRadius}">
                <Grid>
                    <Border BorderThickness="1">
                        <ScrollViewer x:Name="PART_ContentHost" Margin="0"/>
                    </Border>
                </Grid>
            </Border>
        </Grid>

How can I apply my converter using this Template? Thanks!

+1  A: 

Any attempt to modify TextBox properties from inside the ControlTemplate will be messy, so I recommend you do it in the Style instead of the ControlTemplate if at all possible. I'll start by explaining how to do it with a Style then explain how to adapt the technique for use in a ControlTemplate if necessary.

What you need to do is create an attached property that can be used like this:

<Style x:Name="TextBoxStyleBase2" TargetType="TextBox">
  <Setter Property="local:ConverterInstaller.TextPropetyConverter"
          Value="{StaticResource MyConverter}" />
  ...
</Style>

The ConverterInstaller class has a simple attached property that installs the converter into any Binding initially set on the TextBox:

public class ConverterInstaller : DependencyObject
{
  public static IValueConverter GetTextPropertyConverter(DependencyObject obj) { return (IValueConverter)obj.GetValue(TextPropertyConverterProperty); }
  public static void SetTextPropertyConverter(DependencyObject obj, IValueConverter value) { obj.SetValue(TextPropertyConverterProperty, value); }
  public static readonly DependencyProperty TextPropertyConverterProperty = DependencyProperty.RegisterAttached("TextPropertyConverter", typeof(IValueConverter), typeof(Converter), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) =>
      {
        var box = (TextBox)obj;
        box.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
          {
            var binding = BindingOperations.GetBinding(box, TextBox.TextProperty);
            if(binding==null) return;

            var newBinding = new Binding
            {
              Converter = GetTextPropertyConverter(box),

              Path = binding.Path,
              Mode = binding.Mode,
              StringFormat = binding.StringFormat,
            }
            if(binding.Source!=null) newBinding.Source = binding.Source;
            if(binding.RelativeSource!=null) newBinding.RelativeSource = binding.RelativeSource;
            if(binding.ElementName!=null) newBinding.ElementName = binding.ElementName;

            BindingOperations.SetBinding(box, TextBox.TextProperty, newBinding);
          }));
      }
  });
}

The only complexity here is:

  • The use of Dispatcher.BeginInvoke to ensure the binding update happens after the XAML finishes loading and all styles are applied, and
  • The need to copy Binding properties into a new Binding to change the Converter, since the original Binding is sealed

To attach this property to an element inside the ControlTemplate instead of doing it in the style, the same code is used except the original object passed into the PropertyChangedCallback is cast var element = (FrameworkElement)obj; and inside the Dispatcher.BeginInvoke action the actual TextBox is found with var box = (TextBox)element.TemplatedParent;

Ray Burns
Many thanks! That worked great. I only had to modified the copy of the new binding since, Source and RelativeSource cannot both be set at the same time (even tough they are set to NULL). So I added some checks before assigning them. If NULL, I don't set the property.
joerage
Thanks for the feedback. I've updated my answer to do the same.
Ray Burns