Arhn - архитектура программирования

Как отключить кнопку при ошибке проверки

У меня DataGrid вот так:

<DataGrid CanUserSortColumns="False" CanUserAddRows="True" ItemsSource="{Binding Vertices}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn x:Name="YColumn" Width="*" Header="Latitude">
            <DataGridTextColumn.Binding>
                <Binding Path="Y">
                    <Binding.ValidationRules>
                        <validation:DoubleValidationRule />
                    </Binding.ValidationRules>
                </Binding>
            </DataGridTextColumn.Binding>
        </DataGridTextColumn>
        <DataGridTextColumn x:Name="XColumn" Width="*" Header="Longitude">
            <DataGridTextColumn.Binding>
                <Binding Path="X">
                    <Binding.ValidationRules>
                        <validation:DoubleValidationRule />
                    </Binding.ValidationRules>
                </Binding>
            </DataGridTextColumn.Binding>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

У меня есть два столбца с одинаковым правилом проверки (проверка, является ли значение в ячейке двойным):

public class DoubleValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        if (value != null)
        {
            double proposedValue;
            if (!double.TryParse(value.ToString(), out proposedValue))
            {
                return new ValidationResult(false, "'" + value.ToString() + "' is not a whole double.");
            }
        }
        return new ValidationResult(true, null);
    }
}

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

Следуя другим сообщениям по этой теме, я добился этого с помощью MultiDataTriggers:

<Button>
    <Button.Style>
        <Style TargetType="Button">
            <Setter Property="IsEnabled" Value="False" />
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding Path=(Validation.HasError), ElementName=XColumn}" Value="False" />
                        <Condition Binding="{Binding Path=(Validation.HasError), ElementName=YColumn}" Value="False" />
                    </MultiDataTrigger.Conditions>
                    <Setter Property="IsEnabled" Value="True" />
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

Но это не работает. Кнопка никогда не отключается даже при ошибке проверки. Что я делаю неправильно?

Изменить: вот моя модель и связанный с ней код в модели представления:

public class CustomVertex
{
    public double X { get; set; }

    public double Y { get; set; }

    public CustomVertex()
    { }
}

public class CustomPolygonViewModel : ViewModelBase
{
    public ObservableCollection<CustomVertex> Vertices { get; set; }

    public CustomPolygonViewModel()
    {
        Vertices = new ObservableCollection<CustomVertex>();
    }
}

Мой DataContext настроен правильно, и я проверил, что x и y модели обновляются при изменении значения. Правило проверки выполняется правильно.


  • Я не вижу ничего прямо торчащего. Я бы дважды проверил, что ваши привязки к вашей кнопке правильные. Также убедитесь, что вы уведомляете об изменении этих свойств. Я предполагаю, что либо привязки неверны, либо эти свойства неправильно уведомляют об изменениях. 03.01.2019
  • Обновил мой вопрос с соответствующим кодом из модели и модели просмотра. Может ли это иметь какое-то отношение к тому, что имя элемента находится в столбце, а не в ячейке? 03.01.2019
  • Привязка, которую вы должны проверить, - это Validation.HasError. Правильно ли он привязан и как уведомляет об изменениях? 03.01.2019
  • Вы правы, это неправильно, но я не совсем уверен, как это исправить. Я думаю, что неправильно понимаю, как работает проверка. Нужно ли мне свойство в модели просмотра для привязки Validation.HasError к? 03.01.2019
  • Validation.HasErrors не может быть привязан к. См. этот пост для получения правильного решения. 03.01.2019
  • Итак, мне нужно добавить свойство HasError в каждую имеющуюся у меня модель, и я хочу провести валидацию таким образом? Просто кажется, что это много для простого включения / выключения. 03.01.2019
  • Лично я, вероятно, сделал бы свою собственную проверку в модели представления, а затем привязал бы к своим собственным свойствам XHasErrors и YHasErrors. Я думаю, это было бы лучше, чем использовать валидацию в вашем случае. 03.01.2019

Ответы:


1

Вы должны позволить вашей модели представления реализовать INotifyDataErrorInfo MSDN. Пример. Пример из MSDN (Silverlight). Начиная с .Net 4.5, это рекомендуемый способ ввести валидацию в ваши модели представления и поможет вам решить вашу проблему. При реализации этого интерфейса вам нужно будет предоставить свойство HasErrors, к которому вы можете выполнить привязку. INotifyDataErrorInfo заменяет устаревшее IDataErrorInfo.

Привязка к Validation.HasError напрямую, как вы это делали в триггерах, не будет работать, поскольку Validation.HasError является присоединенным свойством только для чтения и поэтому не поддерживает привязку. Чтобы доказать это, я нашел это утверждение на MSDN:

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


Как работает INotifyDataErrorInfo

Если для свойства ValidatesOnNotifyDataErrors объекта Binding установлено значение true, механизм привязки будет искать реализацию INotifyDataErrorInfo в источнике привязки, чтобы подписаться на событие ErrorsChanged.

Если событие ErrorsChanged возникает и HasErrors оценивается как true, привязка вызовет метод GetErrors() для фактического свойства, чтобы получить конкретное сообщение об ошибке и применить настраиваемый шаблон ошибки проверки для визуализации ошибки. По умолчанию проверенный элемент обведен красной рамкой.

Как реализовать INotifyDataErrorInfo

Класс CustomVertex на самом деле является ViewModel для DataGrid столбцов, поскольку вы привязываетесь к его свойствам. Таким образом, он должен реализовать INotifyDataErrorInfo. Это могло выглядеть так:

public class CustomVertex : INotifyPropertyChanged, INotifyDataErrorInfo
{
    public CustomVertex()
    {
      this.errors = new Dictionary<string, List<string>>();
      this.validationRules = new Dictionary<string, List<ValidationRule>>();

      this.validationRules.Add(nameof(this.X), new List<ValidationRule>() {new DoubleValidationRule()});
      this.validationRules.Add(nameof(this.Y), new List<ValidationRule>() {new DoubleValidationRule()});
    }


    public bool ValidateProperty(object value, [CallerMemberName] string propertyName = null)  
    {  
        lock (this.syncLock)  
        {  
            if (!this.validationRules.TryGetValue(propertyName, out List<ValidationRule> propertyValidationRules))
            {
              return;
            }  

            // Clear previous errors from tested property  
            if (this.errors.ContainsKey(propertyName))  
            {
               this.errors.Remove(propertyName);  
               OnErrorsChanged(propertyName);  
            }

            propertyValidationRules.ForEach(
              (validationRule) => 
              {
                ValidationResult result = validationRule.Validate(value, CultuteInfo.CurrentCulture);
                if (!result.IsValid)
                {
                  AddError(propertyName, result.ErrorContent, false);
                } 
              }               
        }  
    }   

    // Adds the specified error to the errors collection if it is not 
    // already present, inserting it in the first position if isWarning is 
    // false. Raises the ErrorsChanged event if the collection changes. 
    public void AddError(string propertyName, string error, bool isWarning)
    {
        if (!this.errors.ContainsKey(propertyName))
        {
           this.errors[propertyName] = new List<string>();
        }

        if (!this.errors[propertyName].Contains(error))
        {
            if (isWarning) 
            {
              this.errors[propertyName].Add(error);
            }
            else 
            {
              this.errors[propertyName].Insert(0, error);
            }
            RaiseErrorsChanged(propertyName);
        }
    }

    // Removes the specified error from the errors collection if it is
    // present. Raises the ErrorsChanged event if the collection changes.
    public void RemoveError(string propertyName, string error)
    {
        if (this.errors.ContainsKey(propertyName) &&
            this.errors[propertyName].Contains(error))
        {
            this.errors[propertyName].Remove(error);
            if (this.errors[propertyName].Count == 0)
            {
              this.errors.Remove(propertyName);
            }
            RaiseErrorsChanged(propertyName);
        }
    }

    #region INotifyDataErrorInfo Members

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public System.Collections.IEnumerable GetErrors(string propertyName)
    {
        if (String.IsNullOrEmpty(propertyName) || 
            !this.errors.ContainsKey(propertyName)) return null;
        return this.errors[propertyName];
    }

    public bool HasErrors
    {
        get { return errors.Count > 0; }
    }

    #endregion

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

    private double x;
    public double X 
    { 
      get => x; 
      set 
      { 
        if (ValidateProperty(value))
        {
          this.x = value; 
          OnPropertyChanged();
        }
      }
    }

    private double y;
    public double Y 
    { 
      get => this.y; 
      set 
      { 
        if (ValidateProperty(value))
        {
          this.y = value; 
          OnPropertyChanged();
        }
      }
    }


    private Dictionary<String, List<String>> errors;

    // The ValidationRules for each property
    private Dictionary<String, List<ValidationRule>> validationRules;
    private object syncLock = new object();
}

Вид:

<DataGrid CanUserSortColumns="False" CanUserAddRows="True" ItemsSource="{Binding Vertices}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn x:Name="YColumn" 
                            Width="*" 
                            Header="Latitude" 
                            Binding="{Binding Y, ValidatesOnNotifyDataErrors=True}" 
                            Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
        <DataGridTextColumn x:Name="XColumn" 
                            Width="*" 
                            Header="Longitude" 
                            Binding="{Binding X, ValidatesOnNotifyDataErrors=True}" 
                            Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />            
    </DataGrid.Columns>
</DataGrid>

Ниже приведен шаблон ошибки проверки на тот случай, если вы хотите настроить визуальное представление (необязательно). Он устанавливается для проверенного элемента (в данном случае DataGridTextColumn) через прикрепленное свойство Validation.ErrorTemplate (см. Выше):

<ControlTemplate x:Key=ValidationErrorTemplate>
    <StackPanel>
        <!-- Placeholder for the DataGridTextColumn itself -->
        <AdornedElementPlaceholder x:Name="textBox"/>
        <ItemsControl ItemsSource="{Binding}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</ControlTemplate>

Кнопка, которая будет отключена при неудачной проверке (поскольку я не знаю, где находится эта кнопка в визуальном дереве, я предполагаю, что она разделяет DataContext столбца DataGrid, модель данных CustomVertex):

<Button>
    <Button.Style>
        <Style TargetType="Button">
            <Setter Property="IsEnabled" Value="True" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=HasErrors}" Value="True">
                    <Setter Property="IsEnabled" Value="False" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

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

Я рекомендую переместить реализацию INotifyDataErrorInfo в базовый класс вместе с INotifyPropertyChanged и позволить всем вашим моделям представления наследовать его. Это делает логику проверки пригодной для повторного использования и поддерживает чистоту классов модели представления.

Вы можете изменить детали реализации INotifyDataErrorInfo в соответствии с требованиями.

Примечания: Код не тестировался. Эти фрагменты должны работать, но предназначены для того, чтобы показать пример того, как INotifyDataErrorInfo интерфейс может быть реализован.

03.01.2019
  • Итак, я не могу использовать правила проверки в xaml и привязать возвращаемый логический аргумент правила проверки к свойству? 03.01.2019
  • ValidationRule не реализует INotifyPropertyChanged. Таким образом, изменения в ValdationRule.HasError не будут обновлять ваши привязки. Попробуйте INotifyErrorDataInfo. Свойство, которое вы должны реализовать, должно использовать INotifyPropertyChanged, и все будет работать. Я добавил ссылку на учебное пособие, показывающее, как его использовать. Все очень просто и интуитивно понятно. 03.01.2019
  • Спасибо за обновления. Посмотрев на пример, который вы отправили, я вроде как понял его, но для моего примера, когда я имею дело с коллекцией моделей, где ячейки в сетке данных соответствуют свойствам в модели, буду ли я реализовывать INotifyErrorDataInfo в моей виртуальной машине или модели? Думаю, у меня возникли проблемы с тем, как это будет правильно реализовано в моей модели представления. 03.01.2019
  • Проверка является частью ViewModel. Вы хотите проверить и принудить данные перед их передачей в модель. В модель должны входить только действительные данные. Поэтому вам нужно реализовать его вместе с INotifyPropertyChanged. Приведу вам пример. 03.01.2019
  • Я исправил утверждение в своем ответе, потому что неправильно прочитал ваш. Я думал, что вы привязываетесь к ValidationRule.HasError, но потом я понял, что для этого типа нет свойства HasError. Вы выполняли привязку к типу Validation, а свойство HasError является присоединенным свойством, доступным только для чтения. Правильное объяснение того, почему ваша привязка не работает, заключается в том, что вы не можете привязаться к DependencyProperty (или AttachedProperty) только для чтения. 04.01.2019
  • Я попытался скопировать этот код, но он не компилируется, потому что OnErrorsChanged и RaiseErrorsChanged не определены, а от ValidateProperty нет возврата, кроме пустого возврата в начале. 25.04.2021
  • @ Кевин Бертон Спасибо. Я исправлю пример, чтобы он скомпилировался. Я написал код без какого-либо редактора. Я просто хотел показать основной узор. Между тем, вы можете следовать этому рабочему примеру, чтобы узнать, как реализовать проверку ошибок. 26.04.2021
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..