Abhishek Batra
Abhishek Batra

Reputation: 1599

MVVM WPF : Validating datagrid row for autogeneratedcolumns

I am creating a DataTable whose columns are stored in a list.

public class CustomColumn
{
    public string ColumnName { get; set; }
    public int MinLength { get; set; }
    public int MaxLength { get; set; }
}

public class ViewModel
{
    public List<CustomColumn> Columns { get; set; }
    public DataTable MyTable { get; set; }

    public ViewModel()
    {
        InitializeCustomColumns();
        MyTable = new DataTable();

        foreach (CustomColumn column in Columns)
        {
            MyTable.Columns.Add(column.ColumnName, typeof(string));
        }
    }
}

Now I am binding the DataTable to a DataGrid and allowing the user to add rows in the DataGrid. My DataGrid Columns are auto generated as the Column List is initialized at run time. When the user enter some value in particular column of the row, I want to validate based on CustomColumn Properties -> MinLength (Minimum string length) & MaxLength(Maximum allowed string length). If the validation fails I want to show the default red border that appears in DataGrid for invalid input. I am following MVVM software architecture pattern.

EDIT

I attached ColumnChanging Listener

MyTable.ColumnChanging += tableColumnChanging;

private void tableColumnChanging(object sender, DataColumnChangeEventArgs e)
{
    //I am able to validate here using my logic
    if(!isValid(e))
    {
        object badValue = e.ProposedValue;
        e.ProposedValue = "Bad Data";
        e.Row.RowError = "The column contains an error";
        e.Row.SetColumnError(e.Column, "Column cannot be " + badValue);
    }
    else
    {
        ... 
    }

}

I am able to validate but I want to display my cell with ! mark if isValid returns false.

Upvotes: 0

Views: 1658

Answers (1)

pushpraj
pushpraj

Reputation: 13669

Well I managed to do a workaround for the same

xaml

<ScrollViewer xmlns:l="clr-namespace:CSharpWPF">
    <ScrollViewer.Resources>
        <DataTemplate DataType="{x:Type l:CustomTable}">
            <DataTemplate.Resources>
                <l:ErrorToVisibilityConverter x:Key="ErrorToVisibilityConverter" />
                <Style TargetType="DataGridCell">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="DataGridCell">
                                <Grid Background="{TemplateBinding Background}">
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text=" ! "
                                                   FontWeight="Bold"
                                                   Foreground="Red">
                                            <TextBlock.Visibility>
                                                <MultiBinding Converter="{StaticResource ErrorToVisibilityConverter}"
                                                              Mode="OneWay">
                                                    <Binding RelativeSource="{RelativeSource FindAncestor,AncestorType=DataGridCell}" />
                                                    <Binding Path="Tag.Errors"
                                                             RelativeSource="{RelativeSource FindAncestor,AncestorType=DataGrid}" />
                                                    <Binding />
                                                </MultiBinding>
                                            </TextBlock.Visibility>
                                        </TextBlock>
                                        <ContentPresenter />
                                    </StackPanel>
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </DataTemplate.Resources>
            <StackPanel>
                ...
            </StackPanel>
        </DataTemplate>
    </ScrollViewer.Resources>
    <ContentControl Content="{Binding TableCollection}" />
</ScrollViewer>

I have added a Style for DataGridCell and defined a custom Template with our extra element to display a ! mark

converter class

namespace CSharpWPF
{
    public class ErrorToVisibilityConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            DataGridColumn column = values[0] as DataGridColumn;
            ObservableCollection<DataColumnChangeEventArgs> errors = values[1] as ObservableCollection<DataColumnChangeEventArgs>;
            DataRowView view = values[2] as DataRowView;

            DataColumnChangeEventArgs args = errors.FirstOrDefault(e => (e.Row == view.Row) && (e.Column.Ordinal == column.DisplayIndex));

            return view.Row.HasErrors && args != null ? Visibility.Visible : Visibility.Collapsed;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

this will detect if the current cell is the one which is affected and return the Visibility.Visible if true otherwise Visibility.Collapsed hence hiding or showing the extra element depending on the error state

change in the CustomTable

public CustomTable()
{
    ...

    Errors = new ObservableCollection<DataColumnChangeEventArgs>();
}

private void tableColumnChanging(object sender, DataColumnChangeEventArgs e)
{
    if (!isValid(e))
    {
        object badValue = e.ProposedValue;
        e.ProposedValue = "Bad Data";
        e.Row.RowError = "The column contains an error";
        e.Row.SetColumnError(e.Column, "Column cannot be " + badValue);
        Errors.Add(e);
        OnPropertyChanged("Errors");
    }
    else
    {
        DataColumnChangeEventArgs args = Errors.FirstOrDefault(ee => (ee.Row == e.Row) && (ee.Column == e.Column));
        if (args != null)
        {
            Errors.Remove(args);
            OnPropertyChanged("Errors");
        }
        //... 
    }
}

public ObservableCollection<DataColumnChangeEventArgs> Errors { get; set; }

result

result

so the whole idea is to add an extra property with notification change capability and use it as a trigger and other property for detecting the appropriate column, and rest is the visibility of our extra ! element in the custom template

Upvotes: 1

Related Questions