IDataErrorInfo only works when property has been set and cleared again

92 views Asked by At

So, in my code I use IDataError to do validation, but I am seeing some strange behavior lately on some views I have made. My class implements INotifyPropertyChanged as well as IDataErrorInfo and this is my model class:

using System;
using System.ComponentModel;
using TMWpfClient.Models.System.Base;

namespace TMWpfClient.Models.Tournament.Operation
{
    public class MatchComplaintInfo : BaseEntity, IDataErrorInfo
    {
        #region private variables
        private string _complaint;
        private string _filedBy;
        private Guid _matchId;
        #endregion

        #region constructors
        public MatchComplaintInfo() : base() { }

        public MatchComplaintInfo(Guid matchid) : base()
        {
            MatchId = matchid;
        }

        public MatchComplaintInfo(Guid id, Guid matchid, string complaint, string filedby, DateTime created,
            Guid createdby) : base(id, created, createdby)
        {
            _complaint = complaint;
            _filedBy = filedby;
            _matchId = matchid;
        }
        #endregion

        #region public properties
        public Guid MatchId { get; set; }
        public string Complaint
        {
            get => _complaint;
            set
            {
                if (_complaint == value)
                    return;
                _complaint = value;
                IsDirty = true;
                OnPropertyChanged("Complaint");
            }
        }

        public string FiledBy
        {
            get => _filedBy;
            set
            {
                if (_filedBy == value)
                    return;
                _filedBy = value;
                IsDirty = true;
                OnPropertyChanged("FiledBy");
            }
        }
        #endregion

        public string this[string columnName]
        {
            get
            {
                switch (columnName)
                {
                    case "Complaint" when string.IsNullOrEmpty(Complaint):
                        return "The complaint has to filled out!";
                    case "FiledBy" when string.IsNullOrEmpty(FiledBy):
                        return "A name of the person filing the complaint has to be supplied";
                    default:
                        return string.Empty;
                }
            }
        }

        public string Error { get; }
    }
}

My BaseEntity class:

   public class BaseEntity : ObservableObject, IDataStatus, IDirty, IBaseEntity
    {
        private Guid _id;
        private DateTime _created;
        private Guid _createdBy;
        private bool _isDirty;
        private bool _isNew;

        public Guid Id
        {
            get { return _id; }
            set { _id = value; }
        }

        public DateTime Created
        {
            get { return _created; }
            set { _created = value; }
        }

        public Guid CreatedBy
        {
            get { return _createdBy; }
            set { _createdBy = value; }
        }

        public BaseEntity()
        {
            Id = Guid.NewGuid();
            Created = DateTime.Now;
            IsNew = IsDirty = true;
        }

        public BaseEntity(Guid id, DateTime created, Guid createdby)
        {
            _id = id;
            _created = created;
            _createdBy = createdby;
        }

        #region datastatus

        public bool IsDirty
        {
            get { return _isDirty; }
            set
            {
                if (_isDirty == value)
                    return;
                _isDirty = value; 
                OnPropertyChanged("IsDirty");
            }
        }

        public bool IsNew
        {
            get => _isNew;
            set
            {
                if (_isNew == value)
                    return;
                _isNew = value;
                OnPropertyChanged("IsNew");
            }
        }

        public bool IsDeleted { get; set; }
        public List<string> ChangedProperties { get; set; } = new List<string>();
        #endregion
    }
}

My ObservableObject class:

public abstract class ObservableObject : INotifyPropertyChanged
{
    #region INotifyPropertyChanged Members

    /// <summary>
    /// Raised when a property on this object has a new value.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises this object's PropertyChanged event.
    /// </summary>
    /// <param name="propertyName">The property that has a new value.</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.VerifyPropertyName(propertyName);

        if (this.PropertyChanged != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            this.PropertyChanged(this, e);
        }
    }

    #endregion // INotifyPropertyChanged Members

    #region Debugging Aides

    /// <summary>
    /// Warns the developer if this object does not have
    /// a public property with the specified name. This
    /// method does not exist in a Release build.
    /// </summary>
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public virtual void VerifyPropertyName(string propertyName)
    {
        // Verify that the property name matches a real,
        // public, instance property on this object.
        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            string msg = "Invalid property name: " + propertyName;

            if (this.ThrowOnInvalidPropertyName)
                throw new Exception(msg);
            else
                Debug.Fail(msg);
        }
    }

    /// <summary>
    /// Returns whether an exception is thrown, or if a Debug.Fail() is used
    /// when an invalid property name is passed to the VerifyPropertyName method.
    /// The default value is false, but subclasses used by unit tests might
    /// override this property's getter to return true.
    /// </summary>
    protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

    #endregion // Debugging Aides
}

My view model used in my user control:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Telerik.Windows.Controls.GridView;
using TMServerAPIContracts.Models.Enums;
using TMServerAPIContracts.Models.MatchTypes;
using TMWpfClient.Common;
using TMWpfClient.Extensions.DataConverters;
using TMWpfClient.Models.Application;
using TMWpfClient.Models.MatchType;
using TMWpfClient.Models.System.Base;
using TMWpfClient.Models.Tournament;
using TMWpfClient.Models.Tournament.Operation;
using TMWpfClient.Models.Tournament.Planning.Schedule;
using TMWpfClient.Properties;
using TMWpfClient.ViewModels.System;
using TMWpfClient.Views.Core;
using MatchTypePenaltySequence = TMWpfClient.Models.MatchType.MatchTypePenaltySequence;

namespace TMWpfClient.ViewModels.Tournament.Operations
{
    public class MatchExecutionBaseViewModel : ObservableObject, IDataErrorInfo
    {
        #region private variables
        private bool _addComplaint;
        private bool _activateComplaintSetting = true;
        private bool _noshowAvailable = true;
        private MatchComplaintInfo _complaint;
        #endregion

        #region public properties
        #region handling complaint

        /// <summary>
        /// Indicating whether a complaint is/should be added
        /// </summary>
        public bool AddComplaint
        {
            get => _addComplaint;
            set
            {
                if (_addComplaint == value)
                    return;
                _addComplaint = value;
                OnPropertyChanged("AddComplaint");
            }
        }

        /// <summary>
        /// Is the setting for complaint editable
        /// </summary>
        public bool ActivateComplaintSetting
        {
            get => _activateComplaintSetting;
            set
            {
                if (_activateComplaintSetting == value)
                    return;
                _activateComplaintSetting = value;
                OnPropertyChanged("ActivateComplaintSetting");
            }
        }

        public MatchComplaintInfo Complaint
        {
            get => _complaint;
            set
            {
                _complaint = value;
                OnPropertyChanged("Complaint");
            }
        }

        #endregion

        #endregion

        #region constructor
        public MatchExecutionBaseViewModel()
        {
            //creating an empty complaint ready to fill
            Complaint = new MatchComplaintInfo(match.Id);
        }
        #endregion
    }
}

XAML Grid for my user control:

                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"></ColumnDefinition>
                        <ColumnDefinition></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"></RowDefinition>
                        <RowDefinition></RowDefinition>
                        <RowDefinition Height="30"></RowDefinition>
                    </Grid.RowDefinitions>
                    <!-- labels -->
                    <TextBlock Grid.Column="0" Grid.Row="0" Margin="2">Create complaint</TextBlock>
                    <TextBlock Grid.Column="0" Grid.Row="1" Margin="2">Complaint</TextBlock>
                    <TextBlock Grid.Column="0" Grid.Row="2" Margin="2">Filed by</TextBlock>

                    <CheckBox Grid.Column="1" Grid.Row="0" Margin="2" ToolTip="Check this to create complaint" IsChecked="{Binding AddComplaint, Mode=TwoWay}" IsEnabled="{Binding ActivateComplaintSetting}"/>
                    <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Complaint.Complaint, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Margin="2" VerticalContentAlignment="Top" IsEnabled="{Binding AddComplaint}" Validation.ErrorTemplate="{StaticResource ValidationErrorTemplateLeft}"/>
                    <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Complaint.FiledBy, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Margin="2" Validation.ErrorTemplate="{StaticResource ValidationErrorTemplateLeft}" IsEnabled="{Binding AddComplaint}"/>
                </Grid>

and my control template for displaying the validation error:

            <ControlTemplate x:Key="ValidationErrorTemplateLeft">
                <Border BorderBrush="Red" BorderThickness="1">
                    <DockPanel>
                        <StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
                            <Grid Width="12" Height="12">
                                <Ellipse Width="12" Height="12" 
                                     Fill="Red" HorizontalAlignment="Center" 
                                     VerticalAlignment="Center"
                            ></Ellipse>
                                <TextBlock Foreground="White" FontWeight="Heavy" 
                                       FontSize="8" HorizontalAlignment="Center" 
                                       VerticalAlignment="Center" TextAlignment="Center"
                                       ToolTip="{Binding ElementName=ErrorAdornerLeft, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">X</TextBlock>
                            </Grid>
                        </StackPanel>
                        <AdornedElementPlaceholder x:Name="ErrorAdornerLeft"></AdornedElementPlaceholder>
                    </DockPanel>
                </Border>
            </ControlTemplate>

When debugging, I see that the validation code is triggered, and the error text is returned, but the validation template is not displayed.

But if I enter a value in the textbox, and then clears it again, then it displays the error template. Any ideas why I see this behaviour?

I would expect the two textfields to light up in red initially,and be gone when enetering data into the fields.

0

There are 0 answers