Search code examples
wpf

WPF RelativeSource UpdateSourceTrigger=PropertyChanged not worked


I'm using this DataTrigger:

<Window x:Class="_11_5_Style_demo4_DataTrigger.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_11_5_Style_demo4_DataTrigger"
        mc:Ignorable="d"
        Title="MainWindow" Height="130" Width="300">
    <Window.Resources>
        <local:L2BConverter x:Key="cvtr"/>
        
        <!--TextBox DataTrigger-->
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{Binding 
                    RelativeSource={x:Static RelativeSource.Self}, 
                    Path=Text.Length, 
                    Converter={StaticResource cvtr},
                    UpdateSourceTrigger=PropertyChanged}"
                             Value="false">
                    <DataTrigger.Setters>
                        <Setter Property="BorderBrush" Value="Red"/>
                        <Setter Property="BorderThickness" Value="1"/>
                    </DataTrigger.Setters>
                </DataTrigger>
            </Style.Triggers>
        </Style>
        
    </Window.Resources>
    
    <StackPanel>
        <TextBox Margin="5"/>
        <TextBox Margin="5,0"/>
        <TextBox Margin="5"/>
    </StackPanel>
</Window>

I expected that the TextBox will have the red border when typed more than 6 characters, and UpdateSourceTrigger=PropertyChanged just not work when there are more than 6 characters in the TextBox. It updated only when lost focus.

Here is the Converter:

public class L2BConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int textLength = (int)value;
        return textLength < 6 ? true : false;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

I've googled, but didn't find relative problem. Can anybody explains why this not work, am I not using it right?


Solution

  • Your trigger is working correctly, but your expectations of the result of its work are not correct. The trigger correctly sets the border brush, but the problem is that the color of the border while the TextBox has focus is not taken from the TextBox.BorderBrush brush, but from the TextBox Template constant. And you can't change it with a trigger. You need to change the template of the TextBox itself or apply another way to solve your problem.
    You can make sure that the trigger works correctly, for example, by changing the frame thickness:

    <Style TargetType="TextBox">
        <Style.Triggers>
            <DataTrigger Binding="{Binding
                                    RelativeSource={x:Static RelativeSource.Self},
                                    Path=Text.Length,
                                    Converter={StaticResource cvtr}}"
                            Value="false">
                <DataTrigger.Setters>
                    <Setter Property="BorderBrush" Value="Red"/>
                    <Setter Property="BorderThickness" Value="10"/>
                </DataTrigger.Setters>
            </DataTrigger>
        </Style.Triggers>
    </Style>
    

    Another way to implement such validation is to use a ValidationRule. But its use is only possible in a binding that you don't have. You can use a little "voodoo magic" for this:

    public class LengthValidate : ValidationRule
    {
        public LengthValidate() :base(ValidationStep.UpdatedValue, true) { }
        public int LengthLimit { get; set; }
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            int limit = LengthLimit;
            if(value is BindingExpression expression)
            {
                value = expression.GetSourceValue();
            }
            if (value is not string text)
            {
                text= value?.ToString()?? string.Empty;
            }
            return text.Length <= limit
                ? ValidationResult.ValidResult
                : new ValidationResult(false, $"String length exceeds limit={limit}.");
        }
    }
    

    The GetSourceValue method from the BindingExpressionHelper class is used.

    Style with this rule:

    <Style TargetType="TextBox">
        <Setter Property="Tag">
            <Setter.Value>
                <Binding Path="Text" RelativeSource="{RelativeSource Self}">
                    <Binding.ValidationRules>
                        <local:LengthValidate LengthLimit="6"/>
                    </Binding.ValidationRules>
                </Binding>
            </Setter.Value>
        </Setter>
    </Style>