Fabrizio Stellato Fabrizio Stellato - 3 months ago 18
C# Question

Automatically fix / remove invalid row from Datagrid

I have a DataGrid with a validation rule inside RowValidationRules markup.

What I want is to have the binded property updated ONLY if there are no validation errors, otherwise should keep the old values.

xaml:

<DataGrid
Margin="10"
CanUserAddRows="True"
CanUserDeleteRows="True"
AutoGenerateColumns="False"
IsReadOnly="False"
ItemsSource="{Binding Source={x:Static services:SharedPropertiesProvider.Instance}, Path=Aliases, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
>

<DataGrid.RowValidationRules>
<validation:AliasValidation />
</DataGrid.RowValidationRules>
<DataGrid.Columns>
<DataGridTextColumn Width="30*" Header="Alias" Binding="{Binding Key, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Width="70*" Header="Path" Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="ApplicationCommands.Delete" Width="20" Height="20">
<Button.Content>
<Image Margin="2" Source="/WinLogInspector;component/Assets/1441392968_f-cross_256.png" />
</Button.Content>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>




validation class:

class AliasValidation : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
if (((BindingGroup)value).Items.Count > 0)
{
Alias item = (value as BindingGroup).Items[0] as Alias;

if (item != null)
{
string aliasPattern = @"^[a-zA-Z]+[a-zA-Z0-9]*$";

string pathPattern = @"^[a-zA-Z0-9\\/@:_\-;]+$";

string key = item.Key ?? String.Empty;

string path = item.Value ?? String.Empty;

bool isValidAlias = Regex.IsMatch(key, aliasPattern);

bool isValidPath = Regex.IsMatch(path, pathPattern);

if (isValidAlias && isValidPath)
return ValidationResult.ValidResult;
else
return new ValidationResult(false, "Invalid alias / path");
}
}

return ValidationResult.ValidResult;

} }


The property in viewmodel:

public ObservableCollection<Alias> Aliases { get; set; }

class Alias
{
public string Key { get; set; }

public string Value { get; set; }

}


So if I try to execute any command from the viewmodel I can see the the Aliases property has the invalid rows.
How I can sanitize this ?

Joe Joe
Answer

Well, I was completely wrong. Failed validation does update the binding! The only workaround I can think of at the moment is to have new display properties, and also validate in the setter...

    public string key;
    public string Key {
        get { return key; }
        set
        {
            if (key != value && this["KeyDisplay"] == string.Empty)
            {
                key = value;
                NotifyPropertyChanged("Key");
            }
        }
    }

    public string _value;
    public string Value
    {
        get { return _value; }
        set
        {
            if (_value != value && this["ValueDisplay"] == string.Empty)
            {
                _value = value;
                NotifyPropertyChanged("Value");
            }
        }
    }

    public string keyDisplay;
    public string KeyDisplay
    {
        get { return keyDisplay; }
        set
        {
            if (keyDisplay != value)
            {
                keyDisplay = value;
                Key = value;
                NotifyPropertyChanged("KeyDisplay");
            }
        }
    }

    public string valueDisplay;
    public string ValueDisplay
    {
        get { return valueDisplay; }
        set
        {
            if (valueDisplay != value)
            {
                valueDisplay = value;
                Value = value;
                NotifyPropertyChanged("ValueDisplay");
            }
        }
    }

    public string this[string columnName]
    {
        get
        {
            if (columnName == nameof(KeyDisplay) || columnName == nameof(ValueDisplay))
            {
                string aliasPattern = @"^[a-zA-Z]+[a-zA-Z0-9]*$";

                string pathPattern = @"^[a-zA-Z0-9\\/@:_\-;]+$";

                string key = KeyDisplay ?? String.Empty;

                string path = ValueDisplay ?? String.Empty;

                bool isValidAlias = Regex.IsMatch(key, aliasPattern);

                bool isValidPath = Regex.IsMatch(path, pathPattern);

                if (isValidAlias && isValidPath)
                    return string.Empty;
                else
                    return "Invalid alias / path";
            }
            return string.Empty;
        }
    }

This is the original answer and completely incorrect! Validation prevents updating of the property associated with the binding. I'd guess you're properties are getting set even though they don't pass validation is because those properties aren't actually being validated. You're enabeling NotifyOnValidationError=True validation only on the ItemsControl ItemsSource property.

With MVVM I'd really recommend using IDataErrorInfo and validating in the ViewModel or Model - Validation isn't display related, and doesn't belong in the view in my opinion.

class Alias : IDataErrorInfo
{
    public string Key { get; set; }

    public string Value { get; set; }

    public string this[string columnName]
    {
        get
        {
            if (columnName == nameof(Key) || columnName == nameof(Value))
            {
                string aliasPattern = @"^[a-zA-Z]+[a-zA-Z0-9]*$";

                string pathPattern = @"^[a-zA-Z0-9\\/@:_\-;]+$";

                string key = Key ?? String.Empty;

                string path = Value ?? String.Empty;

                bool isValidAlias = Regex.IsMatch(key, aliasPattern);

                bool isValidPath = Regex.IsMatch(path, pathPattern);

                if (isValidAlias && isValidPath)
                    return string.Empty;
                else
                    return "Invalid alias / path";
            }
            return string.Empty;
        }
    }

    public string Error
    {
        get
        {
            return string.Empty;
        }
    }
}

It also gives you more flexibility to check multiple properties are valid, as you have access to the whole data model in the binding logic. I think with ValidationRules you might have to mess around with multi-binding or something.

If you do stick with ValidationRules if you used validation in your Key and Value bindings: Binding="{Binding Key, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True...}"> they shouldn't get updated on failure.