В WPF, как я могу иметь привязку к LostFocus, но проверку на PropertyChanged?

#c# #wpf #data-binding

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

Вопрос:

Я хочу иметь возможность привязывать текстовое поле с помощью UpdateSourceTrigger, для которого установлено значение LostFocus (по умолчанию), но выполнять проверку по мере ввода пользователем текста. Лучшее, что я могу придумать, — это обработать событие TextChanged и вызвать метод проверки в модели представления. Мне интересно, есть ли лучшее решение.

Моя модель представления прослушивает изменения свойств в модели, чтобы обновить себя (включая форматирование). Я не хочу связываться с UpdateSourceTrigger, для которого установлено значение PropertyChanged, потому что это приводит к форматированию текста сразу после ввода пользователем (например, пользователь может захотеть ввести «1.2», но как только он / она вводит «1», текст меняется на «1.0» из-заавтоматическое форматирование с помощью модели представления).

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

1. сохраняйте UpdateSourceTrigger = PropertyChanged и добавляйте Delay = 300 (это миллисекунды)

2. @ASh Разве это не было бы неудобно с точки зрения удобства использования? Пользователь может ввести «1», и если он не введет «.2» достаточно быстро, он увидит изменение текста на «1.0», что, я думаю, будет раздражать. Кроме того, проверка будет не немедленной, а отложенной (поскольку это происходит во время привязки).

3. ИМО, приятным побочным эффектом является задержка проверки (лично мне не нравятся красные границы / восклицательные знаки, когда я все еще печатаю ). может быть, использовать StringFormat для форматирования?

4. Могу ли я спросить, какую проверку вы применяете? В общем, мне кажется немного странным иметь такое требование. Нехорошо, когда модель представления управляет форматированием текста. Эта логика должна быть реализована в элементе управления. С точки зрения данных не имеет значения, обрабатывается ли число 1 как 1 или 1.0. Это всегда 1. В вашем случае речь идет о презентации. Вы хотите отобразить 1 как 1.0. Это полностью не зависит от модели представления, добавляющей 1 к модели. И, очевидно, это даже не зависит от проверки, которая доказывает, что десятичное требование не является обязательным для достоверности данных

5. Итак, чтобы принудительно использовать десятичные числа, вы должны переопределить UIElement.OnLostFocus или обработать перенаправленное событие элемента LostFocus , чтобы применить правила форматирования после завершения ввода. Хорошо, если модель представления нормализует данные для представления: при чтении 1 из модели модель представления нормализует его до 1.0, прежде чем предоставлять данные для представления. Но модель представления не должна напрямую настраивать пользовательский ввод. Вы должны делегировать такого рода косметические средства элементу управления, который собирает входные данные и осведомлен о связанных событиях ввода. Это также решит вашу проблему.

Ответ №1:

Развивая комментарий, который я оставил, вот пример того, как это можно сделать.

К вашему сведению, я использовал пакет nuget MVVMLight для подключения.

MainWindow.xaml

 <StackPanel>
    <TextBox x:Name="myTextBox" Text="{Binding SomeNumberViewModel.IntermediateText, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Width="100" Margin="5"/>
    <Button Content="Hi" HorizontalAlignment="Center" Padding="5,15" Margin="5"/>
</StackPanel>
 

MainWindow.xaml.cs

 public MainViewModel ViewModel
{
    get
    {
        return this.DataContext as MainViewModel;
    }
}
public MainWindow()
{
    InitializeComponent();
    this.myTextBox.LostFocus  = MyTextBox_LostFocus;
}

private void MyTextBox_LostFocus(object sender, RoutedEventArgs e)
{
    // If the IntermediateText has no validation errors, then update your model.
    if (string.IsNullOrEmpty(this.ViewModel.SomeNumberViewModel[nameof(this.ViewModel.SomeNumberViewModel.IntermediateText)]))
    {
        // Update your model and it gets formatted result
        this.ViewModel.SomeNumberViewModel.ModelValue = this.ViewModel.SomeNumberViewModel.IntermediateText;

        // Then, update your IntermediateText to update the UI.
        this.ViewModel.SomeNumberViewModel.IntermediateText = this.ViewModel.SomeNumberViewModel.ModelValue;
    }
}
 

MainViewModel.cs

 public class MainViewModel : ViewModelBase
{
    private SomeNumberViewModel someNumberViewModel;

    public string MyTitle { get => "Stack Overflow Question 65279367"; }

    public SomeNumberViewModel SomeNumberViewModel
    {
        get
        {
            if (this.someNumberViewModel == null)
                this.someNumberViewModel = new SomeNumberViewModel(new MyModel());
            return this.someNumberViewModel;
        }
    }
}
 

SomeNumberViewModel.cs

 public class SomeNumberViewModel : ViewModelBase, IDataErrorInfo
{
    public SomeNumberViewModel(MyModel model)
    {
        this.Model = model;
    }

    private string intermediateText;
    public string IntermediateText { get => this.intermediateText; set { this.intermediateText = value; RaisePropertyChanged(); } }
    public string ModelValue 
    { 
        get => this.Model.SomeNumber.ToString("0.00"); 
        
        set 
        {
            try
            {
                this.Model.SomeNumber = Convert.ToDouble(value);
                RaisePropertyChanged();
            }
            catch
            {
            }
        } 
    }

    public MyModel Model { get; private set; }
    
    public string Error { get => null; }

    public string this[string columnName]
    {
        get
        {
            switch (columnName)
            {
                case "IntermediateText":
                    if (!string.IsNullOrEmpty(this.IntermediateText) amp;amp; FormatErrors(this.IntermediateText))
                        return "Format errors";
                    break;
            }

            return string.Empty;
        }
    }

    /// <summary>
    /// Only allow numbers to be d , or d .d 
    /// For Example: 1, 1.0, 11.23, etc.
    /// Anything else is a format violation.
    /// </summary>
    /// <param name="numberText"></param>
    /// <returns></returns>
    private bool FormatErrors(string numberText)
    {
        var valid = (Regex.IsMatch(numberText, @"^(d |d .d )$"));
        return !valid;
    }
}
 

MyModel.cs

 public class MyModel
{
    public double SomeNumber { get; set; }
}
 

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

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

2. @redcurry понял. Это было решение первого шага, а не надежное решение.