BaseCollections.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Collections.ObjectModel;
  7. using System.Text;
  8. using System.Globalization;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Collections;
  11. namespace FastReport.DataVisualization.Charting
  12. {
  13. /// <summary>
  14. /// Base class for all chart element collections
  15. /// </summary>
  16. public abstract class ChartElementCollection<T> : Collection<T>, IChartElement, IDisposable
  17. where T : ChartElement
  18. {
  19. #region Member variables
  20. private IChartElement _parent = null;
  21. private CommonElements _common = null;
  22. internal int _suspendUpdates = 0;
  23. #endregion
  24. #region Properties
  25. /// <summary>
  26. /// Gets or sets the parent.
  27. /// </summary>
  28. internal IChartElement Parent
  29. {
  30. get { return _parent; }
  31. set
  32. {
  33. _parent = value;
  34. Invalidate();
  35. }
  36. }
  37. /// <summary>
  38. /// Gets the CommonElements of the chart.
  39. /// </summary>
  40. internal CommonElements Common
  41. {
  42. get
  43. {
  44. if (_common == null && _parent != null)
  45. {
  46. _common = _parent.Common;
  47. }
  48. return _common;
  49. }
  50. }
  51. /// <summary>
  52. /// Gets the chart.
  53. /// </summary>
  54. internal Chart Chart
  55. {
  56. get
  57. {
  58. if (Common != null)
  59. return Common.Chart;
  60. else
  61. return null;
  62. }
  63. }
  64. /// <summary>
  65. /// Gets the items as List&lt;T&gt;. Use this property to perform advanced List specific operations (Sorting, etc)
  66. /// </summary>
  67. internal List<T> ItemList
  68. {
  69. get { return Items as List<T>; }
  70. }
  71. internal bool IsSuspended
  72. {
  73. get { return _suspendUpdates > 0; }
  74. }
  75. #endregion
  76. #region Constructors
  77. /// <summary>
  78. /// Initializes a new instance of the <see cref="ChartElementCollection&lt;T&gt;"/> class.
  79. /// </summary>
  80. /// <param name="parent">The parent chart element.</param>
  81. internal ChartElementCollection(IChartElement parent)
  82. {
  83. _parent = parent;
  84. }
  85. #endregion
  86. #region Methods
  87. /// <summary>
  88. /// Forces the invalidation of the parent chart element
  89. /// </summary>
  90. public virtual void Invalidate()
  91. {
  92. if (_parent != null && !IsSuspended)
  93. _parent.Invalidate();
  94. }
  95. /// <summary>
  96. /// Suspends invalidation
  97. /// </summary>
  98. public virtual void SuspendUpdates()
  99. {
  100. _suspendUpdates++;
  101. }
  102. /// <summary>
  103. /// Resumes invalidation.
  104. /// </summary>
  105. public virtual void ResumeUpdates()
  106. {
  107. if (_suspendUpdates>0)
  108. _suspendUpdates--;
  109. if (_suspendUpdates==0)
  110. this.Invalidate();
  111. }
  112. /// <summary>
  113. /// Removes all elements from the <see cref="T:System.Collections.ObjectModel.Collection`1"/>.
  114. /// </summary>
  115. [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
  116. protected override void ClearItems()
  117. {
  118. SuspendUpdates();
  119. while (this.Count > 0)
  120. {
  121. this.RemoveItem(0);
  122. }
  123. ResumeUpdates();
  124. }
  125. /// <summary>
  126. /// Deinitializes the specified item.
  127. /// </summary>
  128. /// <param name="item">The item.</param>
  129. internal virtual void Deinitialize( T item)
  130. {
  131. }
  132. /// <summary>
  133. /// Initializes the specified item.
  134. /// </summary>
  135. /// <param name="item">The item.</param>
  136. internal virtual void Initialize(T item)
  137. {
  138. }
  139. /// <summary>
  140. /// Removes the element at the specified index of the <see cref="T:System.Collections.ObjectModel.Collection`1"/>.
  141. /// </summary>
  142. /// <param name="index">The zero-based index of the element to remove.</param>
  143. [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
  144. protected override void RemoveItem(int index)
  145. {
  146. this.Deinitialize(this[index]);
  147. this[index].Parent = null;
  148. base.RemoveItem(index);
  149. Invalidate();
  150. }
  151. /// <summary>
  152. /// Inserts an element into the <see cref="T:System.Collections.ObjectModel.Collection`1"/> at the specified index.
  153. /// </summary>
  154. /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
  155. /// <param name="item">The object to insert. The value can be null for reference types.</param>
  156. [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
  157. protected override void InsertItem(int index, T item)
  158. {
  159. this.Initialize(item);
  160. item.Parent = this;
  161. base.InsertItem(index, item);
  162. Invalidate();
  163. if(Chart != null)
  164. Chart.CallOnModifing(this);
  165. }
  166. /// <summary>
  167. /// Replaces the element at the specified index.
  168. /// </summary>
  169. /// <param name="index">The zero-based index of the element to replace.</param>
  170. /// <param name="item">The new value for the element at the specified index. The value can be null for reference types.</param>
  171. [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
  172. protected override void SetItem(int index, T item)
  173. {
  174. this.Initialize(item);
  175. item.Parent = this;
  176. base.SetItem(index, item);
  177. Invalidate();
  178. }
  179. #endregion
  180. #region IChartElement Members
  181. IChartElement IChartElement.Parent
  182. {
  183. get { return this.Parent; }
  184. set { this.Parent = value; }
  185. }
  186. void IChartElement.Invalidate()
  187. {
  188. this.Invalidate();
  189. }
  190. CommonElements IChartElement.Common
  191. {
  192. get{ return this.Common; }
  193. }
  194. #endregion
  195. #region IDisposable Members
  196. /// <summary>
  197. /// Releases unmanaged and - optionally - managed resources
  198. /// </summary>
  199. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  200. protected virtual void Dispose(bool disposing)
  201. {
  202. if (disposing)
  203. {
  204. // Dispose managed resources
  205. foreach (T element in this)
  206. {
  207. element.Dispose();
  208. }
  209. }
  210. }
  211. /// <summary>
  212. /// Performs freeing, releasing, or resetting managed resources.
  213. /// </summary>
  214. [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
  215. public void Dispose()
  216. {
  217. this.Dispose(true);
  218. GC.SuppressFinalize(this);
  219. }
  220. #endregion
  221. }
  222. /// <summary>
  223. /// Base class for all collections of named chart elements. Performs the name management and enforces the uniquness of the names
  224. /// </summary>
  225. /// <typeparam name="T"></typeparam>
  226. public abstract class ChartNamedElementCollection<T> : ChartElementCollection<T>, INameController
  227. where T : ChartNamedElement
  228. {
  229. #region Fields
  230. private List<T> _cachedState = null;
  231. private int _disableDeleteCount = 0;
  232. #endregion
  233. #region Properties
  234. /// <summary>
  235. /// Gets the name prefix that is used to create unique chart element names.
  236. /// </summary>
  237. /// <value>The default name prefix of the chart elements stored in the collection.</value>
  238. protected virtual string NamePrefix
  239. {
  240. get { return typeof(T).Name; }
  241. }
  242. /// <summary>
  243. /// Gets or sets the chart element with the specified name.
  244. /// </summary>
  245. /// <value></value>
  246. public T this[string name]
  247. {
  248. get
  249. {
  250. int index = this.IndexOf(name);
  251. if (index != -1)
  252. {
  253. return this[index];
  254. }
  255. throw new ArgumentException(SR.ExceptionNameNotFound(name, this.GetType().Name));
  256. }
  257. set
  258. {
  259. int nameIndex = this.IndexOf(name);
  260. int itemIndex = this.IndexOf(value);
  261. bool nameFound = nameIndex > -1;
  262. bool itemFound = itemIndex > -1;
  263. if (!nameFound && !itemFound)
  264. this.Add(value);
  265. else if (nameFound && !itemFound)
  266. this[nameIndex] = value;
  267. else if (!nameFound && itemFound)
  268. throw new ArgumentException(SR.ExceptionNameAlreadyExistsInCollection(name, this.GetType().Name));
  269. else if (nameFound && itemFound && nameIndex != itemIndex)
  270. throw new ArgumentException(SR.ExceptionNameAlreadyExistsInCollection(name, this.GetType().Name));
  271. }
  272. }
  273. #endregion
  274. #region Constructors
  275. /// <summary>
  276. /// Initializes a new instance of the <see cref="ChartNamedElementCollection&lt;T&gt;"/> class.
  277. /// </summary>
  278. /// <param name="parent">The parent chart element.</param>
  279. internal ChartNamedElementCollection(IChartElement parent)
  280. : base(parent)
  281. {
  282. }
  283. #endregion
  284. #region Events
  285. internal event EventHandler<NameReferenceChangedEventArgs> NameReferenceChanged;
  286. internal event EventHandler<NameReferenceChangedEventArgs> NameReferenceChanging;
  287. #endregion
  288. #region Methods
  289. /// <summary>
  290. /// Determines whether the chart element with the specified name already exists in the collection.
  291. /// </summary>
  292. /// <param name="name">The new chart element name.</param>
  293. /// <returns>
  294. /// <c>true</c> if new chart element name is unique; otherwise, <c>false</c>.
  295. /// </returns>
  296. public virtual bool IsUniqueName(string name)
  297. {
  298. return FindByName(name)==null;
  299. }
  300. /// <summary>
  301. /// Finds the unique name for a new element being added to the collection
  302. /// </summary>
  303. /// <returns>Next unique chart element name</returns>
  304. public virtual string NextUniqueName()
  305. {
  306. // Find unique name
  307. string result = string.Empty;
  308. string prefix = this.NamePrefix;
  309. for (int i = 1; i < System.Int32.MaxValue; i++)
  310. {
  311. result = prefix + i.ToString(CultureInfo.InvariantCulture);
  312. // Check whether the name is unique
  313. if (IsUniqueName(result))
  314. {
  315. break;
  316. }
  317. }
  318. return result;
  319. }
  320. /// <summary>
  321. /// Indexes the of chart element with the specified name.
  322. /// </summary>
  323. /// <param name="name">The name.</param>
  324. /// <returns></returns>
  325. public int IndexOf(string name)
  326. {
  327. int i = 0;
  328. foreach (T namedObj in this)
  329. {
  330. if (namedObj.Name == name)
  331. return i;
  332. i++;
  333. }
  334. return -1;
  335. }
  336. /// <summary>
  337. /// Verifies the name reference to a chart named element stored in this collection and throws the argument exception if its not valid.
  338. /// </summary>
  339. /// <param name="name">Chart element name.</param>
  340. internal void VerifyNameReference(string name)
  341. {
  342. if (Chart!=null && !Chart.serializing && !IsNameReferenceValid(name))
  343. throw new ArgumentException(SR.ExceptionNameNotFound(name, this.GetType().Name));
  344. }
  345. /// <summary>
  346. /// Verifies the name reference to a chart named element stored in this collection.
  347. /// </summary>
  348. /// <param name="name">Chart element name.</param>
  349. internal bool IsNameReferenceValid(string name)
  350. {
  351. return String.IsNullOrEmpty(name) ||
  352. name == Constants.NotSetValue ||
  353. IndexOf(name) >= 0;
  354. }
  355. /// <summary>
  356. /// Finds the chart element by the name.
  357. /// </summary>
  358. /// <param name="name">The name.</param>
  359. /// <returns></returns>
  360. public virtual T FindByName(string name)
  361. {
  362. foreach (T namedObj in this)
  363. {
  364. if (namedObj.Name == name)
  365. return namedObj;
  366. }
  367. return null;
  368. }
  369. /// <summary>
  370. /// Inserts the specified item in the collection at the specified index.
  371. /// </summary>
  372. /// <param name="index">The zero-based index where the item is to be inserted.</param>
  373. /// <param name="item">The object to insert.</param>
  374. protected override void InsertItem(int index, T item)
  375. {
  376. if (String.IsNullOrEmpty(item.Name))
  377. item.Name = this.NextUniqueName();
  378. else if (!IsUniqueName(item.Name))
  379. throw new ArgumentException(SR.ExceptionNameAlreadyExistsInCollection(item.Name, this.GetType().Name));
  380. //If the item references other named references we might need to fix the references
  381. FixNameReferences(item);
  382. base.InsertItem(index, item);
  383. if (this.Count == 1 && item != null)
  384. {
  385. // First element is added to the list -> fire the NameReferenceChanged event to update all the dependent elements
  386. ((INameController)this).OnNameReferenceChanged(new NameReferenceChangedEventArgs(null, item));
  387. }
  388. }
  389. /// <summary>
  390. /// Replaces the element at the specified index.
  391. /// </summary>
  392. /// <param name="index">The zero-based index of the element to replace.</param>
  393. /// <param name="item">The new value for the element at the specified index.</param>
  394. protected override void SetItem(int index, T item)
  395. {
  396. if (String.IsNullOrEmpty(item.Name))
  397. item.Name = this.NextUniqueName();
  398. else if (!IsUniqueName(item.Name) && IndexOf(item.Name) != index)
  399. throw new ArgumentException(SR.ExceptionNameAlreadyExistsInCollection(item.Name, this.GetType().Name));
  400. //If the item references other named references we might need to fix the references
  401. FixNameReferences(item);
  402. // Remember the removedElement
  403. ChartNamedElement removedElement = index<Count ? this[index] : null;
  404. ((INameController)this).OnNameReferenceChanging(new NameReferenceChangedEventArgs(removedElement, item));
  405. base.SetItem(index, item);
  406. // Fire the NameReferenceChanged event to update all the dependent elements
  407. ((INameController)this).OnNameReferenceChanged(new NameReferenceChangedEventArgs(removedElement, item));
  408. }
  409. /// <summary>
  410. /// Removes the element at the specified index of the collection.
  411. /// </summary>
  412. /// <param name="index">The zero-based index of the element to remove.</param>
  413. protected override void RemoveItem(int index)
  414. {
  415. // Remember the removedElement
  416. ChartNamedElement removedElement = index < Count ? this[index] : null;
  417. if (_disableDeleteCount == 0)
  418. {
  419. ((INameController)this).OnNameReferenceChanged(new NameReferenceChangedEventArgs(removedElement, null));
  420. }
  421. base.RemoveItem(index);
  422. if (_disableDeleteCount == 0)
  423. {
  424. // All elements referencing the removed element will be redirected to the first element in collection
  425. // Fire the NameReferenceChanged event to update all the dependent elements
  426. ChartNamedElement defaultElement = this.Count > 0 ? this[0] : null;
  427. ((INameController)this).OnNameReferenceChanged(new NameReferenceChangedEventArgs(removedElement, defaultElement));
  428. }
  429. }
  430. /// <summary>
  431. /// Fixes the name references of the item.
  432. /// </summary>
  433. internal virtual void FixNameReferences(T item)
  434. {
  435. //Nothing to fix at the base class...
  436. }
  437. #endregion
  438. #region INameController Members
  439. /// <summary>
  440. /// Determines whether is the name us unique.
  441. /// </summary>
  442. /// <param name="name">The name.</param>
  443. /// <returns>
  444. /// <c>true</c> if is the name us unique; otherwise, <c>false</c>.
  445. /// </returns>
  446. bool INameController.IsUniqueName(string name)
  447. {
  448. return this.IsUniqueName(name);
  449. }
  450. /// <summary>
  451. /// Gets or sets a value indicating whether this instance is in edit mode by collecrtion editor.
  452. /// </summary>
  453. /// <value>
  454. /// <c>true</c> if this instance the colection is editing; otherwise, <c>false</c>.
  455. /// </value>
  456. bool INameController.IsColectionEditing
  457. {
  458. get
  459. {
  460. return _disableDeleteCount == 0;
  461. }
  462. set
  463. {
  464. _disableDeleteCount += value ? 1 : -1;
  465. }
  466. }
  467. /// <summary>
  468. /// Raises the <see cref="E:NameReferenceChanging"/> event.
  469. /// </summary>
  470. /// <param name="e">The <see cref="NameReferenceChangedEventArgs"/> instance containing the event data.</param>
  471. void INameController.OnNameReferenceChanging(NameReferenceChangedEventArgs e)
  472. {
  473. if (!IsSuspended)
  474. {
  475. if (this.NameReferenceChanging != null)
  476. this.NameReferenceChanging(this, e);
  477. }
  478. }
  479. /// <summary>
  480. /// Raises the <see cref="E:NameReferenceChanged"/> event.
  481. /// </summary>
  482. /// <param name="e">The <see cref="NameReferenceChangedEventArgs"/> instance containing the event data.</param>
  483. void INameController.OnNameReferenceChanged(NameReferenceChangedEventArgs e)
  484. {
  485. if (!IsSuspended)
  486. {
  487. if (this.NameReferenceChanged != null)
  488. this.NameReferenceChanged(this, e);
  489. }
  490. }
  491. /// <summary>
  492. /// Does the snapshot of collection items.
  493. /// </summary>
  494. /// <param name="save">if set to <c>true</c> collection items will be saved.</param>
  495. /// <param name="changingCallback">The changing callback.</param>
  496. /// <param name="changedCallback">The changed callback.</param>
  497. void INameController.DoSnapshot(bool save,
  498. EventHandler<NameReferenceChangedEventArgs> changingCallback,
  499. EventHandler<NameReferenceChangedEventArgs> changedCallback)
  500. {
  501. if (save)
  502. {
  503. _cachedState = new List<T>(this);
  504. if (changingCallback != null) this.NameReferenceChanging += changingCallback;
  505. if (changedCallback != null) this.NameReferenceChanged += changedCallback;
  506. }
  507. else
  508. {
  509. if (changingCallback != null) this.NameReferenceChanging -= changingCallback;
  510. if (changedCallback != null) this.NameReferenceChanged -= changedCallback;
  511. _cachedState.Clear();
  512. _cachedState = null;
  513. }
  514. }
  515. /// <summary>
  516. /// Gets the snapshot of saved collection items.
  517. /// </summary>
  518. /// <value>The snapshot.</value>
  519. IList INameController.Snapshot
  520. {
  521. get { return _cachedState; }
  522. }
  523. #endregion
  524. }
  525. }