Привязка OneWayToSource к одноразовой инициализации целевого объекта

#wpf #data-binding

#wpf #привязка к данным

Вопрос:

У меня есть DataGrid с редактируемыми ячейками, привязанными к их соответствующим значениям в модели представления соответствующих элементов.

Первоначально данные загружаются и отображаются пользователю, который затем может редактировать данные в таблице.

Привязка работает так, как должна (в моем случае с UpdateSourceTrigger=OnPropertyChanged ), но из-за преобразований между double (view model) и string (UI) TwoWay привязка может вызвать раздражающие ошибки пользовательского интерфейса, такие как исчезновение десятичных разделителей или нулей после десятичной точки при вводе пользователем.

Два ошибочных решения:

  • Создание свойства a string в модели представления и выполнение необходимых преобразований внутри модели представления.
    • Проблема: возникает странная проблема несовместимости культур между пользовательским интерфейсом и моделью представления (и я не ожидаю, что модель представления будет знать культуру пользовательского интерфейса)
  • Использование OneWayToSource привязки. Это устраняет все ошибки пользовательского интерфейса, поскольку виртуальная машина перестает отправлять обратно проанализированные и повторно преобразованные значения.
    • Проблема: я не могу (или не знаю, как) инициализировать значения в таблице с загруженными данными.

Итак, могу ли я каким-то образом использовать OneWayToSource привязку «после» OneTime привязки или каким-то образом суммировать их?

Я попытался привязать FallbackValue и TargetNullValue к исходным значениям, но они не принимают привязки.

Комментарии:

1. Установка UpdateSourceTrigger = LostFocus или явно частично решает проблему.

2. » Привязка к двум путям может вызвать раздражающие ошибки пользовательского интерфейса, такие как исчезновение десятичных разделителей при вводе пользователем » — что именно вы имеете в виду под этим? Это должно быть исправлено, потому что привязка к двум путям — это правильный путь.

3. Я разобрался с этой проблемой и обнаружил, что это происходит не из-за изменения свойств. Даже если событие не происходит (поскольку значение не изменяется) Привязка по-прежнему отбрасывает последнюю точку и нули после точки. Скорее всего, это связано с внутренней реализацией PropertyDescriptor — привязки работают через него. Если вы говорите по-русски, я недавно дал объяснение их работы здесь cyberforum.ru/wpf-silverlight/thread2650880.html

Ответ №1:

Исчезновение десятичного знака — это «функция», которую они ввели, пытаясь исправить что-то еще. Я думал, что это был .Net 4.0, это было введено, и люди начали замечать, что это было кардинальное изменение, но документация, похоже, подразумевает .Net 4.5.

Обычно это происходит потому, что вы устанавливаете updatesourcetrigger=propertychanged.

Простое исправление часто заключается в том, чтобы просто удалить это.

Потому что

 , UpdateSourceTrigger=LostFocus
  

Это поведение по умолчанию для привязки текста к текстовому полю.

В качестве альтернативы, вы могли бы поэкспериментировать с KeepTextBoxDisplaySynchronizedWithTextProperty

https://msdn.microsoft.com/en-us/library/system.windows.frameworkcompatibilitypreferences.keeptextboxdisplaysynchronizedwithtextproperty(v=vs.110).aspx

     public MainWindow()
    {
        FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
        InitializeComponent();
    }
  

Вы можете установить это в Mainwindow перед отображением чего-либо.

Комментарии:

1. Я пробовал ваши решения, LostFocus одно из них является лучшим, но, к сожалению, оно все равно удалит весь текст, если пользователь уйдет с недопустимыми строками. Если сетка содержит только одну строку, щелчок за пределами строки не меняет фокус, есть определенные места, на которые необходимо щелкнуть. — Для решения для обеспечения совместимости он также удалял весь текст всякий раз, когда проверка завершалась неудачей.

Ответ №2:

Я обнаружил хитрый обходной путь, который включает использование двух свойств, исходного и строки, выделенной пользователю, для плавного поведения. Используйте для этой цели специальный конвертер. (Я думаю, что я приму это в качестве шаблона для будущих случаев)

Это работает в случаях, когда модель представления не изменяет свойство, только пользователь изменяет свойство. (Если вы хотите действительно двустороннее взаимодействие, при котором модель представления также изменяет свойство, вам нужно устанавливать свойство string равным null всякий раз, когда вам нужно изменить свойство)

В модели представления:

Отличия от стандартного кода заключаются в:

  • добавление строкового свойства без логики
  • добавление уведомления для этого свойства при изменении исходного свойства

Код:

 private double? _TheProperty;

public double? TheProperty { get { return _TheProperty ; } set { SetTheProperty (value); } }
public string ThePropertyUserString { get; set; } //for UI only!!! Don't change via code

private void SetTheProperty(double? value)
{
    if (value == null)
    {
        //implement validation errors if necessary
        //using IDataErrorInfo and ValidatesOnDataErrors
        //this type of validation is the only I found that helps enabling/disabling command buttons
    }

    //do your logic
    _TheProperty = value;

    //notify
    if (PropertyChanged != null)
    {
        PropertyChanged("TheProperty", ...);
        PropertyChanged("ThePropertyUserString", ...);
    }
}
  

В XAML:

 <TextBox Style="{StaticResource ErrorStyle}">
    <TextBox.Text>
        <MultiBinding UpdateSourceTrigger="PropertyChanged" 
                      Mode="TwoWay"
                      Converter="{StaticResource DoubleUserStringConverter}">
            <Binding Path="TheProperty" ValidatesOnDataErrors="True"/>
            <Binding Path="ThePropertyUserString"/>
         /MultiBinding>
    </TextBox.Text>
</TextBox>
  

Конвертер:

 /// <summary>
/// multibinding, first binding is double? and second is string, both representing the same value
/// the double? value is for the viewmodel to use as normally intended
/// the string value is for the user not to have ui bugs
/// </summary>
class DoubleUserStringConverter : IMultiValueConverter
{
    private OriginalConverterYouWanted converter;
    public DoubleUserStringConverter()
    {
        converter = new OriginalConverterYouWanted(); //single binding, not multi
        //for types "double" in the view model and "string" in the UI
        //in case of invalid strings, the double value sent to UI is null
    }

    //from view model to UI:
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[1] == null)  //null string means UI initialization, use double
            return converter.Convert(values[0], targetType, parameter, culture);
        else 
            return values[1]; //in the rest of the time, send user string to UI
    }

    //from UI to view model
    public object[] ConvertBack(object value, Type[] targetTypes, 
                                object parameter, CultureInfo culture)
    {
        return new object[] { 
            converter.ConvertBack(value, targetTypes[0], parameter, culture), //can be null
            value //string is always sent as is, no changes to what the user types
        };
    }
}
  

Комментарии:

1. Это кажется большой работой по сравнению с установкой одного свойства.

2. Это так, но это комплексное решение, у всех остальных есть раздражающие ошибки пользовательского интерфейса. Учитывая, что у вас обычно уже есть конвертер и логика get / set / notify, на самом деле это не так уж много.