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.