WPF- validation error event doesn't fire

2020-08-18 05:55发布

i think i have read already all related articles but non of them help..

im trying to enable/disable a save button of datagrid by the error state- but with no success.

this is my code:

contractor:

AddHandler(Validation.ErrorEvent, new RoutedEventHandler(OnErrorEvent));

XAML:

    <Page
  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:col="clr-namespace:System.Collections;assembly=mscorlib"
 xmlns:local="clr-namespace:Metsuka_APP" x:Class="Metsuka_APP.MichlolimManagment" 
  mc:Ignorable="d" 
  d:DesignHeight="500" d:DesignWidth="500"
Title="MichlolimManagment"
x:Name="Michlolim_Managment" Validation.Error="Michlolim_Managment_Error">
<Page.Resources>

<DataGrid x:Name="AGAFIMDataGrid" VerticalAlignment="Center" RowEditEnding="rowEditEnding" Margin="10" FlowDirection="RightToLeft" Height="340"
    AutoGenerateColumns="False" EnableRowVirtualization="True"
                  ItemsSource="{Binding Source={StaticResource aGAFIMViewSource}}"   Grid.Row="1"
                  RowDetailsVisibilityMode="VisibleWhenSelected"
                 ScrollViewer.CanContentScroll="True"
                 ScrollViewer.VerticalScrollBarVisibility="Auto" 
                 HorizontalGridLinesBrush="Silver"
                 VerticalGridLinesBrush="Silver">
            <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="Background" Value="Red"/>
                            <Setter Property="ToolTip" 
          Value="{Binding RelativeSource={RelativeSource Self},
            Path=(Validation.Errors)[0].ErrorContent}"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="agaf_nameColumn"  Header="name" Width="*">
                    <DataGridTextColumn.Binding>
                        <Binding Path="agaf_name" NotifyOnValidationError="True" >
                            <Binding.ValidationRules>
                            <local:MichlolimValidationRule ValidationStep="UpdatedValue"/>
                        </Binding.ValidationRules>
                    </Binding>
                        </DataGridTextColumn.Binding>
                </DataGridTextColumn>
            </DataGrid.Columns>
            <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"  />
                    </Grid>
                </ControlTemplate>
            </DataGrid.RowValidationErrorTemplate>
        </DataGrid>

code behind:

    private int errorCount;

    private void OnErrorEvent(object sender, RoutedEventArgs e)
    {
        var validationEventArgs = e as ValidationErrorEventArgs;
        if (validationEventArgs == null)
            throw new Exception("Unexpected event args");
        switch (validationEventArgs.Action)
        {
            case ValidationErrorEventAction.Added:
                {
                    errorCount++; break;
                }
            case ValidationErrorEventAction.Removed:
                {
                    errorCount--; break;
                }
            default:
                {
                    throw new Exception("Unknown action");
                }
        }
        btnSavePop.IsEnabled = errorCount == 0;
    }

but the "OnErrorEvent" never fires- any idea why?

4条回答
Viruses.
2楼-- · 2020-08-18 06:09

Try creating a class like the following:

public class AgafDescriptor : INotifyPropertyChanged, IDataErrorInfo
{
    private string _name;

    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            if (_name != value)
            {
                _name = value;

                RaisePropertyChanged(x => x.Name);
            }
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged<T>(Expression<Func<AgafDescriptor, T>> propertyExpression)
    {
        PropertyChangedEventHandler localPropertyChanged = this.PropertyChanged as PropertyChangedEventHandler;

        if ((localPropertyChanged != null) && (propertyExpression != null))
        {
            MemberExpression body = propertyExpression.Body as MemberExpression;

            if (body != null)
            {
                localPropertyChanged(this, new PropertyChangedEventArgs(body.Member.Name));
            }
        }
    }

    #endregion

    #region IDataErrorInfo Members

    // Does nothing in WPF.
    public string Error
    {
        get { return null; }
    }

    public string this[string columnName]
    {
        get
        {
            string returnVal = null;

            if (string.Equals("Name", columnName, StringComparison.Ordinal))
            {
                if (string.IsNullOrWhiteSpace(Name))
                {
                    returnVal = "A name must be supplied.";
                }
            }

            return returnVal;
        }
    }

    #endregion
}

This will provide an error whenever there is a change to the Name property. Please note that if you wish to trigger new validation checks without modifying the property you just need to call:

  RaisePropertyChanged(x => x.Name);

You will then need to change your binding to something like:

<DataGridTextColumn x:Name="agaf_nameColumn"  Header="name" Width="*" Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnValidationError=True}"/>

Note you will have to load your data from the DB and create a descriptor for each item you want to display in the DataGrid.

The reason behind why you aren't seeing the event being fired:

You aren't raising property change events (INotifyPropertyChanged or through a DependencyProperty) therefore the UI won't receive updates and the event won't be fired because it hasn't received an update to then perform the validation. By binding direct to your DB then you aren't raising the property change events. You can see that the Name property I suggested in my answer does raise the property changed event

查看更多
狗以群分
3楼-- · 2020-08-18 06:15

You need to set NotifyOnValidationError="True" on your bindings - otherwise, the event won't be raised. I would recommend using the IDataErrorInfo or INotifyDataErrorInfo interfaces instead for an MVVM error handling approach.

查看更多
Explosion°爆炸
4楼-- · 2020-08-18 06:31

From my sample of your code I think you are missing specifying UpdateSourceTrigger="PropertyChanged" or UpdateSourceTrigger="LostFocus" on the DataGridTextColumn in the DataGrid instead of using the default behavior of the DataGrids binding.

That is assuming my assumptions are correct see bottom.

Your code causes OnErrorEvent to fire if i change:

<DataGridTextColumn.Binding>
  <Binding Path="agaf_name" NotifyOnValidationError="True"  >
  ...

To include UpdateSourceTrigger for PropertyChanged or LostFocus like so:

<DataGridTextColumn.Binding>
   <Binding Path="agaf_name" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged" >
   ....

enter image description here

Assumptions - For your ValidationRule to test i made it always return false (see below). And for the test item source i am binding to a string value in 'agaf_name' property.

public class MichlolimValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        return new ValidationResult(false, "bad");
    }
}
查看更多
劫难
5楼-- · 2020-08-18 06:34

The attribute

Validation.Error="Michlolim_Managment_Error"

in your XAML already sets a handler for the error event on the window level, however, to a method which isn't defined in your code behind fragment. Your AddHandler call looks ok, but, depending on where it is in the constructor, it might get overridden by the XAML event handler definition.

Probably not related, but you might want to change

(Validation.Errors)[0].ErrorContent

to

(Validation.Errors)/ErrorContent

in your ToolTip bindings, since the former causes binding errors if there are no errors. Unfortunately, it is still contained in the documentation samples...

查看更多
登录 后发表回答