Click here to Skip to main content
15,880,405 members
Please Sign up or sign in to vote.
4.78/5 (4 votes)
See more:
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
     {
         // for Utilization Entry 
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
         {
             // the UtilizationAmount total cannot > 100%
             BindingGroup bindingGroup = (BindingGroup)value;
 
            if (bindingGroup != null)
             {
                 // utilization total
                 int utilTotal = 0;
 
                foreach (var bindingSource in bindingGroup.Items)
                 {
                     // get item
                     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
 {
     /// <summary>
     /// Interaction logic for page
     /// </summary>
     public partial class UtilizationEntry2 : Page
     {
 

        // NOTE: The Master-Detail UIs are different than those that use just
         // fields for data entry and use the EF entity IDataErrorInfo interface
         // for validation
         // Seemingly the Detail collection does not fire the EF entity class
         // I am not sure why. Hence for validation must be done by validation rules
         // at a row/column level.
 
        private WorkbookPlanEntities db = new WorkbookPlanEntities();
         private EmpUtilCollection EmpUtilData;
 
        // collections for EF data 
        private CollectionViewSource MasterViewSource;
         private CollectionViewSource DetailViewSource;
         
        // collections wired to UI
         private ListCollectionView MasterView;
         private BindingListCollectionView DetailView;
 
 
 
        public UtilizationEntry2()
         {
             InitializeComponent();
         }
 
        private void Page_Loaded(object sender, RoutedEventArgs e)
         {
 
            try
             {
 
 
 
                // get data from model
                 var employees = from emp in db.Employees.Include("Utilizations")
                                 orderby emp.LastName, emp.FirstName
                                 select emp;
 

                CollectionViewSource organizationLookup = (CollectionViewSource)this.FindResource("OrganizationLookup");
                 // lookup
                 organizationLookup.Source = from org in db.Organizations orderby org.Name select org;
 

                // create obs. collection of data from EF data context
                 this.EmpUtilData = new EmpUtilCollection(employees, db);
 
                // set up Master view: Employees which is read only since we are not changing Employee table
                 this.MasterViewSource = (CollectionViewSource)this.FindResource("MasterView");
                 // set up detail view: Utilization which will be changed
                 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);
 
            // set navigation buttons
             // this.PreviousClick.IsEnabled = this.MasterView.CurrentPosition > 0;
             //this.NextClick.IsEnabled = this.MasterView.CurrentPosition < this.MasterView.CurrentPostion - 1;
         }
 

    
        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)
         {
 
            // for detail only
             this.DetailView.AddNew();
             this.DetailView.CommitNew();
            
           
          
        }
 
        private void DeleteClick(object sender, System.Windows.RoutedEventArgs e)
         {
 
            // for detail only
             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>
Posted
Updated 14-Jun-23 3:05am
v2
Comments
[no name] 18-Jun-23 14:16pm    
You build a data model (in memory). You update the data model. You valide the model. You save the model. The UI (view) is in sync with the data model. The UI is just a reflection. If you start integrating the model and the view, you get the type of "bleed" you're trying to fix.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900