Tomáš Zato
Tomáš Zato

Reputation: 53119

Validation based on existing data in WPF

I need to create a validation node that will return an error if value entered already exists. I have GUI with items that can have their name set. I want to enforce the names to be unique.

So for each validation, I need following two parameters:

The data contexts look like this (just the interface for illustration):

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

class Item 
{
  public string Name {get;}
}

The field in WPF looks like this and its parent is bound to `{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>

The validator looks like this:

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?");
      }
    }
  }
}

I tried to at least bind current name to the validator. The text box already exists in current items data context, so a correct binding would be:

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

Except that this gives an error:

The member MyName is not recognized or is not accessible.

The list of all items is in the windows data context, accessible through ItemNames. I suppose it could be accessed like this:

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

I tried correct binding using an answer below, but I then get an error:

A 'Binding' cannot be set on the 'MyName' property of type MyProject_Validation_UniqueNameRule_9_468654. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

Looks like bindings are not supported at all.

So how can I put this together, so that the validation rule can access both of these variables?

Upvotes: 1

Views: 666

Answers (1)

ΩmegaMan
ΩmegaMan

Reputation: 31596

The binding is failing due to the nature of how the validation rule falls on the visual tree, and maybe is what you suspect.

There are other flavors of RelativeSource (see the properties section in that document) on bindings.


Ultimately one wants the parent node, here is one used on styles which might be relevant:

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

Or work your way up the chain, instead of x:Type Window how the more likely binding to the parent such as x:Type TextBox:

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

Upvotes: 0

Related Questions