1

I'm creating a WPF application using MVVM. I'd like to make it so all textboxes in the application, by default trims the text.

I have tried to follow the answer here

I managed to add System.Windows.Interactivty reference via NuGet. I created a UserControl in a behaviors folder and copied the provided code. But Visual Studio cannot find suitable method to override, and AssociatedObject does not exist.

And in the XAML, it does not like <local:TrimTextBoxBehavior /> or xmlns:local="clr-namespace:StaffApp;assembly=mscorlib" or xmlns:local="clr-namespace:StaffApp.Behaviors;assembly=mscorlib"


I have tried a different method of trimming all the binded properties' setters in my Model

e.g. public string MiddleNames { get => _middleNames; set => _middleNames = value.Trim(); }

But I'm not a fan of having to do this for every property, and this causes issues when the textbox is null as defined from my XAML form:

<Label Width="100" Content="Middle name(s)" />
<TextBox Text="{Binding Employee.MiddleNames, TargetNullValue=''}" />
smally
  • 453
  • 1
  • 4
  • 14

3 Answers3

3

You need a ValueConverter or an attached behavior that you apply via a Style to all TextBox controls. Third option would be to extend the TextBox and override TextBoxBase.OnTextChanged(TextChangedEventArgs).

TextTrimBehavior:

public class TextTrimBehavior : DependencyObject
{
  #region IsEnabled attached property

  public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
    "IsEnabled", typeof(bool), typeof(TextTrimBehavior), new PropertyMetadata(false, TextTrimBehavior.OnAttached));

  public static void SetIsEnabled(DependencyObject attachingElement, bool value)
  {
    attachingElement.SetValue(TextTrimBehavior.IsEnabledProperty, value);
  }

  public static bool GetIsEnabled(DependencyObject attachingElement)
  {
    return (bool) attachingElement.GetValue(TextTrimBehavior.IsEnabledProperty);
  }

  #endregion

  private static void OnAttached(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    if (!(d is TextBox attachedTextBox))
    {
      return;
    }

    if ((bool) e.NewValue)
    {
      attachedTextBox.LostFocus += TextTrimBehavior.TrimText;
    }
    else
    {
      attachedTextBox.LostFocus -= TextTrimBehavior.TrimText;
    }
  }

  private static void TrimText(object sender, RoutedEventArgs e)
  {
    if (sender is TextBox textBox)
    {
      textBox.Text = textBox.Text.Trim();
    }
  }
}

TextBox Style:

<Style TargetType="TextBox">
    <Setter Property="TextTrimBehavior.IsEnabled" 
            Value="True" /> 
</Style> 

Since the Style has no key it will apply implicitly to all TextBox controls within the scope. To make the style global you have to put it into the App.xaml ResourceDictionary.

Extending the implicit style using Style.BasedOn:

<Style x:Key="ExplicitStyle" TargetType="TextBox" 
       BasedOn="{StaticResource {x:Type TextBox}}">
    <Setter Property="Background"
            Value="YellowGreen" />
</Style>

Alternatively you can set the attached property locally

<TextBox TextTrimBehavior.IsEnabled="True" 
         Text="{Binding Employee.MiddleNames, TargetNullValue=''}" />
BionicCode
  • 1
  • 4
  • 28
  • 44
  • This is exactly what I'm after. For some reason the intellisense doesn't like `` but the project builds ok. – smally Jun 17 '19 at 11:09
  • @smally I can't reproduce it. Have you tried to clean the solution and then rebuild it? What is the tool tip reading when hovering over it? The code is definitely fine. But I also experienced Intelisense to show false warnings sometimes. Do you use Resharper? I my case it was Resharper. – BionicCode Jun 17 '19 at 11:38
  • Yes I have cleaned the solution, and restarted VS. The tooltip says 'The name "TextTrimBehaviour" does not exist in the namespace "clr-namespace:StaffApp:Behaviours".' I don't use Resharper – smally Jun 17 '19 at 11:46
  • @smally Can you try to switch the build mode to 'Release'? Then 'Rebuild Solution' and switch back to 'Debug' mode. And rebuild. Does this help? – BionicCode Jun 17 '19 at 11:52
  • No luck there. IntelliSense still doesn't like it. – smally Jun 17 '19 at 12:04
  • @smally Maybe rename the class to trigger a new scan? Also try to delete the hidden _.vs_ folder in your project directory. Otherwise you can or have to ignore it. You know the class is there. And aslong the compiler knows this too all is well. It's just Intellisense. – BionicCode Jun 17 '19 at 13:45
  • I have tried your suggestions, and tried renaming the behaviours folder and the behaviours namespace. I only have the VS Community edition so maybe it could be that. It is definately just the intellisense so I will just need to ignore it. – smally Jun 17 '19 at 15:11
1

You could try using Converters. This way you just have to add the converter to the binding of the textbox and that would do it.

// Property in the View Model
public string Text { get;set; }

// Converter class
public class TrimTextConverter : IValueConverter {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        if (!string.IsNullOrEmpty((string)value)) {
            return ((string)value).Trim();
        }
        return string.Empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        return value;
    }
}
<!--In the xaml file-->

<!--Reference to the converter namespace-->
xmlns:converter="clr-namespace:namespace-where-converter-is-located"

<!--Adding Converter To Resource Dictionary-->
<ResourceDictionary>
    <converter:TrimTextConverter x:Key="TrimTextConverter"/>
</ResourceDictionary>

<!--TextBox-->
<TextBox Grid.Row="4" Text="{Binding Text, Converter={StaticResource TrimTextConverter}">
carlos chourio
  • 853
  • 7
  • 16
0

This is my code to trim text after lost keyboard focus

public class TextBoxTrimBehavior : Behavior<TextBox>
{
    /// <summary>
    /// Called after the behavior is attached to an AssociatedObject.
    /// </summary>
    /// <remarks>
    /// Override this to hook up functionality to the AssociatedObject.
    /// </remarks>
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.LostKeyboardFocus += AssociatedObject_LostKeyboardFocus;
    }

    void AssociatedObject_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        var tb = sender as TextBox;
        if (tb != null && tb.Text != tb.Text?.Trim())
        {
            tb.Text = tb.Text?.Trim();
        }
    }

    /// <summary>
    /// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
    /// </summary>
    /// <remarks>
    /// Override this to unhook functionality from the AssociatedObject.
    /// </remarks>
    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.LostKeyboardFocus -= AssociatedObject_LostKeyboardFocus;
    }
}

and use the behavior in xaml as follow

<TextBox>
    <i:Interaction.Behaviors>
        <behavior:TextBoxTrimBehavior />
    </i:Interaction.Behaviors>
</TextBox>
Phoeson
  • 191
  • 1
  • 6