Проверка на основе существующих данных в WPF - PullRequest
1 голос
/ 25 февраля 2020

Мне нужно создать узел проверки, который будет возвращать ошибку, если введенное значение уже существует. У меня есть GUI с предметами, которым можно задать имя. Я хочу, чтобы имена были уникальными.

Поэтому для каждой проверки мне нужны следующие два параметра:

  • Список всех имен всех элементов или некоторый предикат, который сообщит меня имя существует
  • Текущее имя элемента, чтобы исключить его из вышеуказанной проверки (изменение имени на то же значение не должно быть ошибкой)

Контексты данных выглядят так (просто интерфейс для иллюстрации):

class AppMainContext
{
  public IEnumerable<string> ItemNames {get;}
  public Item SelectedItem {get;}
}

class Item 
{
  public string Name {get;}
}

Поле в WPF выглядит следующим образом, а его родительский элемент привязан к `{SelectedItem}:

<DockPanel DockPanel.Dock="Top">
  <Label Content="Name: "/>
  <TextBox DockPanel.Dock="Top">
    <TextBox.Text>
      <Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
        <Binding.ValidationRules>
          <vmvalidation:UniqueNameRule  />
        </Binding.ValidationRules>
      </Binding>
    </TextBox.Text>
  </TextBox>
</DockPanel>

Валидатор выглядит следующим образом:

using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Controls;

namespace MyApp.Validation
{
  public class UniqueNameRule : ValidationRule
  {
    public IEnumerable<string> ExistingNames { get; set; }

    public string MyName { get; set; }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
      if(value is string newValue)
      {
        // name changed
        if(!value.Equals(MyName))
        {
          if(ExistingNames.Contains(newValue))
          {
            return new ValidationResult(false, "Name already exists!");
          }
        }
        return new ValidationResult(true, null);
      }
      else
      {
        return new ValidationResult(false, "Invalid value type. Is this validator valid for the given field?");
      }
    }
  }
}

Я попытался хотя бы связать текущее имя с валидатором. Текстовое поле уже существует в контексте данных текущих элементов, поэтому правильная привязка будет:

<Binding.ValidationRules>
  <vmvalidation:UniqueNameRule MyName="{Binding Name}"  />
</Binding.ValidationRules>

За исключением того, что это дает ошибку:

Элемент MyName не является распознан или недоступен.

Список всех элементов находится в контексте данных windows, доступном через ItemNames. Я полагаю, к нему можно получить доступ так:

{Binding Path=DataContext.ItemNames, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}

Я попытался правильно связать, используя ответ ниже, но затем я получаю ошибку:

Невозможно установить «Связывание» в свойстве «MyName» типа MyProject_Validation_UniqueNameRule_9_468654. «Привязка» может быть установлена ​​только для свойства DependencyProperty объекта DependencyObject.

Похоже, что привязки вообще не поддерживаются.

Так как я могу собрать это вместе, чтобы Правило проверки может получить доступ к обеим этим переменным?

1 Ответ

0 голосов
/ 25 февраля 2020

Привязка не выполняется из-за характера того, как правило проверки попадает на визуальное дерево, и, возможно, это то, что вы подозреваете.

Существуют другие разновидности RelativeSource (, см. Раздел свойств в этом документе ) по привязкам.


В конечном счете, нужен родительский узел, вот тот, который используется для стилей, которые могут иметь отношение:

<vmvalidation:UniqueNameRule 
                MyName="{Binding Name, RelativeSource={RelativeSource TemplatedParent}}"/>

Или продвигайтесь вверх по цепочке, вместо x:Type Window как более вероятная привязка к родителю, такая как x:Type TextBox:

<vmvalidation:UniqueNameRule 
    MyName="{Binding Name, RelativeSource={RelativeSource Mode=FindAncestor, 
                                                          AncestorType={x:Type TextBox}}"/>
...