Я хочу иметь возможность связать TextBox с UpdateSourceTrigger, установленным на LostFocus (по умолчанию), но выполнять проверку по мере того, как пользователь вводит текст. Лучшее, что я могу придумать, это обработать событие TextChanged и вызвать метод проверки для модели представления. Мне интересно, есть ли лучшее решение.
Моя модель просмотра прослушивает изменения свойств в модели, чтобы обновлять себя (включая форматирование). Я не хочу связываться с UpdateSourceTrigger, установленным в PropertyChanged, потому что это приводит к форматированию текста, как только пользователь вводит (например, пользователь может захотеть ввести «1.2», но как только он/она введет «1 " текст изменится на "1.0" из-за автоматического форматирования моделью представления).
@ASh Разве это не было бы неудобно с точки зрения удобства использования? Пользователь может ввести «1», и если он не наберет «.2» достаточно быстро, он увидит, что текст изменится на «1.0», что, я думаю, будет раздражать. Кроме того, проверка будет не немедленной, а отложенной (поскольку это происходит во время привязки).
IMO, это приятный побочный эффект, заключающийся в том, что проверка задерживается (лично мне не нравятся красные рамки/восклицательные знаки, когда я все еще печатаю). может быть, использовать StringFormat для форматирования?
Могу я спросить, какую валидацию вы подаете? Вообще мне кажется немного странным иметь такое требование. Нехорошо, чтобы модель представления управляла форматированием текста. Эта логика должна быть реализована в элементе управления. С точки зрения данных не имеет значения, рассматривается ли число 1 как 1 или 1,0. Всегда 1. В вашем случае речь идет о презентации. Вы хотите отобразить 1 как 1.0. Это полностью не зависит от модели представления, нажимающей 1 на модель. И, очевидно, это даже не зависит от проверки, что доказывает, что десятичное требование не является обязательным для достоверности данных.
Поэтому, чтобы принудительно использовать десятичные числа, вы должны переопределить UIElement.OnLostFocus
или обработать перенаправленное событие элемента LostFocus
, чтобы применить правила форматирования после завершения ввода. Это нормально, когда модель представления нормализует данные для представления: при чтении 1 из модели модель представления нормализует его до 1,0, прежде чем предоставлять данные представлению. Но модель представления не должна напрямую корректировать пользовательский ввод. Вы должны делегировать этот вид косметических средств элементу управления, который собирает входные данные и знает о связанных входных событиях. Это также решит вашу проблему.
Невозможно заранее разделить условия проверки и обновления свойств. Но вы можете реализовать собственный Control, полученный из TextBox
, и исправить это внутри.
Привяжите свой TextBox к промежуточному строковому свойству, которое существует только в вашей ViewModel, вместо того, чтобы привязывать его непосредственно к вашей модели. Затем установите свой UpdateSourceTrigger=PropertyChanged. Это позволит выполнять проверку только на промежуточном звене, а не на вашей модели. В коде программной части xaml зарегистрируйтесь на событие LostFocus вашего TextBox. Внутри этого метода обработчика событий обновите модель только в том случае, если в промежуточной строке нет ошибок, чтобы текст модели отформатировался. Наконец, в том же методе верните текст вашей модели в промежуточное значение, чтобы обновить пользовательский интерфейс.
@BionicCode Проверка проверяет, является ли введенный текст числом и находится ли он в диапазоне. Для меня модели представления — это взаимно однозначное представление представлений, так что презентацию легче тестировать. Таким образом, мои модели представления содержат в основном строковые свойства, даже если их соответствующие свойства модели являются чем-то другим (например, int, double, DateTime). Это также дает мне больший контроль над тем, что отображается; например, мы можем отобразить «-», если базовое значение модели равно double.NaN. Я рассматриваю такую логику представления как обязанность модели представления, потому что мы хотим, чтобы ее можно было протестировать.
Развивая комментарий, который я оставил, вот пример того, как это можно сделать.
К вашему сведению, я использовал пакет 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) && 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;
}
}
МояМодель.cs
public class MyModel
{
public double SomeNumber { get; set; }
}
Я думаю, что это хорошее решение, за исключением того, что я бы попытался сделать логику более многоразовой, чтобы ее можно было применять к другим текстовым полям без дублирования кода.
@redcurry Понятно. Это было решение для первого шага, а не надежное решение.
оставьте UpdateSourceTrigger = PropertyChanged и добавьте
Delay = 300
(это миллисекунды)