📜 ⬆️ ⬇️

Validation of data in the DataGrid by "columns"

Introduction



In my WPF projects, I mainly use a DataGrid to display data. This control is very convenient, easy to use and also with the release of Visual Studio 2010 is part of the 4th framework.

So, if it is necessary to change the data in the table (DataGrid), I offered the user a modal window in which the data of the object marked in the table was displayed. And the user changed this very object depending on the need. Data validation occurred before the user closes the window. Everything worked smoothly.
')
But once it became necessary to provide the ability for the user to change data directly in the table (as in Excel), without calling the modal window. We must do it.

But when implementing this very action, I ran into one problem: data validation. And if you specifically need to avoid entering the same data into the table. It must be said that the DataGrid contains support for data validation implemented by the ValidationRule object. But the fact is that data validation took place within the limits of the actual object. That is, validation was performed on the “string” DataGrid and not on the “column”. Google could not help. So I had to think a little.


Implementation



So, let's try to validate in the DataGrid by “columns”.

To begin with we will be defined with object which we will be changes. This will be a Person object. But before proceeding with the implementation of this object, create a helper class WPFBase that implements INotifyPropertyChanged to report data changes in the Person object:

public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
* This source code was highlighted with Source Code Highlighter .
  1. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  2. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  3. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  4. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  5. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  6. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  7. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  8. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  9. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  10. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  11. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  12. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  13. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  14. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
  15. public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
    * This source code was highlighted with Source Code Highlighter .
public class WPFBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged( string PropertyName) { if (PropertyChanged != null ) { this .PropertyChanged( this , new PropertyChangedEventArgs(PropertyName)); } } public void OnPropertyChanged(MethodBase MethodeBase) { this .OnPropertyChanged(MethodeBase.Name.Substring(4)); } }
* This source code was highlighted with Source Code Highlighter .


Now create a Person class that contains two properties: Name and Vorname. And we inherit it from WPFBase:

  1. public partial class Person: WPFBase
  2. {
  3. private string _Name;
  4. private string _Vorname;
  5. public string Vorname
  6. {
  7. get { return _Vorname; }
  8. set {_Vorname = value ; OnPropertyChanged (MethodInfo.GetCurrentMethod ()); }
  9. }
  10. public string Name
  11. {
  12. get { return _Name; }
  13. set {_Name = value ; OnPropertyChanged (MethodInfo.GetCurrentMethod ()); }
  14. }
  15. }

* This source code was highlighted with Source Code Highlighter .


You probably noticed that the Person class is designated as partial. This is done for convenience, since we are implementing a couple more interfaces and for readability we divide the class into three parts.

So, we implement the IDataErrorInfo interface. In object-oriented programming, each object is responsible for itself and for the data it contains. Therefore, let the Person object itself worry about how it is “stuffed”. Let's start:

  1. public partial class Person: IDataErrorInfo
  2. {
  3. public string Error
  4. {
  5. get
  6. {
  7. string error;
  8. StringBuilder sb = new StringBuilder ();
  9. error = this [ "Name" ];
  10. if (error! = string .Empty)
  11. sb.AppendLine (error);
  12. error = this [ "Vorname" ];
  13. if (error! = string .Empty)
  14. sb.AppendLine (error);
  15. if (! string .IsNullOrEmpty (sb.ToString ()))
  16. return sb.ToString ();
  17. return "" ;
  18. }
  19. }
  20. public string this [ string columnName]
  21. {
  22. get
  23. {
  24. switch (columnName)
  25. {
  26. case "Name" :
  27. if ( string .IsNullOrEmpty ( this .Name))
  28. Return "Enter the name !!!" ;
  29. break ;
  30. case "Vorname" :
  31. if ( string .IsNullOrEmpty ( this .Vorname))
  32. Return "Enter the surname !!!" ;
  33. break ;
  34. default :
  35. break ;
  36. }
  37. return "" ;
  38. }
  39. }
  40. }

* This source code was highlighted with Source Code Highlighter .

Well, we have ensured that if there is no data in the Name and Vorname properties of the Person object, a message is returned indicating that it is necessary to enter a first or last name.

Well, the final touch: let's implement the IDataEditObject interface. It is necessary so that in case of input of invalid data into the Person object, when the input operation is canceled, the data will return to its original position:

  1. public partial class Person: IEditableObject
  2. {
  3. bool inEdit = false ;
  4. Person tempPerson;
  5. public void BeginEdit ()
  6. {
  7. if (inEdit)
  8. return ;
  9. inEdit = true ;
  10. tempPerson = new Person ();
  11. tempPerson.Name = this .Name;
  12. tempPerson.Vorname = this .Vorname;
  13. }
  14. public void CancelEdit ()
  15. {
  16. if (! inEdit)
  17. return ;
  18. inEdit = false ;
  19. this .Name = tempPerson.Name;
  20. this .Vorname = tempPerson.Vorname;
  21. tempPerson = null ;
  22. }
  23. public void EndEdit ()
  24. {
  25. if (! inEdit)
  26. return ;
  27. inEdit = false ;
  28. tempPerson = null ;
  29. }
  30. }

* This source code was highlighted with Source Code Highlighter .

Create another class ListPerson inheriting from ObservableCollection <Person>. The Personen object will be used as a container for Person objects. Well, for ease of testing, we immediately add to the ListPerson collection several objects:

  1. public class ListPerson: ObservableCollection <Person>
  2. {
  3. public ListPerson ()
  4. {
  5. this .Add ( new Person ()
  6. {
  7. Name = "Ivanov" ,
  8. Vorname = "Sergey"
  9. });
  10. this .Add ( new Person ()
  11. {
  12. Name = "Petrov" ,
  13. Vorname = "Andrew"
  14. });
  15. }
  16. }

* This source code was highlighted with Source Code Highlighter .

We add the ListPerson collection to the window resources:

  1. < Window.Resources >
  2. < dt: ListPerson x: Key = "ListPerson" />
  3. </ Windows.Resources >

* This source code was highlighted with Source Code Highlighter .

And we inform our table where the data is located:

  1. < DataGrid ItemsSource = "{Binding Source = {StaticResource ListPerson}}" />

* This source code was highlighted with Source Code Highlighter .

Now add the following code to the DataGrid:

  1. < DataGrid.RowValidationRules >
  2. < DataErrorValidationRule ValidationStep = "UpdatedValue" />
  3. </ DataGrid.RowValidationRules >

* This source code was highlighted with Source Code Highlighter .

What does this mean: we added a new rule to the DataGrid rules collection, which reports that after changing the data in the Person object, it is necessary to check whether the object itself does not indicate that the data does not comply with the rules we specified when implementing the IDataErrorInfo interface . If so, the DataGrid will respond to the error.

What we lack is that for which we actually started all this: to prevent the entry of the same data in the table.

To do this, create a new class ValidationRule but with our own criteria for checking the validity of the data. In addition, we will need to create an additional property that refers to the same object container as the DataGrid itself. In order for XAML to bind data to this property, it is necessary to declare it as a DependencyProperty. The problem is that for this the object must be inherited from the DependencyObject class. Our new rule inherits from ValidationRule. Therefore, we simply simply create an additional class ListPersonHelper and inherit it from DependencyObject. Okay, stop talking. Let's look at the code better:

  1. class ColumnValidation: ValidationRule
  2. {
  3. private ListPersonHelper _ListPerson;
  4. // This property is auxiliary; it is used to implement DependencyProperty
  5. public ListPersonHelper ListPerson
  6. {
  7. get { return _ListPerson; }
  8. set {_ListPerson = value ; }
  9. }
  10. // Check for valid data
  11. public override ValidationResult Validate
  12. ( object value , System.Globalization.CultureInfo cultureInfo)
  13. {
  14. BindingGroup bg = value as BindingGroup;
  15. Person person = bg.Items [0] as Person;
  16. // If there is an object with the same name or surname in the collection, we report an error!
  17. var result = from p in ListPerson.Items
  18. where p! = person &&
  19. (p.Name == person.Name
  20. || p.Vorname == person.Vorname)
  21. select p;
  22. if (result.Any ())
  23. {
  24. return new ValidationResult ( false , "First Name or Last Name already in the database" );
  25. }
  26. return ValidationResult.ValidResult;
  27. }
  28. }
  29. // Helper class for implementing DependencyProperty
  30. public class ListPersonHelper: DependencyObject
  31. {
  32. // You can privatize the data in the XAML property to the Items property, since it is declared as a DependencyProperty
  33. public ListPerson Items
  34. {
  35. get { return (ListPerson) GetValue (ItemsProperty); }
  36. set {SetValue (ItemsProperty, value ); }
  37. }
  38. public static readonly DependencyProperty ItemsProperty =
  39. DependencyProperty.Register
  40. ( "Items" ,
  41. typeof (ListPerson),
  42. typeof (ListPersonHelper),
  43. new UIPropertyMetadata ( null ));
  44. }

* This source code was highlighted with Source Code Highlighter .

We add ListPersonHelper to window resources. The Items property is set to the same data source as the DataGrid. In the table's ValidationRules, add our new ColumnValidation rule. The ListPerson property is pointed to the ListPerson resource. That's all:

  1. < Window x: Class = "DPTest.MainWindow"
  2. xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns: x = "http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns: dt = "clr-namespace: DPTest"
  5. Title = "MainWindow" Height = "437" Width = "684" >
  6. < Window.Resources >
  7. < dt: ListPerson x: Key = "ListPerson" />
  8. < dt: ListPersonHelper x: Key = "ListPersonHelper"
  9. Items = "{Binding Source = {StaticResource ListPerson}}" />
  10. < ControlTemplate x: Key = "GridError" >
  11. < Grid Margin = "0, -2.0, -2"
  12. ToolTip = "{Binding RelativeSource = {RelativeSource

    FindAncestor, AncestorType = {x: Type DataGridRow}},

    Path = (Validation.Errors) [0] .ErrorContent} "
    >
  13. < Ellipse StrokeThickness = "0" Fill = "Red"
  14. Width = "{TemplateBinding FontSize}"
  15. Height = "{TemplateBinding FontSize}" />
  16. < TextBlock Text = "!" FontSize = "{TemplateBinding FontSize}"
  17. FontWeight = "Bold" Foreground = "White"
  18. HorizontalAlignment = "Center" />
  19. </ Grid >
  20. </ ControlTemplate >
  21. </ Window.Resources >
  22. < Grid >
  23. < DataGrid ItemsSource = "{Binding Source = {StaticResource ListPerson}}"
  24. AutoGenerateColumns = "False"
  25. RowValidationErrorTemplate = "{StaticResource GridError}" >
  26. < DataGrid.Columns >
  27. < DataGridTextColumn
  28. Binding = "{Binding Path = Vorname}"
  29. Header = "Vorname" />
  30. < DataGridTextColumn
  31. Binding = "{Binding Path = Name}"
  32. Header = "Name" />
  33. </ DataGrid.Columns >
  34. < DataGrid.RowValidationRules >
  35. < DataErrorValidationRule
  36. ValidationStep = "UpdatedValue" />
  37. < dt: ColumnValidation
  38. ValidationStep = "UpdatedValue" >
  39. < dt: ColumnValidation.ListPerson >
  40. < dt: ListPersonHelper
  41. Items = "{Binding Source = {StaticResource ListPerson}}" />
  42. </ dt: ColumnValidation.ListPerson >
  43. </ dt: ColumnValidation >
  44. </ DataGrid.RowValidationRules >
  45. </ Datagrid >
  46. </ Grid >
  47. </ Window >

* This source code was highlighted with Source Code Highlighter .


An example can be downloaded here: DataGridColumnValidation.zip (71,37 kb)

Source: https://habr.com/ru/post/100532/


All Articles