Click here to Skip to main content
15,885,366 members
Articles / Desktop Programming / WPF

C.B.R.

Rate me:
Please Sign up or sign in to vote.
4.96/5 (52 votes)
22 Oct 2012GPL329 min read 124.2K   1.8K   132  
Comic and electronic publication reader with library management, extended file conversion, and devices support.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;

namespace CBR.Core.Helpers
{
    /// <summary>
    /// Provides change notification, similar to ObservableCollection but without the need to depend on WPF.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Serializable]
    public class NotificationCollection<T> : Collection<T>, INotifyPropertyChanged
    {
        private SimpleMonitor monitor;

        public NotificationCollection()
        {
            monitor = new SimpleMonitor();
        }

        public NotificationCollection(IEnumerable<T> collection)
        {
            this.monitor = new SimpleMonitor();
            if (collection == null)
            {
                throw new ArgumentNullException("collection");
            }
            this.CopyFrom(collection);
        }

        public NotificationCollection(List<T> list)
            : base((list != null) ? new List<T>(list.Count) : list)
        {
            this.monitor = new SimpleMonitor();
            this.CopyFrom(list);
        }

        #region Public Members

        [field:NonSerialized]
        public event NotificationCollectionChangedEventHandler CollectionChanged;

        public void Move(int oldIndex, int newIndex)
        {
            this.MoveItem(oldIndex, newIndex);
        }

        #endregion

        #region Protected Members

        [NonSerialized]
        protected PropertyChangedEventHandler PropertyChanged;

        protected IDisposable BlockReentrancy()
        {
            this.monitor.Enter();
            return this.monitor;
        }

        protected void CheckReentrancy()
        {
            if ((this.monitor.Busy && (this.CollectionChanged != null)) && (this.CollectionChanged.GetInvocationList().Length > 1))
            {
                throw new InvalidOperationException("NotificationCollection Reentrancy Not Allowed");
            }
        }

        protected virtual void OnCollectionChanged(NotificationCollectionChangedEventArgs e)
        {
            if (this.CollectionChanged != null)
            {
                using (this.BlockReentrancy())
                {
                    this.CollectionChanged(this, e);
                }
            }
        }

        protected override void ClearItems()
        {
            this.CheckReentrancy();
            base.ClearItems();
            this.OnPropertyChanged("Count");
            this.OnPropertyChanged("Item[]");
            this.OnCollectionReset();
        }

        protected override void InsertItem(int index, T item)
        {
            this.CheckReentrancy();
            base.InsertItem(index, item);
            this.OnPropertyChanged("Count");
            this.OnPropertyChanged("Item[]");
            this.OnCollectionChanged(NotificationCollectionChangedAction.Add, item, index);
        }

        protected override void SetItem(int index, T item)
        {
            this.CheckReentrancy();
            T oldItem = base[index];
            base.SetItem(index, item);
            this.OnPropertyChanged("Item[]");
            this.OnCollectionChanged(NotificationCollectionChangedAction.Replace, oldItem, item, index);
        }

        protected override void RemoveItem(int index)
        {
            this.CheckReentrancy();
            T item = base[index];
            base.RemoveItem(index);
            this.OnPropertyChanged("Count");
            this.OnPropertyChanged("Item[]");
            this.OnCollectionChanged(NotificationCollectionChangedAction.Remove, item, index);
        }

        protected virtual void MoveItem(int oldIndex, int newIndex)
        {
            this.CheckReentrancy();
            T item = base[oldIndex];
            base.RemoveItem(oldIndex);
            base.InsertItem(newIndex, item);
            this.OnPropertyChanged("Item[]");
            this.OnCollectionChanged(NotificationCollectionChangedAction.Move, item, newIndex, oldIndex);
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, e);
            }
        }

        #endregion

        #region Private Members

        private void CopyFrom(IEnumerable<T> collection)
        {
            IList<T> items = base.Items;
            if ((collection != null) && (items != null))
            {
                using (IEnumerator<T> enumerator = collection.GetEnumerator())
                {
                    while (enumerator.MoveNext())
                    {
                        items.Add(enumerator.Current);
                    }
                }
            }
        }

        private void OnCollectionChanged(NotificationCollectionChangedAction action, object item, int index)
        {
            this.OnCollectionChanged(new NotificationCollectionChangedEventArgs(action, item, index));
        }

        private void OnCollectionChanged(NotificationCollectionChangedAction action, object item, int index, int oldIndex)
        {
            this.OnCollectionChanged(new NotificationCollectionChangedEventArgs(action, item, index, oldIndex));
        }

        private void OnCollectionChanged(NotificationCollectionChangedAction action, object oldItem, object newItem, int index)
        {
            this.OnCollectionChanged(new NotificationCollectionChangedEventArgs(action, newItem, oldItem, index));
        }

        private void OnCollectionReset()
        {
            this.OnCollectionChanged(new NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction.Reset));
        }

        private void OnPropertyChanged(string propertyName)
        {
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        #endregion


        #region Simple Monitor

        [Serializable]
        private class SimpleMonitor : IDisposable
        {
            // Fields
            private int busyCount;

            // ConstruConstructor
            public SimpleMonitor()
            {
            }

            public void Dispose()
            {
                busyCount--;
            }
            public void Enter()
            {
                busyCount++;
            }

            // Properties
            public bool Busy
            {
                get { return busyCount > 0; }
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            add
            {
                this.PropertyChanged = (PropertyChangedEventHandler)Delegate.Combine(this.PropertyChanged, value);
            }
            remove
            {
                this.PropertyChanged = (PropertyChangedEventHandler)Delegate.Remove(this.PropertyChanged, value);
            }
        }

        #endregion
    }

    public delegate void NotificationCollectionChangedEventHandler(object sender, NotificationCollectionChangedEventArgs e);

    [Serializable]
    public class NotificationCollectionChangedEventArgs : EventArgs
    {
        #region Private Data

        private NotificationCollectionChangedAction action;
        private IList newItems;
        private int newStartingIndex;
        private IList oldItems;
        private int oldStartingIndex;

        #endregion

        #region ConstruConstructors

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (action != NotificationCollectionChangedAction.Reset)
            {
                throw new ArgumentException("Wrong Action For ConstruConstructor: " + NotificationCollectionChangedAction.Reset, "action");
            }
            this.InitializeAdd(action, null, -1);
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, IList changedItems)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (((action != NotificationCollectionChangedAction.Add) && (action != NotificationCollectionChangedAction.Remove)) && (action != NotificationCollectionChangedAction.Reset))
            {
                throw new ArgumentException("Must Be Reset Add Or Remove Action For ConstruConstructor", "action");
            }
            if (action == NotificationCollectionChangedAction.Reset)
            {
                if (changedItems != null)
                {
                    throw new ArgumentException("ResetActionRequiresNullItem", "action");
                }
                this.InitializeAdd(action, null, -1);
            }
            else
            {
                if (changedItems == null)
                {
                    throw new ArgumentNullException("changedItems");
                }
                this.InitializeAddOrRemove(action, changedItems, -1);
            }
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, object changedItem)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (((action != NotificationCollectionChangedAction.Add) && (action != NotificationCollectionChangedAction.Remove)) && (action != NotificationCollectionChangedAction.Reset))
            {
                throw new ArgumentException("Must Be Reset Add Or Remove Action For ConstruConstructor", "action");
            }
            if (action == NotificationCollectionChangedAction.Reset)
            {
                if (changedItem != null)
                {
                    throw new ArgumentException("Reset Action Requires Null Item", "action");
                }
                this.InitializeAdd(action, null, -1);
            }
            else
            {
                this.InitializeAddOrRemove(action, new object[] { changedItem }, -1);
            }
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, IList newItems, IList oldItems)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (action != NotificationCollectionChangedAction.Replace)
            {
                throw new ArgumentException("Wrong Action For ConstruConstructor: " + NotificationCollectionChangedAction.Replace, "action");
            }
            if (newItems == null)
            {
                throw new ArgumentNullException("newItems");
            }
            if (oldItems == null)
            {
                throw new ArgumentNullException("oldItems");
            }
            this.InitializeMoveOrReplace(action, newItems, oldItems, -1, -1);
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, IList changedItems, int startingIndex)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (((action != NotificationCollectionChangedAction.Add) && (action != NotificationCollectionChangedAction.Remove)) && (action != NotificationCollectionChangedAction.Reset))
            {
                throw new ArgumentException("Must Be Reset Add Or Remove Action For ConstruConstructor", "action");
            }
            if (action == NotificationCollectionChangedAction.Reset)
            {
                if (changedItems != null)
                {
                    throw new ArgumentException("Reset Action Requires Null Item", "action");
                }
                if (startingIndex != -1)
                {
                    throw new ArgumentException("Reset Action Requires (Index - 1)", "action");
                }
                this.InitializeAdd(action, null, -1);
            }
            else
            {
                if (changedItems == null)
                {
                    throw new ArgumentNullException("changedItems");
                }
                if (startingIndex < -1)
                {
                    throw new ArgumentException("Index Cannot Be Negative", "startingIndex");
                }
                this.InitializeAddOrRemove(action, changedItems, startingIndex);
            }
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, object changedItem, int index)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (((action != NotificationCollectionChangedAction.Add) && (action != NotificationCollectionChangedAction.Remove)) && (action != NotificationCollectionChangedAction.Reset))
            {
                throw new ArgumentException("Must Be Reset Add Or Remove Action For ConstruConstructor", "action");
            }
            if (action == NotificationCollectionChangedAction.Reset)
            {
                if (changedItem != null)
                {
                    throw new ArgumentException("Reset Action Requires Null Item", "action");
                }
                if (index != -1)
                {
                    throw new ArgumentException("Reset Action Requires (Index - 1)", "action");
                }
                this.InitializeAdd(action, null, -1);
            }
            else
            {
                this.InitializeAddOrRemove(action, new object[] { changedItem }, index);
            }
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, object newItem, object oldItem)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (action != NotificationCollectionChangedAction.Replace)
            {
                throw new ArgumentException("Wrong Action For ConstruConstructor: " + NotificationCollectionChangedAction.Replace, "action");
            }
            this.InitializeMoveOrReplace(action, new object[] { newItem }, new object[] { oldItem }, -1, -1);
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (action != NotificationCollectionChangedAction.Replace)
            {
                throw new ArgumentException("Wrong Action For Constructor: " + NotificationCollectionChangedAction.Replace , "action");
            }
            if (newItems == null)
            {
                throw new ArgumentNullException("newItems");
            }
            if (oldItems == null)
            {
                throw new ArgumentNullException("oldItems");
            }
            this.InitializeMoveOrReplace(action, newItems, oldItems, startingIndex, startingIndex);
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, IList changedItems, int index, int oldIndex)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (action != NotificationCollectionChangedAction.Move)
            {
                throw new ArgumentException("Wrong Action For Constructor: " + NotificationCollectionChangedAction.Move , "action");
            }
            if (index < 0)
            {
                throw new ArgumentException("Index Cannot Be Negative", "index");
            }
            this.InitializeMoveOrReplace(action, changedItems, changedItems, index, oldIndex);
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, object changedItem, int index, int oldIndex)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (action != NotificationCollectionChangedAction.Move)
            {
                throw new ArgumentException("Wrong Action For Constructor: " + NotificationCollectionChangedAction.Move , "action");
            }
            if (index < 0)
            {
                throw new ArgumentException("Index Cannot Be Negative", "index");
            }
            object[] newItems = new object[] { changedItem };
            this.InitializeMoveOrReplace(action, newItems, newItems, index, oldIndex);
        }

        public NotificationCollectionChangedEventArgs(NotificationCollectionChangedAction action, object newItem, object oldItem, int index)
        {
            this.newStartingIndex = -1;
            this.oldStartingIndex = -1;
            if (action != NotificationCollectionChangedAction.Replace)
            {
                throw new ArgumentException("Wrong Action For Constructor: " + NotificationCollectionChangedAction.Replace , "action");
            }
            this.InitializeMoveOrReplace(action, new object[] { newItem }, new object[] { oldItem }, index, index);
        }

        #endregion

        #region Public Members

        public NotificationCollectionChangedAction Action
        {
            get
            {
                return this.action;
            }
        }

        public IList NewItems
        {
            get
            {
                return this.newItems;
            }
        }

        public int NewStartingIndex
        {
            get
            {
                return this.newStartingIndex;
            }
        }

        public IList OldItems
        {
            get
            {
                return this.oldItems;
            }
        }

        public int OldStartingIndex
        {
            get
            {
                return this.oldStartingIndex;
            }
        }

        #endregion

        #region Private Members

        private void InitializeAdd(NotificationCollectionChangedAction action, IList newItems, int newStartingIndex)
        {
            this.action = action;
            this.newItems = (newItems == null) ? null : ArrayList.ReadOnly(newItems);
            this.newStartingIndex = newStartingIndex;
        }

        private void InitializeAddOrRemove(NotificationCollectionChangedAction action, IList changedItems, int startingIndex)
        {
            if (action == NotificationCollectionChangedAction.Add)
            {
                this.InitializeAdd(action, changedItems, startingIndex);
            }
            else if (action == NotificationCollectionChangedAction.Remove)
            {
                this.InitializeRemove(action, changedItems, startingIndex);
            }
            else
            {
                Debug.Assert(false, "Unsupported action: {0}", action.ToString());
            }
        }

        private void InitializeMoveOrReplace(NotificationCollectionChangedAction action, IList newItems, IList oldItems, int startingIndex, int oldStartingIndex)
        {
            this.InitializeAdd(action, newItems, startingIndex);
            this.InitializeRemove(action, oldItems, oldStartingIndex);
        }

        private void InitializeRemove(NotificationCollectionChangedAction action, IList oldItems, int oldStartingIndex)
        {
            this.action = action;
            this.oldItems = (oldItems == null) ? null : ArrayList.ReadOnly(oldItems);
            this.oldStartingIndex = oldStartingIndex;
        }

        #endregion
    }

    public enum NotificationCollectionChangedAction
    {
        Add,
        Remove,
        Replace,
        Move,
        Reset
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Architect
France France
WPF and MVVM fan, I practice C # in all its forms from the beginning of the NET Framework without mentioning C ++ / MFC and other software packages such as databases, ASP, WCF, Web & Windows services, Application, and now Core and UWP.
In my wasted hours, I am guilty of having fathered C.B.R. and its cousins C.B.R. for WinRT and UWP on the Windows store.
But apart from that, I am a great handyman ... the house, a rocket stove to heat the jacuzzi and the last one: a wood oven for pizza, bread, and everything that goes inside

https://guillaumewaser.wordpress.com/
https://fouretcompagnie.wordpress.com/

Comments and Discussions