I want to validate my DataGrid
rows for errors, and if there are any errors present then the 'Save' button should be disabled or at least there should be some message saying there are errors.
After some research I stumbled upon the following post: Detecting WPF Validation Errors
The post has as solution to use this piece of code for validation a DataGrid on errors:
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = IsValid(sender as DependencyObject);
}
private bool IsValid(DependencyObject obj)
{
// The dependency object is valid if it has no errors and all
// of its children (that are dependency objects) are error-free.
return !Validation.GetHasError(obj) &&
LogicalTreeHelper.GetChildren(obj)
.OfType<DependencyObject>()
.All(IsValid);
}
However, I am using the ICommand
implementation for my button and I can't seem to figure out how to implement the IsValid
function with it. I tried several things with registering the DependencyObject
, using a Binding
and some other stuff.
Could anyone point me in the right direction, it should be rather simple I guess but I just can't get my head around it.
Below the implementation of my button:
public class MyViewModel
{
public ICommand MyCommandButton { get; set; }
public MyViewModel()
{
MyCommandButton = new BaseCommand(MyCommandFunction);
this.Initialize();
}
private void MyCommandFunction(object obj)
{
//... some (not yet implemented) logic
}
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
: this(method, null)
{
}
public BaseCommand(Action<object> method, Predicate<object> canExecute)
{
_method = method;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
}
XAML Button:
<Button
Name="MyButton"
Command="{Binding MyCommandButton}"
/>
UPDATE
After some more research (Object parameter
of MyCommandCanExecuteFunction
in @RajN example was returning null
everytime), I stumbled upon the following post: object sender is always null in RelayCommand which states I should use the CommandParameter
for it not to be null
.
Now the IsValid
function works but keeps returning TRUE ( so it's Valid) even when there are errors on the datagrid
.
I suspect there is something wrong with the datagrid / data itself so i'll post that below as well:
The DataGrid
<DataGrid x:Name="MainGrid"
ItemsSource="{Binding ItemList}"
SelectedItem="{Binding SelectedItem,Converter={StaticResource ignoreNewItemPlaceHolderConverter}}"
AutoGenerateColumns="False"
DataContextChanged="OnMainGridDataContextChanged"
CanUserAddRows="False"
>
<DataGrid.Columns>
<DataGridTextColumn>
<DataGridTextColumn.Header>
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
<Button
Height="25"
Width="25"
Style="{StaticResource MaterialDesignFloatingActionMiniDarkButton}"
Command="{Binding Path=DataContext.AddRowCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
ToolTip="Voeg regel toe"
>
<materialDesign:PackIcon
Kind="Add"
Height="24"
Width="24" />
</Button>
</Grid>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding SequenceNumber}"
Header="Line"
EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnEditingStyle}"
Width="63"
IsReadOnly="true" />
<DataGridTextColumn Header="Width" Width="100" IsReadOnly="false" EditingElementStyle="{StaticResource errTemplate}" >
<DataGridTextColumn.Binding>
<Binding Path="Width" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<validationbinding1:RequiredRule ValidatesOnTargetUpdated="True"/>
<validationbinding1:NumericRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn Header="Height" Width="100" IsReadOnly="false" EditingElementStyle="{StaticResource errTemplate}" >
<DataGridTextColumn.Binding>
<Binding Path="Height" ValidatesOnDataErrors="True" NotifyOnValidationError = "True">
<Binding.ValidationRules>
<validationbinding1:RequiredRule />
<validationbinding1:NumericRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
~~Some other columns which are left out
</DataGrid.Columns>
</DataGrid>
The DataGrid properties and bindings:
On the view itself
private void OnMainGridDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
m_MyViewModel = (m_MyViewModel)this.DataContext;
}
In the ViewModel
public class MyViewModel : MyModel
{
// Property variables
private ObservableCollection<ItemListDetails> p_ItemList;
public ICommand MyCommandButton { get; set; }
public MyViewModel()
{
MyCommandButton = new BaseCommand(MyCommandFunction, canExecute);
this.Initialize();
}
private bool canExecute (object obj)
{
return IsValid(obj as DependencyObject);
}
private bool IsValid(DependencyObject obj)
{
// The dependency object is valid if it has no errors and all
// of its children (that are dependency objects) are error-free.
if (obj == null)
return true;
return !Validation.GetHasError(obj) &&
LogicalTreeHelper.GetChildren(obj)
.OfType<DependencyObject>()
.All(IsValid);
}
private void MyCommandFunction(object obj)
{
//... some (not yet implemented) logic
}
private void AddRow(object obj)
{
ItemListDetails Item = new ItemListDetails
{
Width = 0,
Height = 0,
};
p_ItemList.Add(Item);
}
public ObservableCollection<ItemListDetails> ItemList
{
get { return p_ItemList; }
set
{
p_ItemList = value;
this.MutateVerbose(ref p_ItemList, value, RaisePropertyChanged());
}
}
/// <summary>
/// The currently-selected item.
/// </summary>
public ItemListDetails SelectedItem { get; set; }
/// <summary>
/// Updates the ItemCount Property when the list collection changes.
/// </summary>
void OnListChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Update item count
this.ItemCount = this.p_ItemList.Count;
// Resequence list
SequencingService.SetCollectionSequence(this.p_ItemList);
}
/// <summary>
/// Initializes this application.
/// </summary>
private void Initialize()
{
// Create item list
p_ItemList = new ObservableCollection<ItemListDetails>();
// Subscribe to CollectionChanged event
p_ItemList.CollectionChanged += OnListChanged;
// Initialize list index
this.p_ItemList = SequencingService.SetCollectionSequence(this.p_ItemList);
// Update bindings
//base.RaisePropertyChangedEvent("GroceryList");
//this.MutateVerbose(ref _materiaal, value, RaisePropertyChanged());
}
}
The ItemListDetails class of the DataGrid Items
public class ItemListDetails : ObservableObject, ISequencedObject
{
// Property variables
private int p_SequenceNumber;
private int p_Width;
private int p_Height;
/// <summary>
/// Default constructor
/// </summary>
public ItemListDetails ()
{
}
/// <summary>
/// Paramterized constructor.
/// </summary>
public ItemListDetails (int width, int height, int itemIndex)
{
p_Width = width;
p_Height = height;
p_SequenceNumber = itemIndex;
}
/// <summary>
/// The sequential position of this item in a list of items.
/// </summary>
public int SequenceNumber
{
get { return p_SequenceNumber; }
set
{
p_SequenceNumber = value;
base.RaisePropertyChangedEvent("SequenceNumber");
}
}
/// <summary>
/// The width
/// </summary>
public int Width
{
get { return p_Width; }
set
{
p_Width = value;
base.RaisePropertyChangedEvent("Int");
}
}
/// <summary>
/// The height
/// </summary>
public int Height
{
get { return p_Height; }
set
{
p_Height = value;
base.RaisePropertyChangedEvent("Int");
}
}
}
The MyModel
class contains some other fields which are not contained in the DataGrid
.