Reputation: 3809
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:
INotifyPropertyChanged
in a way that it does not fire when value does not change, with no successUpdateSourceProperty=Explicit
+ UpdateSource()
)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
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