In WPF i have a DataDrid with different validations.
So far i used ToolTip in the DataGrid to show the error validation:
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate>
<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" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>
Now the client asked to show the validation error in a different TextBlock/Label in the page- how do i do that? How do i use the: (Validation.Errors)[0].ErrorContent outside the DataGrid?
Assuming you have a list (e.g. MyList) as your DataGrid.ItemsSource which is an ObservableCollection<ItemVM> where ItemVM represents the view-model of each DataGridRow then You can use a MultiBinding with a multi-value converter to save the last error message in a property of currently invalid ItemVM (e.g. ErrorMessage) and then retrieve it in your desired TextBlock.Text through binding.
You can say this is a trick for 3-way binding where TextBlock.Text and CurrentItem.ErrorMessage and (Validation.Errors)[0].ErrorContent are all bound together.
The following codes will clear things up:
In MainWindow:
public ObservableCollection<ItemVM> MyList { get; set; }
public ItemVM CurrentItem { get; set; }
In ItemVM:
public class ItemVM : INotifyPropertyChanged
{
//implementation of INotifyPropertyChanged ...
string _errorMessage;
public string ErrorMessage
{
get
{
return _errorMessage;
}
set
{
_errorMessage = value;
OnPropertyChanged("ErrorMessage");
}
}
// Other Properties ...
}
In Xaml:
<Window.Resources>
<local:MultiConverter x:Key="multiConv"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Grid.Row="0" ItemsSource="{Binding MyList}" >
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate >
<Grid Margin="0,-2,0,-2">
<Ellipse StrokeThickness="0" Fill="Red" Width="{TemplateBinding FontSize}" Height="{TemplateBinding FontSize}" />
<TextBlock Text="!" FontSize="{TemplateBinding FontSize}" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Grid.ToolTip>
<MultiBinding Converter="{StaticResource multiConv}">
<Binding Path="(Validation.Errors)[0].ErrorContent" RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=DataGridRow}"/>
<Binding Path="DataContext.CurrentItem" RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=Window}"/>
</MultiBinding>
</Grid.ToolTip>
</Grid>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>
</DataGrid>
<TextBox Grid.Row="1" Text="{Binding CurrentItem.ErrorMessage}"/>
</Grid>
And finally in MultiValueConverter:
public class MultiConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
((ItemVM)values[1]).ErrorMessage = values[0].ToString();
return values[0].ToString();
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
EDIT
my DataGrid.ItemsSource is DataSet DefaultView- its DB based
Ok, since you are using DataSet.DefaultView as the ItemsSource do it like this:
1) First define a very small simple class that implements INotifyPropertyChangedto wrap your error message:
public class ErrorContainer: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _message;
public string Message
{
get
{return _message;}
set
{_message = value; OnPropertyChanged("Message");}
}
}
2) Then use an instance of this class as a property in your MainWindow:
public partial class MainWindow : Window
{
....
public ErrorContainer Error { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
// initialize ErrorContainer
Error = new ErrorContainer();
// some dummy test data (you're probably getting your data from DB instead)
DataSet ds = new DataSet("MyDataSet");
DataTable dt = new DataTable("MyDataTable");
DataColumn propertyName = new DataColumn("Property");
DataColumn propertyValue = new DataColumn("Value");
DataColumn propertyDate = new DataColumn("Date", typeof(DateTime));
dt.Columns.Add(propertyName);
dt.Columns.Add(propertyValue);
dt.Columns.Add(propertyDate);
dt.Rows.Add("Name1", 1, DateTime.Now);
dt.Rows.Add("Name2", 2, DateTime.Now);
dt.Rows.Add("Name3", 3, DateTime.Now);
ds.Tables.Add(dt);
// bind DataGrid.ItemsSource (do NOT set this again in xaml)
var srcBinding = new Binding();
srcBinding.Source = ds.Tables[0].DefaultView;
myDataGrid.SetBinding(DataGrid.ItemsSourceProperty, srcBinding);
}
}
3) Now change the second path in the multi-binding to DataContext.Error and also change TextBox.Text binding to Error.Message:
<Grid>
<DataGrid x:Name=x:Name="myDataGrid">
....
<MultiBinding Converter="{StaticResource multiConv}">
<Binding Path="(Validation.Errors)[0].ErrorContent" RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=DataGridRow}"/>
<Binding Path="DataContext.Error" RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=Window}"/>
</MultiBinding>
</Grid.ToolTip>
</Grid>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>
</DataGrid>
<TextBox Grid.Row="1" Text="{Binding Error.Message}"/>
</Grid>
4) And finally change your MultiValueConverter.Convert method to this:
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
((ErrorContainer)values[1]).Message = values[0].ToString();
return values[0].ToString();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With