Home > Net >  WPF MVVM Dependency property binding not working
WPF MVVM Dependency property binding not working

Time:09-13

I'm using a beavior in a TextBox control to filter input via a regex. For this I added a dependency property to get the regex value. When I set directly in xaml the value, It works properly but when I'm trying to pass it with a bind property, the regex value is not setted in the dependency property:

public class TextBoxInputRegExBehaviour : Behavior<TextBox>
{
    public static readonly DependencyProperty RegularExpressionProperty =
        DependencyProperty.Register("RegularExpression", typeof(string), typeof(TextBoxInputRegExBehaviour), new FrameworkPropertyMetadata(".*"));

    public string RegularExpression
    {
        get { return (string)GetValue(RegularExpressionProperty); 
        set { SetValue(RegularExpressionProperty, value); }
    }

    ... 
}

When using this format, no problem:

<TextBox Text="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Style="{StaticResource ValidableTextBox}">
     <i:Interaction.Behaviors>
         <behaviours:TextBoxInputRegExBehaviour RegularExpression="[0-9.] $" />
     </i:Interaction.Behaviors>
 </TextBox>

But not working with binding:

<TextBox Text="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Style="{StaticResource ValidableTextBox}">
     <i:Interaction.Behaviors>
         <behaviours:TextBoxInputRegExBehaviour RegularExpression="{Binding MyRegExString}" />
     </i:Interaction.Behaviors>
 </TextBox>

What did I wrong?

CodePudding user response:

Since Behavior<T> does not extend FrameworkElement, it does not have a DataContext. You can either use Binding.RelativeSource to traverse the visual tree to find the next parent FrameworkElement (and its DataContext property).

Or implement an attached bahavior (based on attached properties).
Since attached properties are usually attached to a FrameworkElement, you can always bind to the attaching element's DataContext (which is what you would naturally do).
You can enforce a type constraint by checking the attaching element's type in the callback and throw an exception if necessary.

Attached behavior usage example

<TextBox TextBoxInputRegExBehaviour.IsEnabled="True" 
         TextBoxInputRegExBehaviour.RegularExpression="{Binding MyRegExString}"
         Text="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" 
         Style="{StaticResource ValidableTextBox}" />

TextBoxInputRegExBehaviour.cs
Implementation of an attached behavior

public class TextBoxInputRegExBehaviour : DependencyObject
{
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached(
        "IsEnabled", 
        typeof(bool), 
        typeof(TextBoxInputRegExBehaviour), 
        new FrameworkPropertyMetadata(default(bool), OnIsEnabledChanged));
    
    public static void SetIsEnabled(DependencyObject attachingElement, bool value)
        => attachingElement.SetValue(IsEnabledProperty, value);
    public static bool GetIsEnabled(DependencyObject attachingElement)   
        => (bool)attachingElement.GetValue(IsEnabledProperty);

    public static readonly DependencyProperty RegularExpressionProperty =
        DependencyProperty.RegisterAttached(
        "RegularExpression", 
        typeof(string), 
        typeof(TextBoxInputRegExBehaviour), 
        new FrameworkPropertyMetadata(".*", OnRegularExpressionChanged));
    
    public static void SetRegularExpression(DependencyObject attachingElement, string value)
        => attachingElement.SetValue(RegularExpressionProperty, value);
    public static string GetRegularExpression(DependencyObject attachingElement)   
        => (string)attachingElement.GetValue(RegularExpressionProperty);

    private static void OnIsEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e) 
    {
        // Prior to C# 9: if (!(attachingElement is TextBox textBox))
        if (attachingElement is not TextBox textBox)
        {
            // Optionally throw an exception if this makes sense
            return;
        }

        if ((bool)e.NewValue)
        {
            // TODO::Handle behavior enabled, 
            // for example subscribe to events of the TextBox instance
        }
        else
        {
            // TODO::Handle behavior disabled, 
            // for example unsubscribe from events of the TextBox instance
        }
    }

    private static void OnRegularExpressionChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e) 
    {
        // Prior to C# 9: if (!(attachingElement is TextBox textBox))
        if (attachingElement is not TextBox textBox)
        {
            // Optionally throw an exception if this makes sense
            return;
        }


        // TODO::Handle the changed RegularExpression value
    }


    ... 
}
  • Related