Reputation: 1599
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
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
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