I am new to WPF/EF and have a simple Master Detail UI. I have implemented simple cell/row validation using validation rules. I need to validate an integer field in the Detail data: validating against all the datagrid rows. I have tried implementing a binding group at the datagrid level and page (this is an XPAP application) grid level; set various properties, etc. The validation rule fires when I click a Save button but the binding group has no items. I have researched the Internet but found one reference of what I want to do. The solution did not work for me.
1) How can I validate across multiple rows for a field?
2) Can somebody provide a simple example?
I will try to include XAML, code behind, and binding group validation rule if somebody needs to see. Note: I am not implementing an MVVM approach since I am new a newbie; the approach to the Master/Detail UI is taken from a MSDN online video on WPF/EF (by Beth Massie).
TIA
-- Validation rule for binding group (on data group)
public class UtilizationValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
BindingGroup bindingGroup = (BindingGroup)value;
if (bindingGroup != null)
{
int utilTotal = 0;
foreach (var bindingSource in bindingGroup.Items)
{
Utilization util = (Utilization)bindingSource;
utilTotal += util.UtilizationAmount;
}
if (utilTotal > 100)
{
return new ValidationResult(false, "Total utilization cannot exceed 100%.");
}
}
return new ValidationResult(true, null);
}
}
-- here is the code behind if you need to see that
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using DataModel;
using System.ComponentModel;
namespace WorkbookPlan
{
public partial class UtilizationEntry2 : Page
{
private WorkbookPlanEntities db = new WorkbookPlanEntities();
private EmpUtilCollection EmpUtilData;
private CollectionViewSource MasterViewSource;
private CollectionViewSource DetailViewSource;
private ListCollectionView MasterView;
private BindingListCollectionView DetailView;
public UtilizationEntry2()
{
InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
try
{
var employees = from emp in db.Employees.Include("Utilizations")
orderby emp.LastName, emp.FirstName
select emp;
CollectionViewSource organizationLookup = (CollectionViewSource)this.FindResource("OrganizationLookup");
organizationLookup.Source = from org in db.Organizations orderby org.Name select org;
this.EmpUtilData = new EmpUtilCollection(employees, db);
this.MasterViewSource = (CollectionViewSource)this.FindResource("MasterView");
this.DetailViewSource = (CollectionViewSource)this.FindResource("DetailView");
this.MasterViewSource.Source = this.EmpUtilData;
this.MasterView = (ListCollectionView)(MasterViewSource.View);
this.MasterView.CurrentChanged += new EventHandler(MasterView_CurrentChanged);
this.DetailView = (BindingListCollectionView)(DetailViewSource.View);
}
catch (Exception ex)
{
MessageBox.Show("Exception: " + ex.Message);
}
}
void MasterView_CurrentChanged(object sender, EventArgs e)
{
this.DetailView = (BindingListCollectionView)(DetailViewSource.View);
}
private void SaveClick(object sender, RoutedEventArgs e)
{
try
{
bool okay = this.FormBindingGroup.ValidateWithoutUpdate();
if (okay)
{
db.SaveChanges();
MessageBox.Show("Data was saved.", this.Title, MessageBoxButton.OK, MessageBoxImage.Information);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void AddClick(object sender, System.Windows.RoutedEventArgs e)
{
this.DetailView.AddNew();
this.DetailView.CommitNew();
}
private void DeleteClick(object sender, System.Windows.RoutedEventArgs e)
{
if (this.DetailView.CurrentPosition > -1)
{
this.DetailView.RemoveAt(this.DetailView.CurrentPosition);
}
}
private void PreviousClick(object sender, RoutedEventArgs e)
{
this.MasterView.MoveCurrentToPrevious();
}
private void NextClick(object sender, RoutedEventArgs e)
{
this.MasterView.MoveCurrentToNext();
}
-- XAML
<Page x:Class="WorkbookPlan.UtilizationEntry2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WorkbookPlan"
xmlns:localuc="clr-namespace:UserControlLibrary;assembly=UserControlLibrary"
mc:Ignorable="d"
Title="Utilization Data Entry" Name="UtilizationEntry"
Height="230" Width="600" Loaded="Page_Loaded">
<Page.Resources>
<CollectionViewSource x:Key="OrganizationLookup"/>
<CollectionViewSource x:Key="MasterView" />
<CollectionViewSource x:Key="DetailView"
Source="{Binding Source={StaticResource MasterView},
Path='Utilizations'}"/>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="LightSteelBlue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Padding" Value="3" />
<Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="DataGridCell.IsSelected" Value="True">
<Setter Property="Background" Value="LightCyan" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="ValidationErrorTemplate">
<Setter.Value>
<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" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid Name="FormGrid" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="5" />
<RowDefinition Height="25" />
<RowDefinition Height="5" />
<RowDefinition Height="100*" />
<RowDefinition Height="5" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<!--<Grid.BindingGroup>
<BindingGroup x:Name="FormBindingGroup" NotifyOnValidationError="True" >
<BindingGroup.ValidationRules>
<local:UtilizationValidationRule
ValidationStep="RawProposedValue" />
</BindingGroup.ValidationRules>
</BindingGroup>
</Grid.BindingGroup>-->
<Border Grid.Row="0" BorderThickness="1" BorderBrush="Black"
Grid.RowSpan="7" Grid.ColumnSpan="2" Background="White"></Border>
<Border Grid.Row="0" Grid.ColumnSpan="3" BorderThickness="1" BorderBrush="Black"
Background="Gainsboro"></Border>
<localuc:PageHeader Grid.Row="0" Grid.ColumnSpan="2" Content="Utilization Data Entry"/>
<Label Grid.Row="2" Grid.Column="0"
Style="{StaticResource ListboxHeaderText}" VerticalContentAlignment="Center" >Employee List</Label>
<localuc:DataEntryButtons x:Name="ucButtons" Grid.Row="2" Grid.Column="1"
Save="SaveClick" Add="AddClick" Delete="DeleteClick"/>
<Border Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="1" Style="{StaticResource BorderBlueBackground}"></Border>
<Grid Grid.Row="4" Grid.Column="0" DataContext="{Binding Source={StaticResource MasterView}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="5" />
<RowDefinition Height="30" />
<RowDefinition Height="5" />
<RowDefinition Height="30" />
<RowDefinition Height="5" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<localuc:NavigationButtons x:Name="ucNavigationButtons" Grid.Row="0" Grid.ColumnSpan ="2"
Previous="PreviousClick" Next="NextClick"/>
<Label Grid.Row="2" Grid.Column="0">Number:</Label>
<Label Grid.Row="4" Grid.Column="0">Last Name:</Label>
<Label Grid.Row="6" Grid.Column="0">First Name:</Label>
<TextBox Grid.Row="2" Grid.Column="1" Padding="5"
Name="Number" IsReadOnly="True"
Text="{Binding Path=Number, Mode=OneWay}"/>
<TextBox Grid.Row="4" Grid.Column="1" Padding="5"
Name="LastName" IsReadOnly="True"
Text="{Binding Path=LastName, Mode=OneWay}"/>
<TextBox Grid.Row="6" Grid.Column="1" Padding="5"
Name="FirstName" IsReadOnly="True"
Text="{Binding Path=FirstName, Mode=OneWay}"/>
</Grid>
<Border Grid.Row="4" Grid.Column="1" BorderThickness="1" BorderBrush="Black"
Background="White">
<DataGrid Grid.Row="4" Grid.Column="1" Name="ListView1" AutoGenerateColumns="false"
CanUserAddRows="false" CanUserDeleteRows="false" GridLinesVisibility="All"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource DetailView}}"
Background="LightGray" BorderBrush="Cornsilk" >
<DataGrid.BindingGroup>
<BindingGroup x:Name="FormBindingGroup" NotifyOnValidationError="True" >
<BindingGroup.ValidationRules>
<local:UtilizationValidationRule
ValidationStep="RawProposedValue" />
</BindingGroup.ValidationRules>
</BindingGroup>
</DataGrid.BindingGroup>
<DataGrid.Resources>
<Style x:Key="errorStyle" TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="-2"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="BorderBrush" Value="red"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.RowValidationRules>
<local:UtilizationRowValidationRule ValidationStep="RawProposedValue"/>
</DataGrid.RowValidationRules>
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Organization" Width="150"
ItemsSource="{Binding Source={StaticResource OrganizationLookup}}"
SelectedValueBinding="{Binding Path=Organization,
ValidatesOnExceptions=True,ValidatesOnDataErrors=True,
NotifyOnValidationError=True,UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name">
</DataGridComboBoxColumn>
<DataGridTextColumn Header="Utilization" Width="75"
EditingElementStyle="{StaticResource errorStyle}">
<DataGridTextColumn.Binding>
<Binding Path="UtilizationAmount"
NotifyOnValidationError="true"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnExceptions="true"
ValidatesOnDataErrors="true">
<Binding.ValidationRules>
<local:PercentValidationRule/>
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Border>
<ListView Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2"
ItemsSource="{Binding Path=(Validation.Errors), ElementName=FormGrid}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Foreground="Red" Content="{Binding Path=ErrorContent}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>