/Nova.AppFramework/ObservableCollectionExt.cs

http://coevery.codeplex.com · C# · 390 lines · 200 code · 40 blank · 150 comment · 7 complexity · b2984949f0292407b355d721b54b6534 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Collections.Specialized;
  5. using System.ComponentModel;
  6. using System.Linq;
  7. using System.Linq.Expressions;
  8. using System.Runtime.Serialization;
  9. using System.Text;
  10. namespace Nova.AppFramework
  11. {
  12. /// <summary>
  13. /// Extends <see cref = "INotifyPropertyChanged" /> such that the change event can be raised by external parties.
  14. /// </summary>
  15. public interface INotifyPropertyChangedEx : INotifyPropertyChanged
  16. {
  17. /// <summary>
  18. /// Enables/Disables property change notification.
  19. /// </summary>
  20. bool IsNotifying { get; set; }
  21. /// <summary>
  22. /// Notifies subscribers of the property change.
  23. /// </summary>
  24. /// <param name = "propertyName">Name of the property.</param>
  25. void NotifyOfPropertyChange(string propertyName);
  26. /// <summary>
  27. /// Raises a change notification indicating that all bindings should be refreshed.
  28. /// </summary>
  29. void Refresh();
  30. }
  31. [Serializable]
  32. /// <summary>
  33. /// A base class that implements the infrastructure for property change notification and automatically performs UI thread marshalling.
  34. /// </summary>
  35. public class PropertyChangedBase : INotifyPropertyChangedEx
  36. {
  37. /// <summary>
  38. /// Creates an instance of <see cref = "PropertyChangedBase" />.
  39. /// </summary>
  40. public PropertyChangedBase()
  41. {
  42. executor = new SafeExecutor(this);
  43. IsNotifying = true;
  44. }
  45. [field: NonSerialized]
  46. /// <summary>
  47. /// Occurs when a property value changes.
  48. /// </summary>
  49. public event PropertyChangedEventHandler PropertyChanged = delegate { };
  50. [field: NonSerialized]
  51. SafeExecutor executor;
  52. [field: NonSerialized]
  53. bool isNotifying; //serializator try to serialize even autogenerated fields
  54. /// <summary>
  55. /// Enables/Disables property change notification.
  56. /// </summary>
  57. public bool IsNotifying
  58. {
  59. get { return isNotifying; }
  60. set { isNotifying = value; }
  61. }
  62. /// <summary>
  63. /// Raises a change notification indicating that all bindings should be refreshed.
  64. /// </summary>
  65. public void Refresh()
  66. {
  67. NotifyOfPropertyChange(string.Empty);
  68. }
  69. /// <summary>
  70. /// Notifies subscribers of the property change.
  71. /// </summary>
  72. /// <param name = "propertyName">Name of the property.</param>
  73. public virtual void NotifyOfPropertyChange(string propertyName)
  74. {
  75. if (IsNotifying)
  76. executor.ExecuteOnUIThread(() => RaisePropertyChangedEventCore(propertyName));
  77. }
  78. /// <summary>
  79. /// Notifies subscribers of the property change.
  80. /// </summary>
  81. /// <typeparam name = "TProperty">The type of the property.</typeparam>
  82. /// <param name = "property">The property expression.</param>
  83. public virtual void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
  84. {
  85. NotifyOfPropertyChange(property.GetMemberInfo().Name);
  86. }
  87. /// <summary>
  88. /// Raises the property changed event immediately.
  89. /// </summary>
  90. /// <param name = "propertyName">Name of the property.</param>
  91. public virtual void RaisePropertyChangedEventImmediately(string propertyName)
  92. {
  93. if (IsNotifying)
  94. RaisePropertyChangedEventCore(propertyName);
  95. }
  96. void RaisePropertyChangedEventCore(string propertyName)
  97. {
  98. var handler = PropertyChanged;
  99. if (handler != null)
  100. handler(this, new PropertyChangedEventArgs(propertyName));
  101. }
  102. [OnDeserialized]
  103. void OnDeserialized(StreamingContext c)
  104. {
  105. IsNotifying = true;
  106. }
  107. }
  108. /// <summary>
  109. /// Represents a collection that is observable.
  110. /// </summary>
  111. /// <typeparam name = "T">The type of elements contained in the collection.</typeparam>
  112. public interface IObservableCollection<T> : IList<T>, INotifyPropertyChangedEx, INotifyCollectionChanged
  113. {
  114. /// <summary>
  115. /// Adds the range.
  116. /// </summary>
  117. /// <param name = "items">The items.</param>
  118. void AddRange(IEnumerable<T> items);
  119. /// <summary>
  120. /// Removes the range.
  121. /// </summary>
  122. /// <param name = "items">The items.</param>
  123. void RemoveRange(IEnumerable<T> items);
  124. }
  125. /// <summary>
  126. /// A base collection class that supports automatic UI thread marshalling.
  127. /// </summary>
  128. /// <typeparam name="T">The type of elements contained in the collection.</typeparam>
  129. [Serializable]
  130. public class BindableCollection<T> : ObservableCollection<T>, IObservableCollection<T>
  131. {
  132. /// <summary>
  133. /// Initializes a new instance of the <see cref = "Illusion.BindableCollection{T}" /> class.
  134. /// </summary>
  135. public BindableCollection()
  136. {
  137. IsNotifying = true;
  138. executor = new SafeExecutor(this);
  139. }
  140. [field:NonSerialized]
  141. SafeExecutor executor;
  142. /// <summary>
  143. /// Initializes a new instance of the <see cref = "Illusion.BindableCollection{T}" /> class.
  144. /// </summary>
  145. /// <param name = "collection">The collection from which the elements are copied.</param>
  146. /// <exception cref = "T:System.ArgumentNullException">
  147. /// The <paramref name = "collection" /> parameter cannot be null.
  148. /// </exception>
  149. public BindableCollection(IEnumerable<T> collection)
  150. {
  151. IsNotifying = true;
  152. AddRange(collection);
  153. }
  154. [field: NonSerialized]
  155. bool isNotifying; //serializator try to serialize even autogenerated fields
  156. /// <summary>
  157. /// Enables/Disables property change notification.
  158. /// </summary>
  159. public bool IsNotifying
  160. {
  161. get { return isNotifying; }
  162. set { isNotifying = value; }
  163. }
  164. /// <summary>
  165. /// Notifies subscribers of the property change.
  166. /// </summary>
  167. /// <param name = "propertyName">Name of the property.</param>
  168. public void NotifyOfPropertyChange(string propertyName)
  169. {
  170. if (IsNotifying)
  171. executor.ExecuteOnUIThread(() => RaisePropertyChangedEventImmediately(new PropertyChangedEventArgs(propertyName)));
  172. }
  173. /// <summary>
  174. /// Raises a change notification indicating that all bindings should be refreshed.
  175. /// </summary>
  176. public void Refresh()
  177. {
  178. executor.ExecuteOnUIThread(() =>
  179. {
  180. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  181. OnPropertyChanged(new PropertyChangedEventArgs(string.Empty));
  182. });
  183. }
  184. /// <summary>
  185. /// Inserts the item to the specified position.
  186. /// </summary>
  187. /// <param name = "index">The index to insert at.</param>
  188. /// <param name = "item">The item to be inserted.</param>
  189. protected override sealed void InsertItem(int index, T item)
  190. {
  191. executor.ExecuteOnUIThread(() => InsertItemBase(index, item));
  192. }
  193. /// <summary>
  194. /// Exposes the base implementation of the <see cref = "InsertItem" /> function.
  195. /// </summary>
  196. /// <param name = "index">The index.</param>
  197. /// <param name = "item">The item.</param>
  198. /// <remarks>
  199. /// Used to avoid compiler warning regarding unverifiable code.
  200. /// </remarks>
  201. protected virtual void InsertItemBase(int index, T item)
  202. {
  203. base.InsertItem(index, item);
  204. }
  205. #if NET
  206. /// <summary>
  207. /// Moves the item within the collection.
  208. /// </summary>
  209. /// <param name="oldIndex">The old position of the item.</param>
  210. /// <param name="newIndex">The new position of the item.</param>
  211. protected sealed override void MoveItem(int oldIndex, int newIndex) {
  212. Execute.OnUIThread(() => MoveItemBase(oldIndex, newIndex));
  213. }
  214. /// <summary>
  215. /// Exposes the base implementation fo the <see cref="MoveItem"/> function.
  216. /// </summary>
  217. /// <param name="oldIndex">The old index.</param>
  218. /// <param name="newIndex">The new index.</param>
  219. /// <remarks>Used to avoid compiler warning regarding unverificable code.</remarks>
  220. protected virtual void MoveItemBase(int oldIndex, int newIndex) {
  221. base.MoveItem(oldIndex, newIndex);
  222. }
  223. #endif
  224. /// <summary>
  225. /// Sets the item at the specified position.
  226. /// </summary>
  227. /// <param name = "index">The index to set the item at.</param>
  228. /// <param name = "item">The item to set.</param>
  229. protected override sealed void SetItem(int index, T item)
  230. {
  231. executor.ExecuteOnUIThread(() => SetItemBase(index, item));
  232. }
  233. /// <summary>
  234. /// Exposes the base implementation of the <see cref = "SetItem" /> function.
  235. /// </summary>
  236. /// <param name = "index">The index.</param>
  237. /// <param name = "item">The item.</param>
  238. /// <remarks>
  239. /// Used to avoid compiler warning regarding unverifiable code.
  240. /// </remarks>
  241. protected virtual void SetItemBase(int index, T item)
  242. {
  243. base.SetItem(index, item);
  244. }
  245. /// <summary>
  246. /// Removes the item at the specified position.
  247. /// </summary>
  248. /// <param name = "index">The position used to identify the item to remove.</param>
  249. protected override sealed void RemoveItem(int index)
  250. {
  251. executor.ExecuteOnUIThread(() => RemoveItemBase(index));
  252. }
  253. /// <summary>
  254. /// Exposes the base implementation of the <see cref = "RemoveItem" /> function.
  255. /// </summary>
  256. /// <param name = "index">The index.</param>
  257. /// <remarks>
  258. /// Used to avoid compiler warning regarding unverifiable code.
  259. /// </remarks>
  260. protected virtual void RemoveItemBase(int index)
  261. {
  262. base.RemoveItem(index);
  263. }
  264. /// <summary>
  265. /// Clears the items contained by the collection.
  266. /// </summary>
  267. protected override sealed void ClearItems()
  268. {
  269. executor.ExecuteOnUIThread(ClearItemsBase);
  270. }
  271. /// <summary>
  272. /// Exposes the base implementation of the <see cref = "ClearItems" /> function.
  273. /// </summary>
  274. /// <remarks>
  275. /// Used to avoid compiler warning regarding unverifiable code.
  276. /// </remarks>
  277. protected virtual void ClearItemsBase()
  278. {
  279. base.ClearItems();
  280. }
  281. /// <summary>
  282. /// Raises the <see cref = "E:System.Collections.ObjectModel.ObservableCollection`1.CollectionChanged" /> event with the provided arguments.
  283. /// </summary>
  284. /// <param name = "e">Arguments of the event being raised.</param>
  285. protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
  286. {
  287. if (IsNotifying)
  288. base.OnCollectionChanged(e);
  289. }
  290. /// <summary>
  291. /// Raises the PropertyChanged event with the provided arguments.
  292. /// </summary>
  293. /// <param name = "e">The event data to report in the event.</param>
  294. protected override void OnPropertyChanged(PropertyChangedEventArgs e)
  295. {
  296. if (IsNotifying)
  297. base.OnPropertyChanged(e);
  298. }
  299. void RaisePropertyChangedEventImmediately(PropertyChangedEventArgs e)
  300. {
  301. OnPropertyChanged(e);
  302. }
  303. /// <summary>
  304. /// Adds the range.
  305. /// </summary>
  306. /// <param name = "items">The items.</param>
  307. public virtual void AddRange(IEnumerable<T> items)
  308. {
  309. executor.ExecuteOnUIThread(() =>
  310. {
  311. var previousNotificationSetting = IsNotifying;
  312. IsNotifying = false;
  313. var index = Count;
  314. foreach (var item in items)
  315. {
  316. InsertItemBase(index, item);
  317. index++;
  318. }
  319. IsNotifying = previousNotificationSetting;
  320. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  321. OnPropertyChanged(new PropertyChangedEventArgs(string.Empty));
  322. });
  323. }
  324. /// <summary>
  325. /// Removes the range.
  326. /// </summary>
  327. /// <param name = "items">The items.</param>
  328. public virtual void RemoveRange(IEnumerable<T> items)
  329. {
  330. executor.ExecuteOnUIThread(() =>
  331. {
  332. var previousNotificationSetting = IsNotifying;
  333. IsNotifying = false;
  334. foreach (var item in items)
  335. {
  336. var index = IndexOf(item);
  337. RemoveItemBase(index);
  338. }
  339. IsNotifying = previousNotificationSetting;
  340. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  341. OnPropertyChanged(new PropertyChangedEventArgs(string.Empty));
  342. });
  343. }
  344. [OnDeserialized]
  345. void OnDeserialized(StreamingContext c)
  346. {
  347. IsNotifying = true;
  348. }
  349. }
  350. }