powered by simpleCommunicator - 2.0.49     © 2025 Programmizd 02
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / ValidationRule+IValueConverter: сброс source в null при ошибке+отображение ошибки - как?
4 сообщений из 4, страница 1 из 1
ValidationRule+IValueConverter: сброс source в null при ошибке+отображение ошибки - как?
    #39774891
WinterGraveyard
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Есть вот такой схематичный код:
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
<Window.DataContext>
  <Binding RelativeSource="{RelativeSource Self}" />
</Window.DataContext>

<Window.Resources>
  <ControlTemplate x:Key="TextBoxErrorTemplate">
    <StackPanel>
      <Border BorderBrush="#FFdc000c" VerticalAlignment="Top">
        <Grid>
          <AdornedElementPlaceholder x:Name="adorner" Margin="-1" />
        </Grid>
      </Border>
      <Border
        x:Name="errorBorder"
        Background="#FFdc000c"
        Margin="0,2,0,0"
        MinHeight="24"
        Width="{Binding ElementName=adorner, Path=AdornedElement.ActualWidth}">
        <TextBlock
          Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
          Foreground="White"
          Margin="5,2,5,3"
          TextWrapping="Wrap"
          VerticalAlignment="Stretch"/>
      </Border>
    </StackPanel>
  </ControlTemplate>
</Window.Resources>

<Grid Margin="5">
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition />
  </Grid.RowDefinitions>
  <TextBox
    Margin="5"
    Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}">
    <TextBox.Resources>
      <local:StringToIntConverter x:Key="StringToIntConverter" />
    </TextBox.Resources>
    <TextBox.Text>
      <Binding
        Path="Id"
        Mode="TwoWay"
        UpdateSourceTrigger="PropertyChanged"
        Converter="{StaticResource StringToIntConverter}">
        <Binding.ValidationRules>
          <local:IntegerValidator />
        </Binding.ValidationRules>
      </Binding>
    </TextBox.Text>
  </TextBox>
  <Button
    Grid.Row="1"
    VerticalAlignment="Center"
    HorizontalAlignment="Center"
    Content="Test"
    Padding="5"
    Command="{x:Static local:MainWindow.ShowIdCommand}">
    <Button.CommandBindings>
      <CommandBinding
        Command="{x:Static local:MainWindow.ShowIdCommand}"
        Executed="ShowIdCommandHandler" />
    </Button.CommandBindings>
  </Button>
</Grid>



Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
public partial class MainWindow : INotifyPropertyChanged
{
  public MainWindow()
  {
    InitializeComponent();
  }

  int? _id;
  public int? Id
  {
    get => _id;
    set
    {
      _id = value;
      Console.WriteLine("Id={0}", Id?.ToString() ?? "null");
      OnPropertyChanged();
    }
  }

  public static RoutedUICommand ShowIdCommand { get; } = new RoutedUICommand("ShowId", "ShowId", typeof(MainWindow));
  void ShowIdCommandHandler(object sender, ExecutedRoutedEventArgs e)
  {
    MessageBox.Show($"Id=({Id?.ToString() ?? "null"})");
  }

  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

public class StringToIntConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return ((int?)value)?.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
  }

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    if (string.IsNullOrWhiteSpace(value as string))
      return (int?)null;
    if (int.TryParse((string)value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n))
      return (int?)n;
    return (int?)null;
  }
}

public class IntegerValidator : ValidationRule
{
  public override ValidationResult Validate(object value, CultureInfo cultureInfo)
  {
    if (string.IsNullOrWhiteSpace(value as string))
      return ValidationResult.ValidResult;
    try
    {
      int.Parse((string)value, NumberStyles.Integer, CultureInfo.InvariantCulture);
      return ValidationResult.ValidResult;
    }
    catch (Exception e)
    {
      return new ValidationResult(false, e.Message);
    }
  }
}


Работает он вполне нормально, но есть одно но: если сначала в текстбокс ввести правильное значение (например, 123 - связанное свойство примет значение 123), потом выделить весь текст, и начать вводить неправильное значение (например, qqq), то соответствующая ошибка валидации будет отображена, но связанное свойство по-прежнему будет содержать предыдущее правильное значение (123) - а нужно, чтобы оно при этом сбросилось в null (т.к. в реальности пользователь, как обычно, не обращает внимания на эти сообщения валидации, жмакает на кнопку, и возмущается, что команда отработала с значением, которое он уже вводил).
Пробовал экспериментировать с ValidationStep (по умолчанию оно равно RawProposedValue, т.е. правило отрабатывает до конвертера) - получилось не то, что хотелось: при ValidationStep=CommitedValue или UpdatedValue сначала отрабатывает IValueConverter.ConvertBack, постит null-значение в source, потом отрабатывает IValueConverter.Convert, и target апдейтится значением пустой строки, и только после этого отрабатывает ValidationRule, в который приходит пустая строка (а это значение считается валидным - оно нужно для сброса source в null). В результате невалидный текст просто вообще не набирается (что, конечно, хорошо, но чисто ради научного интереса хотелось бы, чтобы он набирался, и программа писала, что Input string was not in a correct format).
Как сделать задуманное?
...
Рейтинг: 0 / 0
ValidationRule+IValueConverter: сброс source в null при ошибке+отображение ошибки - как?
    #39775210
Eld Hasp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
WinterGraveyard, сразу оговариваюсь - я не спец.., токо начинаю. Но из того что пришло в голову - Вам валидация не нужна. Смысл валидации, именно в то чтьобы не пропускать не корректные данные. А вывод сообщения о некорректности - это визуальная "красивость". Если Вам не нужна валидация, а нужно просто известить пользователя о некорректном значении, то введите вывод извещения или в обработчик TextChanged, или в сеттер свойство VM? к которому привязан Text.
...
Рейтинг: 0 / 0
ValidationRule+IValueConverter: сброс source в null при ошибке+отображение ошибки - как?
    #39775305
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
WinterGraveyardКак сделать задуманное?
В озвученной постановке - никак, т.к. налицо взаимоисключающие требования: если ввод неволиден, то источник нужно сбросить в null, а null, как написано выше, валиден как с т.з. типа данных, так и с т.з бизнес-логики (логики валидатора): сброс источника в валидное значение будет протранслирован в target, сработает валидация по валидному значению, и ошибка пропадет (контрол Validation.ErrorTemplate скроется).
Что здесь можно сделать:
1. Слушать событие валидации Validation.Error. Не очень удобно - code-behind не всегда доступен, и скрещивать его с вьюмоделью тоже не особенно удобно. Можно использовать паттерн event to command (attached command behavior, Interaction.Triggers из System.Windows.Interactivity), чтобы оповещать модель о невалидном вводе, и вручную синхронизировать сообщения о невалидном вводе, и состояние свойств. Если таких наблюдаемых свойств будет несколько, получится не особенно изящно.
2. Оповещать модель о невалидном вводе прямо из ValidationRule - соответственно, ValidationRule должен иметь ссылку на вьюмодель. Можно её предоставлять с помощь синглтона, сервис-локатора, можно у ValidationRule задать public-свойство на чтение/запись, и в него из xaml-разметки передавать как StaticResource ссылку на вьюмодель, если вьюмодель инициализурется в xaml. Дальше по этой ссылке делать, что нужно - выставлять признаки некорректного ввода, обнулять backing fields свойств (без возбуждения PropertyChanged), итд. Минусы всё те же, что и выше.
3. Держать для свойства 2 поля: одно - текстовое, используемое в биндинге контрола, и валидируемое на шаге CommitedValue. Второе - только для чтения, вычисляемое на лету по содержимому первого свойства.
В этом случае при невалидном значении первого свойства (которое будет сохранено в VM, т.к. шаг валидации CommitedValue), второе свойство может вернуть null (по результату парсинга значения первого свойства), и при этом будет отображаться контрол Validation.ErrorTemplate, т.к. валидация отработает с ValidationResult.IsValid = false:
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
<Grid Margin="5">
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
    <RowDefinition />
  </Grid.RowDefinitions>
  <TextBox
    Margin="5"
    Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}">
    <TextBox.Text>
      <Binding
        Path="IdStr"
        Mode="TwoWay"
        UpdateSourceTrigger="PropertyChanged">
        <Binding.ValidationRules >
          <local:IntegerValidator ValidationStep="CommittedValue"/>
        </Binding.ValidationRules>
      </Binding>
    </TextBox.Text>
  </TextBox>
  <TextBox
    Grid.Row="1"
    Margin="5,30,5,5"
    IsReadOnly="True"
    Text="{Binding Id, Mode=OneWay}" />
  <Button
    Grid.Row="2"
    VerticalAlignment="Center"
    HorizontalAlignment="Center"
    Content="Test"
    Padding="5"
    Command="{Binding ShowIdCommand}" />
</Grid>



Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
public partial class MainWindow
{
  public MainWindow()
  {
    InitializeComponent();
    DataContext = new MainModel();
  }
}

public class MainModel : INotifyPropertyChanged
{
  public MainModel()
  {
    ShowIdCommand = new RelayCommand(ShowId);
  }

  string _idStr;
  public string IdStr
  {
    get => _idStr;
    set
    {
      _idStr = value;
      OnPropertyChanged();
      OnPropertyChanged(nameof(Id));
      Console.WriteLine("IdStr={0}, Id={1}", IdStr ?? "null", Id?.ToString() ?? "null");
    }
  }

  public int? Id =>
    int.TryParse(IdStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n)
      ? (int?)n
      : null;

  public RelayCommand ShowIdCommand { get; }
  void ShowId()
  {
    MessageBox.Show($"Id=({Id?.ToString() ?? "null"})");
  }

  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

public class IntegerValidator : ValidationRule
{
  public override ValidationResult Validate(object value, CultureInfo cultureInfo)
  {
    var idStr = ((MainModel) ((BindingExpression) value).ResolvedSource).IdStr;
    if (string.IsNullOrWhiteSpace(idStr))
      return ValidationResult.ValidResult;
    try
    {
      int.Parse(idStr, NumberStyles.Integer, CultureInfo.InvariantCulture);
      return ValidationResult.ValidResult;
    }
    catch (Exception e)
    {
      return new ValidationResult(false, e.Message);
    }
  }
}



Eld HaspСмысл валидации, именно в то чтьобы не пропускать не корректные данные. А вывод сообщения о некорректности - это визуальная "красивость".
Ну неправильно же. ValidationRule для ValidationStep=CommitedValue и UpdatedValue отрабатывает тогда, когда валидируемое значение уже передано в источник. Это заложено в дизайн системы валидации. И ТС об этом в курсе, т.к. экспериментировал с этим.
Ну, и насчет красивости я бы поспорил. При разборе полетов [якобы] некорректной работы приложения, одним из аргументов обвинения может стать отсутствие нотификации о некорректном вводе.
...
Рейтинг: 0 / 0
ValidationRule+IValueConverter: сброс source в null при ошибке+отображение ошибки - как?
    #39775804
WinterGraveyard
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Сон Веры Павловны,

Спасибо. Последний вариант с двумя свойствами вполне устраивает.
...
Рейтинг: 0 / 0
4 сообщений из 4, страница 1 из 1
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / ValidationRule+IValueConverter: сброс source в null при ошибке+отображение ошибки - как?
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]