ValidationRule with ValidationStep="updatedValue" вызывается с BindingExpression вместо обновленного значения
Я начинаю использовать ValidationRules в своем приложении WPF, но я в замешательстве.
У меня есть следующее простое правило:
class RequiredRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (String.IsNullOrWhiteSpace(value as string))
{
return new ValidationResult(false, "Must not be empty");
}
else
{
return new ValidationResult(true, null);
}
}
}
Используется в XAML следующим образом:
<TextBox>
<TextBox.Text>
<Binding Path="Identity.Name">
<Binding.ValidationRules>
<validation:RequiredRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Это в основном работает, как я ожидал. Я был удивлен, увидев, что мой источник собственности (Identity.Name
) не был установлен; У меня есть функция отмены, которая никогда не видит изменения, и нет никакого способа отменить значение, кроме его повторного ввода (не хорошо).
Обзор привязки данных Microsoft описывает процесс проверки в нижней части, который очень хорошо объясняет это поведение. Исходя из этого, я хотел бы иметь ValidationStep
установлен в UpdatedValue
,
<validation:RequiredRule ValidationStep="UpdatedValue"/>
Вот где все становится странным для меня. Вместо вызова Validate() со значением объекта, являющимся значением свойства, которое было установлено (т. Е. Строка), я получаю System.Windows.Data.BindingExpression
! Я не вижу ничего в документации Microsoft, которая описывает это поведение.
В отладчике я вижу исходный объект (DataContext
из TextBox
), найдите путь к свойству и убедитесь, что значение установлено. Тем не менее, я не вижу хорошего способа добраться до нужного свойства в правиле валидации.
Примечание: с ValidationStep
как ConvertedProposedValue
Я получаю введенную строку (конвертер не используется), но он также блокирует обновление свойства источника при сбое проверки, как и ожидалось. С CommittedValue
Я получаю BindingExpression
вместо строки.
Здесь есть несколько вопросов:
Почему я получаю несогласованный тип аргумента, который передается в Validate() на основе параметра ValidationStep?
Как я могу получить фактическое значение из BindingExpression?
С другой стороны, есть ли хороший способ позволить пользователю вернуть TextBox к предыдущему (действительному) состоянию? (Как я уже говорил, моя собственная функция отмены никогда не видит изменений.)
4 ответа
Я решил проблему извлечения значения из BindingExpression
с небольшим ограничением.
Сначала немного более полного XAML:
<Window x:Class="ValidationRuleTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ValidationRuleTest"
Title="MainWindow" Height="100" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="String 1"/>
<TextBox Grid.Column="1">
<TextBox.Text>
<Binding Path="String1" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:RequiredRule ValidationStep="RawProposedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Text="String 2" Grid.Row="1"/>
<TextBox Grid.Column="1" Grid.Row="1">
<TextBox.Text>
<Binding Path="String2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:RequiredRule ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</Window>
Обратите внимание, что первый TextBox использует ValidationStep="RawProposedValue"
(по умолчанию), а второй использует ValidationStep="UpdatedValue"
, но оба используют одно и то же правило проверки.
Простая ViewModel (без учета INPC и других полезных вещей):
class MainWindowViewModel
{
public string String1
{ get; set; }
public string String2
{ get; set; }
}
И наконец, новый RequiredRule:
class RequiredRule : ValidationRule
{
public override ValidationResult Validate(object value,
System.Globalization.CultureInfo cultureInfo)
{
// Get and convert the value
string stringValue = GetBoundValue(value) as string;
// Specific ValidationRule implementation...
if (String.IsNullOrWhiteSpace(stringValue))
{
return new ValidationResult(false, "Must not be empty");
}
else
{
return new ValidationResult(true, null);
}
}
private object GetBoundValue(object value)
{
if (value is BindingExpression)
{
// ValidationStep was UpdatedValue or CommittedValue (Validate after setting)
// Need to pull the value out of the BindingExpression.
BindingExpression binding = (BindingExpression)value;
// Get the bound object and name of the property
object dataItem = binding.DataItem;
string propertyName = binding.ParentBinding.Path.Path;
// Extract the value of the property.
object propertyValue = dataItem.GetType().GetProperty(propertyName).GetValue(dataItem, null);
// This is what we want.
return propertyValue;
}
else
{
// ValidationStep was RawProposedValue or ConvertedProposedValue
// The argument is already what we want!
return value;
}
}
}
GetBoundValue()
Метод найдет интересующее меня значение, если получит BindingExpression, или просто отбросит аргумент, если это не так. Реальный ключ находил "Путь", а затем использовал его, чтобы получить свойство и его значение.
Ограничение: в моем первоначальном вопросе мой переплет Path="Identity.Name"
, как я копался в подобъектах моей ViewModel. Это не будет работать, так как в приведенном выше коде ожидается, что путь будет напрямую к свойству связанного объекта. К счастью, я уже сплющил свою ViewModel, так что это больше не так, но можно было бы обойти эту проблему, чтобы сначала установить в качестве подобъекта объектный текст элемента управления.
Я хотел бы отдать должное Эдуардо Бритесу, так как его ответ и обсуждение вернули меня к размышлениям об этом и дали часть его головоломки. Кроме того, в то время как я собирался полностью отказаться от ValidationRules и использовать вместо этого IDataErrorInfo, мне нравится его предложение об их совместном использовании для различных типов и сложностей проверки.
Это продолжение ответа mbmcavoy.
Я изменил GetBoundValue
метод для того, чтобы снять ограничение для путей привязки. BindingExpression удобно имеет свойства ResolvedSource и ResolvedSourcePropertyName, которые видны в отладчике, но не доступны через обычный код. Получить их с помощью рефлексии не проблема, и это решение должно работать с любым связующим путем.
private object GetBoundValue(object value)
{
if (value is BindingExpression)
{
// ValidationStep was UpdatedValue or CommittedValue (validate after setting)
// Need to pull the value out of the BindingExpression.
BindingExpression binding = (BindingExpression)value;
// Get the bound object and name of the property
string resolvedPropertyName = binding.GetType().GetProperty("ResolvedSourcePropertyName", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null).ToString();
object resolvedSource = binding.GetType().GetProperty("ResolvedSource", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null);
// Extract the value of the property
object propertyValue = resolvedSource.GetType().GetProperty(resolvedPropertyName).GetValue(resolvedSource, null);
return propertyValue;
}
else
{
return value;
}
}
Это альтернативное расширение ответа mbmcavoy и adabyron.
Чтобы снять ограничение на пути привязки, я получаю значение свойства, используя такой метод:
public static object GetPropertyValue(object obj, string propertyName)
{
foreach (String part in propertyName.Split('.'))
{
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
Теперь просто поменяй
object propertyValue = dataItem.GetType().GetProperty(propertyName).GetValue(dataItem, null);
в
object propertyValue = GetPropertyValue(dataItem, propertyName);
Связанный пост: Получить значение свойства из строки, используя отражение в C#
Чтобы ответить на ваш вопрос 2:
string strVal = (string) ((BindingExpression) value).DataItem