Текст в TextBox с UpdateSourceTrigger = PropertyChanged не обновляется, когда принуждение ввода текста приводит к неизменному исходному значению

У меня есть текстовое поле, у которого Текстовое свойство имеет TwoWay MultiBinding с UpdateSourceTrigger с PropertyChanged. Первым связыванием является свойство зависимостей (Значение), которое имеет функцию PropertyChangedCallBack, которая округляет значение до одного десятичного знака.

Цель текстового поля - выполнять округление по типу пользователя, а не при том, что текстовое поле теряет фокус, поэтому для параметра UpdateSourceTrigger установлено значение PropertyChanged.

Проблема заключается в том, что если текст введен, который не приводит к изменению Значение, свойство Текст и Значение синхронизации. Только если операция округления вызывает изменение Значение, Текст обновляется "на лету". Например, если Текст и Значение равны 123.4, а пользовательский тип 1 после этого, Значение округляется до того же значения (123.4), но Текст показывает 123.41. Однако, если 9 после этого вводится после 4, Значение округляется до 123.5. И из-за этого фактического изменения Текст затем обновляется до того же (123.5).

Есть ли способ заставить текстовое поле обновляться из его источника, даже если источник не изменился со времени последнего триггера? Я попытался использовать BindingExpressionBase.UpdateTarget(), но это работает только тогда, когда UpdateSourceTrigger установлено значение Явно, которое не может использоваться как Значение больше не получает обновлен до подходящего времени, когда можно было вызвать UpdateTarget (например, обработчик TextInput). Я пробовал другие методы, такие как явное обновление значения Текст из привязанного Значение, чтобы временно изменить значение Значение, чтобы вызвать обновление, но эти "хаки" либо не работают, либо вызывают другие проблемы.

Любая помощь будет принята с благодарностью.

Код ниже.

фрагмент XAML

<TextBox>
  <TextBox.Text>
    <MultiBinding Converter="{local:NumberFormatConverter}"
                  UpdateSourceTrigger="Explicit"
                  Mode="TwoWay">
      <Binding Path="Value"
               RelativeSource="{RelativeSource AncestorType={x:Type Window}}"
               Mode="TwoWay" />
    </MultiBinding>
  </TextBox.Text>
</TextBox>

фрагмент С#

public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value", typeof(decimal), typeof(MainWindow),
        new FrameworkPropertyMetadata(0m,
        new PropertyChangedCallback(OnValueChanged)));

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    obj.SetValue(ValueProperty, Math.Round((decimal)args.NewValue, 1));
}

Требуется класс конвертера

public class NumberFormatConverter : MarkupExtension, IMultiValueConverter
{
    public static NumberFormatConverter Instance { private set; get; }

    static NumberFormatConverter()
    {
        Instance = new NumberFormatConverter();
    }

    public override object ProvideValue(IServiceProvider serviceProvider_)
    {
        return Instance;
    }

    #region Implementation of IMultiValueConverter

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return values[0].ToString();
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        var result = 0m;
        if (value != null)
        {
            decimal.TryParse(value.ToString(), out result);
        }
        return new object[] { result };
    }

    #endregion
}

Ответы

Ответ 1

Я немного поработал в Интернете, и выяснилось, что это было нарушено в WPF 4. Кто-то с почти идентичной проблемой для меня опубликовал здесь: http://www.go4answers.com/Example/textbox-shows-old-value-being-coerced-137799.aspx

"Ответ 8" утверждает, что это было нарушено в WPF 4 и предлагает решение, которое фактически использует UpdateSourceTrigger="Explicit", но для обработки события TextChanged и вызова BindingExpression.UpdateSource(), чтобы принудительно изменить в текстовом поле, которое будет отображаться в базовом значении, как если бы UpdateSourceTrigger="PropertyChanged", в соответствии с этим сообщением: Принудительная работа WPF TextBox больше не работает в .NET 4.0

Я реализовал это, но вот и появились побочные эффекты, в частности, что каждое нажатие клавиши заставляло каретку перейти к началу текстового поля из-за обновления источника и создания события PropertyChanged. А также любые начальные или конечные нули или десятичные числа, введенные с намерением ввести дополнительные цифры, будут немедленно уничтожены. Таким образом, простое условие для проверки проанализированного десятичного значения текстового поля по сравнению с базовым значением разрешило это.

Следующий обработчик событий - это все, что необходимо:

private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    var tb = (TextBox)e.Source;
    MultiBindingExpression binding = BindingOperations.GetMultiBindingExpression(tb, TextBox.TextProperty);

    decimal result = 0m;
    decimal.TryParse(tb.Text, out result);

    if ((decimal)GetValue(ValueProperty) != result && binding != null)
    {
        int caretIndex = tb.CaretIndex;
        binding.UpdateSource();
        tb.CaretIndex = caretIndex;
    }
}