0
votes

I am trying to create a validation dependency between 2 datagrid columns.

The first column is a drop down box. (DataGridTemplateColumn) The second is a text column. (DataGridTextColumn)

I am trying from the drop down box event in the code behind to force the validation of the datagrid text column cell of the same row.

Thanks for your help.

            <data:DataGridTemplateColumn Header="Type" >
                <data:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox ItemsSource="{Binding Source={StaticResource TypeListContainer}, Path=TypeLists}" Loaded="TypeBoxLoaded" DropDownClosed="TypeBoxChanged">
                        </ComboBox>
                    </DataTemplate>
                </data:DataGridTemplateColumn.CellTemplate>
            </data:DataGridTemplateColumn>
            <data:DataGridTextColumn Header="Rule" Binding="{Binding RuleWrapper, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" x:Name="RuleCol" />

Here is the property

public string RuleWrapper
{
    get
    {
        return this.Rule;
    }
    set
    {
        //Required
        if (value == null || value == string.Empty)
        {
            throw new ValidationException("Rule is required");
        }
        //Match regular expression if type is channel
        Regex reg = new Regex(@"^(51[01]|50[0-9]|[0-4]?[0-9][0-9]?)\.(51[01]|50[0-9]|[0-4]?[0-9][0-9]?)\.(51[01]|50[0-9]|[0-4]?[0-9][0-9]?)\.(51[01]|50[0-9]|[0-4]?[0-9][0-9]?)$");
        if (Type == "channel" && !reg.IsMatch(value))
            throw new ValidationException("Channel not matching the right format");

        //Match range if type is trunk
        int intValue = -1;
        //Match if is a number
        if (int.TryParse(value, out intValue))
        {
            //Match if number is in the range
            if (intValue < 0 || intValue > 134217727)
                throw new ValidationException("Trunk value must be between 0 and 134317727");
        }
        else
            throw new ValidationException("Trunk value must a an integer");
        this.Rule = value;
    }
}
1
What is the purpose of the ComboBox control? Should its selected value be validated too? In your example you can force validation by calling RuleWrapper = RuleWrapper, but I'm not sure that it is what you need. - vortexwolf
The combox box selection triggers a different validation for the rule. So when I do RuleWrapper = RuleWrapper to force the validation in the code behind I get a ValidationException thrown but I do not know how to handle it so it gets displayed properly on the datagrid - Stainedart
I know how to force validation if I use the IDataErrorInfo or INotifyDataErrorInfo interfaces. What do you think if I completely rewrite this validation so that it is performed by those interfaces? You will have to rewrite all validation code then. - vortexwolf
You should not have all that logic within a property set! - Darren Young
Its a wrapper to the actual property. Are you saying that I should create for each field a validate method instead of using the setter? - Stainedart

1 Answers

2
votes

A few things to note (I'm still learning about validation, so I stand to be corrected).

  • WPF doesn't support ValidationException.

  • Setting ValidatesOnExceptions=True in a binding only handles exceptions in the internally used default converters (like converting a string to a number).

  • Exceptions in your own converters aren't caught and cause your app to crash.

  • Validation is handled using IDataErrorInfo, INotifyDataErrorInfo (Silverlight and WPF 4.5) and ValidationRule

Also I'm answering from a WPF perspective at the moment. I'll test on Silverlight later.

MSDN has a surprisingly good article here about binding and validation.

Here's my sample which demonstrates a validation dependency between two text columns (for simplicity). The second column is read-only but shows validation errors caused by the first column.

<Grid>
    <Grid.DataContext>
        <Samples:DataGridValidationViewModels/>
    </Grid.DataContext>

    <DataGrid AutoGenerateColumns="False" ItemsSource={Binding Items}>
        <DataGrid.Columns>
            <DataGridTextColumn Header="Column 1" Binding="{Binding Column1, ValidatesOnDataErrors=True}" />
            <DataGridTextColumn IsReadOnly="True" Header="Column 2" Binding="{Binding Column2, ValidatesOnDataErrors=True}" />
        </DataGrid.Columns>
    </DataGrid>
</Grid>

I'm using MVVM Light for my view model/INotifyPropertyChange support, so replace my use of Set(()=>... with your own implementation (_prop = value; RaisePropertyChanged("string"); etc).

Note that raising a property changed event on a read-only property causes validation to happen on that property which is useful.

public class DataGridValidationViewModels
{
    public DataGridValidationViewModels()
    {
        Items = new ObservableCollection<DataGridValidationViewModel>
                    {
                        new DataGridValidationViewModel(),
                        new DataGridValidationViewModel(),
                        new DataGridValidationViewModel(),
                        new DataGridValidationViewModel(),
                        new DataGridValidationViewModel(),
                        new DataGridValidationViewModel(),
                        new DataGridValidationViewModel(),
                    };

    }

    public IEnumerable<DataGridValidationViewModel> Items { get; set; }
}

public class DataGridValidationViewModel : ViewModelBase, IDataErrorInfo
{
    public DataGridValidationViewModel()
    {
        _column1 = "Column 1";
        _column2 = "Column 2";
    }

    private string _column1;

    public string Column1
    {
        get { return _column1; }
        set
        {
            Set(() => Column1, ref _column1, value);
            Column2 = value;
        }
    }

    private string _column2;

    public string Column2
    {
        get { return _column2; }
        private set{ Set(()=>Column2, ref _column2, value);}
    }

    #region Implementation of IDataErrorInfo

    public string this[string columnName]
    {
        get
        {
            switch (columnName)
            {
                case "Column1":
                    return Column1 == "Error" ? "There's an error in column 1!" : string.Empty;

                case "Column2":
                    return Column1 == "Error" ? "There's an error in column 2!" : string.Empty;
            }

            return string.Empty;
        }
    }

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

    #endregion
}

When you change the text of Column 1 to "Error" you get:

enter image description here