Search code examples
c#wpfbindingtextboxstring-formatting

WPF TextBox set common StringFormat for all controls


Is it possible to make something to set StringFormat of Binding property for all (or part of) TextBoxes in project? Yes, I can write something like

<TextBox x:Name="SetPosition" Style="{StaticResource TextBoxSmallStyle}" Text="{Binding SetPosition, Mode=TwoWay, StringFormat='{}{0:#0.0}'}" />

but setting it for many identical TextBoxes it too boring ))) As you see I am used Style which include Heigth, Width e.t.c. I can't override Binding property in Style because need to use this Style with any Binding Path properties but Binding overrides only wholly. Are where any workarounds exists? P.S. I am already using not standard TextBox but overrided control for my special functionality. Can I override Binding for using compoment's code-behind, maybe?


Solution

  • Since you said you have already extended TextBox you can simply enhance that type and modify the existing Binding on the TextBox.Text property.

    The following example shows how the ExtendedTextBox allows to provide a string format expression which is assigned to the current Binding.
    Any locally set Binding.StringFormat values are overwritten with the ExtendedTextBox.TextStringFormat property value.
    If you also want to apply the string formatting when there is no Binding set on the Text property, you would have to monitor the Text property for changes.

    public class ExtendedTextBox : TextBox
    {
      public string TextStringFormat
      {
        get => (string)GetValue(TextStringFormatProperty);
        set => SetValue(TextStringFormatProperty, value);
      }
    
      public static readonly DependencyProperty TextStringFormatProperty = DependencyProperty.Register(
        "TextStringFormat",
        typeof(string),
        typeof(ExtendedTextBox),
        new PropertyMetadata(default(string), OnTextStringFormatChanged));
    
      private static void OnTextStringFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
        var extendedTextBox = (ExtendedTextBox)d;
        string oldFormatString = (string)e.OldValue;
        string newFormatString = (string)e.NewValue;
        if (!string.IsNullOrWhiteSpace(newFormatString))
        {
          extendedTextBox.ApplyBindingStringFormat();
        }
        else if (!string.IsNullOrWhiteSpace(oldFormatString))
        {
          extendedTextBox.ClearTextPropertyFormat();
        }
      }
    
      private bool isFormattingTextProperty;
    
      public ExtendedTextBox()
      {
        this.Loaded += OnLoaded;
      }
    
      private void OnLoaded(object sender, RoutedEventArgs e)
        => FormatTextProperty();
    
      protected override void OnTextChanged(TextChangedEventArgs e)
      {
        if (this.isFormattingTextProperty)
        {
          return;
        }
    
        base.OnTextChanged(e);
    
        BindingBase textBinding = BindingOperations.GetBindingBase(this, TextBox.TextProperty);
        if (textBinding is null)
        {
          // No binding attached. Format numeric text directly.
          FormatTextProperty();
        }
      }
    
      private void FormatTextProperty()
      {
        if (string.IsNullOrWhiteSpace(this.TextStringFormat))
        {
          return;
        }
    
        if (!string.IsNullOrWhiteSpace(this.Text))
        {
          string formattedText;
          if (double.TryParse(this.Text, CultureInfo.CurrentCulture, out double number))
          {
            formattedText = string.Format(CultureInfo.CurrentCulture, this.TextStringFormat, number);
          }
          else
          {
            formattedText = string.Format(CultureInfo.CurrentCulture, this.TextStringFormat, this.Text);
          }
    
          this.isFormattingTextProperty = true;
          int currentCaretIndex = this.CaretIndex;
          SetCurrentValue(TextBox.TextProperty, formattedText);
          this.CaretIndex = currentCaretIndex;
          this.isFormattingTextProperty = false;
        }
      }
    
      // Binding attached. Apply formatting via data binding.
      private void ApplyBindingStringFormat()
      {
        BindingBase textBinding = BindingOperations.GetBindingBase(this, TextBox.TextProperty);
        if (textBinding is null)
        {
          return;
        }
    
        if (string.IsNullOrWhiteSpace(textBinding.StringFormat))
        {
          textBinding = CloneObject(textBinding);
          textBinding.StringFormat = this.TextStringFormat;
          _ = SetBinding(TextBox.TextProperty, textBinding);
        }
      }
    
      private void ClearTextPropertyFormat()
      {
        BindingBase textBinding = BindingOperations.GetBindingBase(this, TextBox.TextProperty);
        if (textBinding is null)
        {
          return;
        }
    
        textBinding = CloneObject(textBinding);
        if (!string.IsNullOrWhiteSpace(textBinding.StringFormat))
        {
          textBinding.StringFormat = null;
          _ = SetBinding(TextBox.TextProperty, textBinding);
        }
      }
    
      private TObject CloneObject<TObject>(TObject objectToClone)
      {
        string xamlObject = XamlWriter.Save(objectToClone);
        return (TObject)XamlReader.Parse(xamlObject);
      }
    }
    

    App.xaml
    Define a global Style that defines the actual string format:

    <Style TargetType="ExtendedTextBox">
      <Setter Property="TextStringFormat"
              Value="{}{0:#0.0}" />
    </Style>