metalheart
metalheart

Reputation: 3809

WPF Binding: How to prevent target update when source value does not change after input

In TextBox binding with a converter I am experiencing a problem: when the user changes the text, the value flows not only to the source, but always also back into the UI - also in cases when the converted value is exactly the same as before the input, i.e. source value does not change.

Can this be prevented?

My scenario is this: I want the user to input a space-separated list of academic titles that are represented by numeric values from a codelist in my model. The problem occurs after there is a valid title and the user presses space in order to be able to write a second title - since the converter is tolerant of spaces, "TITLE1 " is converted to exactly the same number as "TITLE1", but since an update of the target is triggered, the textbox input changes back to "TITLE1", effectively preventing the user to add any further input.

The model value is a String, XAML binding is this:

<TextBox Grid.Row="3" Grid.Column="1">
  <TextBox.Text>
    <Binding Path="Model.TitleValuesDelimitedString" Delay="500"
             Converter="{StaticResource TitleValuesDelimitedStringToDisplayStringConv}" 
             ConverterParameter="{x:Static local:UICodeLists.TitleCodeList}"
             UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
         <vrules:TitlesSpaceSeparated />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

Notes:

UPDATE: Converter code:

public class TitleValuesDelimitedStringToDisplayStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var ciselnik = parameter as Ciselnik;
        var skrDelimitedList = value as string;
        if (string.IsNullOrEmpty(skrDelimitedList))
            return null;

        var skrList = new List<string>();
        Person.ApplyDelimitedString(skrList, skrDelimitedList);

        StringBuilder sb = new StringBuilder();
        foreach (var skr in skrList)
        {
            if (sb.Length > 0)
                sb.Append(' ');
            sb.Append(ciselnik.FindBySkratka(skr).DisplayName);
        }
        return sb.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var ciselnik = parameter as Ciselnik;
        var input = value as string;
        if (string.IsNullOrEmpty(input))
            return Person.CreateDelimitedString(new string[0]);

        List<string> skrList = new List<string>();
        foreach (var titul in input.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
        {
            if (ciselnik.ContainsDisplayName(titul))
                skrList.Add(ciselnik.FindByDisplayName(titul).Skratka);
        }
        return Person.CreateDelimitedString(skrList);
    }
}

Upvotes: 0

Views: 1838

Answers (1)

christoph
christoph

Reputation: 1051

Try removing UpdateSourceTrigger="PropertyChanged". The default will be applied that is LostFocus.

If a single source update on losing focus doesn´t suit your needs you could compare the trimmed versions of your private TitleValuesDelimitedString field and the value within the setter. If those are alike don´t raise PropertyChanged. Try something like this:

    private string titleValuesDelimitedString;
    public string TitleValuesDelimitedString
    {
        get { return titleValuesDelimitedString; }
        set
        {
            string fieldComparable = this.titleValuesDelimitedString ?? string.Empty;
            string valueComparable = value ?? string.Empty;

            if (fieldComparable.Trim() != valueComparable.Trim())
            {
                this.titleValuesDelimitedString = value;
                this.OnPropertyChanged("TitleValuesDelimitedString");
            }
        }
    }

Update:

I edited your converter like this:

public class TitleValuesDelimitedStringToDisplayStringConverter : IValueConverter
{
    private string latestValueSendToSource = null;

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var ciselnik = parameter as Ciselnik;
        var skrDelimitedList = value as string;
        if (string.IsNullOrEmpty(skrDelimitedList))
            return null;

        var skrList = new List<string>();
        Person.ApplyDelimitedString(skrList, skrDelimitedList);

        StringBuilder sb = new StringBuilder();
        foreach (var skr in skrList)
        {
            if (sb.Length > 0)
                sb.Append(' ');
            sb.Append(ciselnik.FindBySkratka(skr).DisplayName);
        }
        string goingToSendToTarget = sb.ToString();

        if (this.latestValueSendToSource != null && this.latestValueSendToSource.Trim().Equals(goingToSendToTarget)
        {
            return this.latestValueSendToSource;
        }
        else
        {
            return goingToSendToTarget;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {            
        var ciselnik = parameter as Ciselnik;
        var input = value as string;
        this.latestValueSendToSource = input;

        if (string.IsNullOrEmpty(input))
            return Person.CreateDelimitedString(new string[0]);



        List<string> skrList = new List<string>();
        foreach (var titul in input.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
        {
            if (ciselnik.ContainsDisplayName(titul))
                skrList.Add(ciselnik.FindByDisplayName(titul).Skratka);
        }

        return Person.CreateDelimitedString(skrList);
    }
}

Note the usage of the variable latestValueSendToSource. For that to not lead to unexpected behaviour you should make clear the binding gets its own instance of the converter:

<TextBox Grid.Row="3" Grid.Column="1">
    <TextBox.Text>
    <Binding Path="Model.TitleValuesDelimitedString" Delay="500"              
         ConverterParameter="{x:Static local:UICodeLists.TitleCodeList}"
         UpdateSourceTrigger="PropertyChanged">
    <Binding.ValidationRules>
     <vrules:TitlesSpaceSeparated />
  </Binding.ValidationRules>
  <Binding.Converter>
      <local:TitleValuesDelimitedStringToDisplayStringConverter />
  </Binding.Converter>
</Binding>

Hope that suits your needs.

Upvotes: 1

Related Questions