0

I have ObservableCollection and ItemsControl

public ObservableCollection<SomeData> Datas { get; }

I`am trying to validate a duplicate exists.

  <ItemsControl ItemsSource="{Binding Datas}">
   <!-- ... -->
  </ItemsControl"

enter image description here

I wrote I simple example (I'm not sure if that works but also it needs a Proxy):

    public class CollectionUniqueValueValidationRule : ValidationRule
    {
        public IList ExternalList { get; set; }

        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            var knownKeys = new HashSet<dynamic>();

            for (var i = 0; i < ExternalList.Count; ++i)
            {
                if (!knownKeys.Add(ExternalList[i]))
                {
                    return new ValidationResult(false, "Already exists");
                }
            }
            return ValidationResult.ValidResult;
        }
    }

But if it works, it just shows me one problematic item: enter image description here

But I need result as the first image.

limeniye
  • 31
  • 1
  • 6

1 Answers1

0

Ok, I wrote a simple example. NOTE: this is a bad realization, I do not advise to use it.enter image description here

The main idea was that we can check not only the objects, but also the individual property of them. For this I created a separate property ValidatingPropertyName.

Disadvantages of implementation

  • Currently, this Behavior only supports INotifyPropertyChanged properties or INotifyCollectionChanged. It does not support normal properties;
  • Currently, the Behavior only works if the item is inside an ItemsControl element;
  • Currently, Behavior only supports the TextBox element;
  • Currently, the Behavior not optimized.

Code demo

    public class IsCollectionItemDuplicatedRuleBehavior : Behavior<FrameworkElement>
    {
        public static readonly DependencyProperty ExtendedCollectionProperty = DependencyProperty.Register(nameof(ExtendedCollection),
           typeof(INotifyCollectionChanged), typeof(IsCollectionItemDuplicatedRuleBehavior),
           new FrameworkPropertyMetadata((d, e) => ((IsCollectionItemDuplicatedRuleBehavior)d).OnExtendedCollectionChanged(e)));

        public static readonly DependencyProperty ValidatingItemProperty = DependencyProperty.Register(nameof(ValidatingItem),
           typeof(object), typeof(IsCollectionItemDuplicatedRuleBehavior),
           new FrameworkPropertyMetadata(null, (d, e) => ((IsCollectionItemDuplicatedRuleBehavior)d).OnValidatingItemChanged(e)));

        public static readonly DependencyProperty ValidatingPropertyNameProperty = DependencyProperty.Register(nameof(ValidatingPropertyName),
           typeof(string), typeof(IsCollectionItemDuplicatedRuleBehavior),
           new FrameworkPropertyMetadata(null));

        private readonly SerialDisposable _eventSubscriptions = new SerialDisposable();
        private readonly CompositeDisposable _disposable = new CompositeDisposable();
        private IEnumerable<dynamic> _castedGenericCollection;
        private ItemsControl _itemsControl;
        internal Lazy<Type> FullAssociatedType { get; private set; }

        public INotifyCollectionChanged ExtendedCollection
        {
            get => (INotifyCollectionChanged)this.GetValue(ExtendedCollectionProperty);
            set => this.SetValue(ExtendedCollectionProperty, value);
        }

        public dynamic ValidatingItem
        {
            get => this.GetValue(ValidatingItemProperty);
            set => this.SetValue(ValidatingItemProperty, value);
        }

        public string ValidatingPropertyName
        {
            get => (string)this.GetValue(ValidatingPropertyNameProperty);
            set => this.SetValue(ValidatingPropertyNameProperty, value);
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            FullAssociatedType = new Lazy<Type>(AssociatedObject.GetType());
            _eventSubscriptions.Disposable = _disposable;

            if (AssociatedObject.IsLoaded)
            {
                Initialize();
            }
            else
            {
                AssociatedObject.Loaded += OnAssociatedObjectLoaded;
            }
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            _eventSubscriptions.Dispose();
            ExtendedCollection.CollectionChanged -= OnExtendedCollectionCollectionChanged;
            AssociatedObject.Loaded -= OnAssociatedObjectLoaded;
        }

        private void OnAssociatedObjectLoaded(object sender, RoutedEventArgs e)
        {
            Initialize();
        }

        private void Initialize()
        {
            SubscribeHandlersChanged();
            ValidateDuplication();
        }

        private void SubscribeHandlersChanged()
        {
            if (ExtendedCollection != null)
            {
                ExtendedCollection.CollectionChanged += OnExtendedCollectionCollectionChanged;
            }
        }

        private void OnValidatingItemChanged(DependencyPropertyChangedEventArgs e)
        {
            if (ValidatingPropertyName == null)
            {
                // TODO : Also its can be just a simple object without inheritance INotifyPropertyChanged interface
                var inpc = (INotifyPropertyChanged)e.NewValue;
                inpc.PropertyChanged -= OnPropertyChanged;
                inpc.PropertyChanged += OnPropertyChanged;
                _disposable.Add(Disposable.Create(delegate
                {
                    inpc.PropertyChanged -= OnPropertyChanged;
                }));
            }
            else
            {
                var propertyInfo = e.NewValue.GetType().GetProperty(ValidatingPropertyName);
                var inpcPropertyDynamic = propertyInfo.GetValue(e.NewValue);

                if (inpcPropertyDynamic is INotifyCollectionChanged collectionChanged)
                {
                    collectionChanged.CollectionChanged -= OnCollectionChanged;
                    collectionChanged.CollectionChanged += OnCollectionChanged;
                    _disposable.Add(Disposable.Create(delegate
                    {
                        collectionChanged.CollectionChanged -= OnCollectionChanged;
                    }));
                }
                else if (inpcPropertyDynamic is INotifyPropertyChanged propertyChanged)
                {
                    propertyChanged.PropertyChanged -= OnPropertyChanged;
                    propertyChanged.PropertyChanged += OnPropertyChanged;
                    _disposable.Add(Disposable.Create(delegate
                    {
                        propertyChanged.PropertyChanged -= OnPropertyChanged;
                    }));
                }
            }
            UpdateBehaviors();
        }

        private void OnExtendedCollectionCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            UpdateBehaviors();
        }

        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            UpdateBehaviors();
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            UpdateBehaviors();
        }

        private void OnExtendedCollectionChanged(DependencyPropertyChangedEventArgs e)
        {
            _castedGenericCollection = ExtendedCollection.CastTo<dynamic>();
            _itemsControl = AssociatedObject.FindParent<ItemsControl>();
            UpdateBehaviors();
        }

        private void UpdateBehaviors()
        {
            // ExtendedCollection still not initialized. 
            if (_castedGenericCollection == null)
            {
                return;
            }

            List<IsCollectionItemDuplicatedRuleBehavior> isCollectionItemDuplicatedRuleBehaviors = new List<IsCollectionItemDuplicatedRuleBehavior>();

            for (int i = 0; i < _itemsControl.Items.Count; i++)
            {
                ContentPresenter contentPresenter = (ContentPresenter)_itemsControl.ItemContainerGenerator.ContainerFromItem(_itemsControl.Items[i]);
                contentPresenter.FindChild(FullAssociatedType.Value, (dp) =>
                {
                    var behaviors = Interaction.GetBehaviors(dp);

                    if (behaviors.Count > 0)
                    {
                        foreach (var behavior in behaviors)
                        {
                            if (behavior is IsCollectionItemDuplicatedRuleBehavior isCollectionItemDuplicatedRuleBehavior)
                            {
                                isCollectionItemDuplicatedRuleBehaviors.Add(isCollectionItemDuplicatedRuleBehavior);
                                return true;
                            }
                        }
                    }
                    return false;
                });
            }

            foreach (var isCollectionItemDuplicatedRuleBehavior in isCollectionItemDuplicatedRuleBehaviors)
            {
                if (isCollectionItemDuplicatedRuleBehavior.AssociatedObject.IsLoaded)
                {
                    isCollectionItemDuplicatedRuleBehavior.ValidateDuplication();
                }
            }
        }

        internal void ValidateDuplication()
        {
            // TODO : TextBox.TextProperty its just for example
            BindingExpression bindingExpression = BindingOperations.GetBindingExpression(AssociatedObject, TextBox.TextProperty);
            // TODO : TextBox.TextProperty its just an example
            BindingExpressionBase bindingExpressionBase = BindingOperations.GetBindingExpressionBase(AssociatedObject, TextBox.TextProperty);

            var oldHasError = Validation.GetHasError(AssociatedObject);
            var currentIsValid = IsValid();
            System.Diagnostics.Debug.WriteLine(currentIsValid);
            if (!currentIsValid && !oldHasError)
            {
                ValidationError validationError = new ValidationError(new ExceptionValidationRule(), bindingExpression);
                Validation.MarkInvalid(bindingExpressionBase, validationError);
            }
            else if (currentIsValid && oldHasError)
            {
                Validation.ClearInvalid(bindingExpressionBase);
            }
            System.Diagnostics.Debug.WriteLine("ValidateDuplication finished");
        }

        private bool IsValid()
        {
            int existingCount = 0;
            string changedPropertyName = ValidatingPropertyName;
            dynamic validatingProperty = null;
            if (string.IsNullOrEmpty(changedPropertyName))
            {
                validatingProperty = ValidatingItem;
            }
            else
            {
                validatingProperty = ValidatingItem.GetType().GetProperty(changedPropertyName).GetValue(ValidatingItem);
            }

            foreach (var item in _castedGenericCollection)
            {
                var itemProperty = item.GetType().GetProperty(ValidatingPropertyName).GetValue(item);

                if (string.IsNullOrEmpty(changedPropertyName))
                {
                    itemProperty = item;
                }
                else
                {
                    itemProperty = item.GetType().GetProperty(ValidatingPropertyName).GetValue(item);
                }

                if (itemProperty is ICollection)
                {
                    if (Enumerable.SequenceEqual(itemProperty, validatingProperty))
                    {
                        ++existingCount;
                    }
                }
                else if (itemProperty.Equals(validatingProperty))
                {
                    ++existingCount;
                }

                if (existingCount > 1)
                {
                    return false;
                }
            }
            return true;
        }
    }

How to use

I wrote property name which I will validated at the future. If you do not specify this property, the program will not search for any properties inside the object, but will compare the objects themselves with each other. There was also an idea to add the ability to search properties more deeply like that: SomeProperty.SomeAnotherPeoprty.Surname. But I didn't.

<ItemsControl ItemsSource="{Binding Shortcuts}"
                VerticalAlignment="Top">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBox>
                <i:Interaction.Behaviors>
                    <behaviors:IsCollectionItemDuplicatedRuleBehavior
                        ExtendedCollection="{Binding DataContext.Shortcuts, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}"
                        ValidatingItem="{Binding}"
                        ValidatingPropertyName="Keys"/>
                </i:Interaction.Behaviors>
            </TextBox>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
limeniye
  • 31
  • 1
  • 6