Hi,
you can use data object with INotifyPropertyChanged to inform about changes and IDataErrorInfo for detection of errors in data objects. Inheriting Collection from ObservableCollection you can inform the UI about changes and switch enable / disable of command button using RelayCommand class.
Try following demo:
XAML:
<Window x:Class="WpfApp1.Window01"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp01"
mc:Ignorable="d"
Title="Validating in DataGrid" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid ItemsSource="{Binding View}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="TransformerNumber">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBox Name="txtNumber">
<TextBox.Text>
<Binding Path ="Number" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"
NotifyOnValidationError="True" ValidatesOnDataErrors="True" ValidatesOnExceptions="True"
ValidatesOnNotifyDataErrors="True">
<Binding.ValidationRules>
<local:TransformerNumberValidationRule ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1">
<Button Content="Save" Command="{Binding Cmd}" Margin="5"/>
</StackPanel>
</Grid>
</Window>
And classes:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace WpfApp01
{
public partial class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
TrulyObservableCollection<Data> col = new TrulyObservableCollection<Data>();
for (int i = 1; i < 10; i++) col.Add(new Data() { ID = i, Number = 0 });
col.CollectionChanged += (sender, e) => { OnPropertyChanged(nameof(Cmd)); };
cvs.Source = col;
}
private CollectionViewSource cvs = new CollectionViewSource();
public ICollectionView View { get => cvs.View; }
public ICommand Cmd { get => new RelayCommand(CmdExec, CanCmdExec); }
private void CmdExec(Object obj)
{
//
}
private bool CanCmdExec(object obj)
{
foreach (var item in View)
{
var d = item as Data;
if (d != null && !string.IsNullOrEmpty(d.Error)) return false;
}
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public class TransformerNumberValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var be = value as BindingExpression;
if (be == null) return new ValidationResult(false, "Invalid use of validation");
var d = be.DataItem as Data;
if (d == null) return new ValidationResult(false, "Invalid use of validation");
if (!string.IsNullOrEmpty(d.Error)) return new ValidationResult(false, "Please enter a value in the range: 10 - 50");
return new ValidationResult(true, null);
}
}
public sealed class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public TrulyObservableCollection() => CollectionChanged += FullObservableCollectionCollectionChanged;
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems) this.Add(item);
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null) foreach (Object item in e.NewItems) ((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
if (e.OldItems != null) foreach (Object item in e.OldItems) ((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e) => OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender)));
}
public class RelayCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _action;
public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }
public void Execute(object o) => _action(o);
public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
public class Data : INotifyPropertyChanged, IDataErrorInfo
{
public int ID { get; set; }
private int _number;
public int Number
{
get => this._number;
set { this._number = value; OnPropertyChanged(); }
}
public string Error => (Number < 10 || Number > 50) ? "Please enter a value in the range: 10 - 50" : "";
public string this[string columnName] => (Number< 10 || Number> 50)? "Please enter a value in the range: 10 - 50":"";
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}