Как отключить кнопку при ошибке проверки - PullRequest
0 голосов
/ 03 января 2019

У меня есть 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 модели обновляются при измененииЗначение.Правило проверки применяется правильно.

1 Ответ

0 голосов
/ 03 января 2019

Вы должны позволить вашей модели представления реализовать 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.

...