سلام دوستان .
یه DataGrid دارم که به یه ObservableCollection<Person> ، بایندینگ (Binding) شد .
کلاس Person ام پروپرتی هایی از قبیل PersonID و FirstName و LastName و PhoneNumber و NationalCode داره .

میخوام اعتبارسنجیِ این بایندینگ را بعد از اینکه کاربر ، مقداری را در DataGrid وارد کرد و قبل از اینکه به Binding Source بره ، این اعتبار سنجی انجام بشه و تصمیم گرفته بشه که بره یا نره .

طبق آموزش در لینک زیر :

How to: Implement Validation with the DataGrid Control - WPF .NET Framework | Microsoft Learn

--------

من این کارها را انجام دادم اما مشکل اینه که وقتی اطلاعات یه سطر را وارد کردم و دکمه ی Enter در کیبرد را زدم (یعنی اطلاعات سطر وارد شد) ، در متد Validate در کلاس DataGridItemsSource_Persons_ValidationRule (در کد زیر) ، مقدار متغییر person در این متد را وقتی میگیرم ، مقدارش ، همون اطلاعاتی نیست که کاربر وارد کرده بود .

بلکه مقدار متغییر person ،اطلاعاتی هست که (تا اون لحظه که در متد Validate هست) ، درون همون Binding Source Object بود . یعنی تا زمانی که اون متد اجرا بشه ، اطلاعات آیتم مورد نظر در پروپرتیِ Persons (در کلاس PhoneBook) را بهم نشون میده که این اطلاعات از قبل بود و برام مهم نیست .

بلکه من در متد Validate ، اطلاعاتی که کاربر ویرایش کرد را نیاز دارم تا تصمیم بگیرم که آیا مقدار جدیدی که وارد شد ، معتبر هست یا نه .

کد xaml در MainWindow.xaml :


<Window x:Class="WPF_Practice_2.MainWindow"
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:WPF_Practice_2"
xmlns:localClasses="clr-namespace:WPF_Practice_2.Classes"
xmlns:bindingValidations="clr-namespace:WPF_Practice_2.Classes.BindingValidation s"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">


<Window.Resources>


<localClasses:ViewModel x:Key="ViewModelKey"/>


<CollectionViewSource x:Key="PersonsCollectionViewSourceKey"
Source="{Binding
Source={StaticResource ViewModelKey},
Path=PhoneBook.Persons}"/>


<ControlTemplate TargetType="{x:Type Control}" x:Key="PersonValidationRulesControlTemplateKey">
<Grid Margin="0,-2,0,-2"
ToolTip="{Binding RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type DataGridRow}},
Path=(Validation.Errors)[0].ErrorContent}">
<Ellipse StrokeThickness="0" Fill="Red"
Width="{TemplateBinding FontSize}"
Height="{TemplateBinding FontSize}" />
<TextBlock Text="!" FontSize="{TemplateBinding FontSize}"
FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center" />
</Grid>
</ControlTemplate>


</Window.Resources>




<Grid DataContext="{Binding
Source={StaticResource PersonsCollectionViewSourceKey}}">


<DataGrid Name="PhoneBookDataGrid" HorizontalAlignment="Center" Height="271"
Margin="0,153,0,0" VerticalAlignment="Top" Width="780"
RowValidationErrorTemplate="{StaticResource PersonValidationRulesControlTemplateKey}"
ItemsSource="{Binding}"


<DataGrid.RowValidationRules>
<bindingValidations:DataGridItemsSource_Persons_Va lidationRule/>
</DataGrid.RowValidationRules>

</DataGrid>

</Grid>
</Window>




کد کلاس اعتبار سنجی :


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;


namespace WPF_Practice_2.Classes.BindingValidations
{
public class DataGridItemsSource_Persons_ValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
BindingGroup bindingGroup = value as BindingGroup;
if (bindingGroup == null)
return new ValidationResult(false, "عمل تبدیل ، ناموفق بود .");
Person person = bindingGroup.Items[0] as Person;
if (person == null)
return new ValidationResult(false, "عمل تبدیل ، ناموفق بود .");




if (person.PersonID < 0)
return new ValidationResult(false, "شناسه باید عددی مثبت باشد");


return ValidationResult.ValidResult;
}






}
}





بقیه ی کدهای کلاس های سی شارپ :


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Navigation;


namespace WPF_Practice_2.Classes
{
public class Person ////: IDataErrorInfo
{
public int PersonID { get; set; }


public string FirstName { get; set; }


public string LastName { get; set; }


public long PhoneNumber { get; set; }


public string NationalCode { get; set; }








public Person()
{


}




public Person(string firstName, string lastName, long phoneNumber, string nationalCode)
{
this.FirstName = firstName;
this.LastName = lastName;
this.PhoneNumber = phoneNumber;
this.NationalCode = nationalCode;
}




public Person(int personID, string firstName, string lastName, long phoneNumber, string nationalCode)
{
this.PersonID = personID;
this.FirstName = firstName;
this.LastName = lastName;
this.PhoneNumber = phoneNumber;
this.NationalCode = nationalCode;
}


}
}




و


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace WPF_Practice_2.Classes
{
public class PhoneBook
{


public ObservableCollection<Person> Persons { get; set; }


public PhoneBook(ObservableCollection<Person> persons)
{
this.Persons = persons;
}






}
}


و


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace WPF_Practice_2.Classes
{
public class ViewModel
{
public PhoneBook PhoneBook { get; set; }


public ViewModel()
{
ObservableCollection<Person> persons = new ObservableCollection<Person>();
Person person = new Person(574, "احمد", "متوسلیان", 109854, "574");
Person person2 = new Person(65, "غلامحسین", "افشردی", 58, "12");
Person person3 = new Person(84, "محمد", "جهان آرا", 09115478, "00");
Person person4 = new Person(313, "قاسم", "سلیمانی", 98, "");
Person person5 = new Person(879, "سید مجتبی", "میر لوحی", 42515874, "7744");


persons.Add(person);
persons.Add(person2);
persons.Add(person3);
persons.Add(person4);
persons.Add(person5);


this.PhoneBook = new PhoneBook(persons);
}




}
}



==============


من حتی از رویداد RowEditEnding در شی DataGrid هم استفاده کردم . این رویداد ، قبل از اینکه متد Validate (برای اعتبارسنجی) اجرا میشه اما دقیقا مثل متد Validate ، اطلاعاتی که کاربر برای اون سطر را (در رویداد RowEditEnding و در متغییر رویدادیِ e.Row.Item) بهم نمیده . بلکه این هم اطلاعات همون سطری که فعلا در پروپرتیِ Persons در کلاس PhoneBook هست را میده که اون اطلاعات اصلا ملاکم نیست و به دردم نمیخوره .

برای شما هم این رویداد ، این طوره؟
یعنی زمانی که کاربر مقادیر سطری را ویرایش یا اضافه کرد ، نشون نمیده؟

------

بعد هم در همون Binding ، پروپرتیِ UpdateSourceTrigger را روی مقدار Explicit گذاشتم :


ItemsSource="{Binding UpdateSourceTrigger=Explicit}"


تا خودم با فراخونی متد BindingExpression.UpdateSource ، بعد از اینکه اطلاعات وارد شده ی کاربر را گرفتم ، اون متد را فراخوانی کنم تا Binding اش ، مقدار Target Object را برای Source Object ارسال کنه اما به احتمال زیاد چون (از طرف DataGrid) ، مقدار Binding را به کل کالکشن انجام دادم اما از اون طرف ، ,وقتی ، سطری اضافه یا ویرایش میشه ، کلِ شیِ کالکشن در Binding Source Object ، تغییری نمیکنه بلکه فقط سطرها که همون آیتم های اون کالکشن هستند ، تغییر میکنن ، احتمالا به همین دلیل تغییر در مقدارِ پروپرتیِ UpdateSourceTrigger ام تاثیری در رفتارش نداره .

=============


نهایتا اینکه دوستان به نظرتون چی کار کنم؟
فرضا اگه میشه ، توی همون متد Validate یا اگه نشد ، حتی در رویداد RowEditEnding بتونم به مقداری که کاربر ویرایش یا اضافه کرد ، دسترسی پیدا کنم؟
یا روش های دیگه برای اعتبار سنجی ، چه راهکاری پیشنهاد میدین؟

دلیلش چیه که در متد Validate یا در رویداد RowEditEnding ، اطلاعاتی که کاربر ویرایش کرد را نمیده و آیا برای شما هم این دو تا متد و رویداد ، همینطوره؟

و همچنین راهکارتون چیه؟


------

روش پیاده سازی اینترفیس IDataErrorInfo را برای کالکشن انجام دادم اما هم اصلا اجرا نشد و اگه هم اجرا بشه ، نمیدونم در اون حالت برای کدوم آیتمِ کالکشن ، تغییرات اتفاق میافته .

- روش دیگه اینکه بذارم اول ، Binding Source Object و کلا آیتم های کالکشن ام ، اول تغییرات روشون اِعمال بشن و بعد اعتبارسنجی انجام بشه (مثل همونی که در لینک مایکروسافت گفت و مقدار پروپرتی ValidationStep ئه ValidationRule (در کد من ، همون کلاس DataGridItemsSource_Persons_ValidationRule) را به مقدار UpdatedValue تغییر بدم تا این اتفاق بیفته) و بعد بیام اعتبار سنجی را انجام بدم

که در اون صورت ، هم زیاد کارایی نداره و هم اینکه خوب همین کار را بدون اعتبار سنجی هم میشه انجام داد . یعنی در لایه ی Model میشه هر وقت آیتم کالکشن مون تغییر کرد ، با خبر بشیم و این کار را انجام بدیم . البته یه کم سخت تره . و هم اینکه در این صورت ، اگه اعتبار سنجی معتبر نبود ، دوباره اون آیتم را باید برگردونیم به مقدار قبلی اش که فکر کنم با پیاده سازی اینترفیس IEditableObject ، این کار قابل انجام باشه .

این روش انگار نسبت به روش های دیگه ، کاراتر به نظر میرسه اما خوب باز هم کارایی اش کمتر (نسبت به اینکه اگه جواب سئوالم را پیدا کنم) و هم کدهاش پیچیده تر میشه .

روش دیگه ، شاید از Converter ها بشه استفاده کرد (تست نکردم) اما اگه هم بشه ، اصولی نیست .

تشکر دوستان .