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
?
You probably have a class like ValidationRules that holds a Validate Function.
due to the magnificent binding that occurs, you no longer need to follow the validation as it is binded. This however sticks to the control.
You can also create an event on the control inside the datagrid that is being used to validate.
<DataGridTemplateColumn Header="MyValue">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Textbox Text="{Binding MyValue}" OnLeave="tb_OnLeave"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
In you behind you can then have something like:
private void tb_OnLeave(object sender, EventArgs e)
{
//sender defines your value;
Textbox s = (Textbox)sender;
//validate the Text property of the sender
if(ValidationRule.ValidateTextBox(s.Text)); //true
return;
//set the focus on your control
s.Focus();
//set the text of your other control
YourOtherControl.Text = string.Format("The value of textbox: {0} is not valid", s.Text);
}
The point here is that you will not use the validationBinding anymore. I think there are ways to do it via binding but i'm not strong enough on that topic to give you a better answer.
Perhaps the RelativeSource can be used to point to another control. But as I said, I don't know enough to be sure.
Assuming you have a list (eg 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
(eg 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 INotifyPropertyChanged
to 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();
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.