/Branches/0.9.5-DeltaShell/src/Common/DelftTools.Utils/PropertyBag/PropertyBag.cs

# · C# · 600 lines · 325 code · 81 blank · 194 comment · 33 complexity · eed5c5864f508f93ef028f8bc8f061fb MD5 · raw file

  1. using System;
  2. using System.Collections;
  3. using System.ComponentModel;
  4. using System.Drawing.Design;
  5. namespace DelftTools.Utils.PropertyBag
  6. {
  7. /// <summary>
  8. /// Represents the method that will handle the GetValue and SetValue events of the
  9. /// PropertyBag class.
  10. /// </summary>
  11. public delegate void PropertySpecEventHandler(object sender, PropertySpecEventArgs e);
  12. /// <summary>
  13. /// Represents a collection of custom properties that can be selected into a
  14. /// PropertyGrid to provide functionality beyond that of the simple reflection
  15. /// normally used to query an object's properties.
  16. /// </summary>
  17. public class PropertyBag : ICustomTypeDescriptor
  18. {
  19. #region PropertySpecCollection class definition
  20. /// <summary>
  21. /// Encapsulates a collection of PropertySpec objects.
  22. /// </summary>
  23. [Serializable]
  24. public class PropertySpecCollection : IList
  25. {
  26. private ArrayList innerArray;
  27. /// <summary>
  28. /// Initializes a new instance of the PropertySpecCollection class.
  29. /// </summary>
  30. public PropertySpecCollection()
  31. {
  32. innerArray = new ArrayList();
  33. }
  34. /// <summary>
  35. /// Gets the number of elements in the PropertySpecCollection.
  36. /// </summary>
  37. /// <value>
  38. /// The number of elements contained in the PropertySpecCollection.
  39. /// </value>
  40. public int Count
  41. {
  42. get { return innerArray.Count; }
  43. }
  44. /// <summary>
  45. /// Gets a value indicating whether the PropertySpecCollection has a fixed size.
  46. /// </summary>
  47. /// <value>
  48. /// true if the PropertySpecCollection has a fixed size; otherwise, false.
  49. /// </value>
  50. public bool IsFixedSize
  51. {
  52. get { return false; }
  53. }
  54. /// <summary>
  55. /// Gets a value indicating whether the PropertySpecCollection is read-only.
  56. /// </summary>
  57. public bool IsReadOnly
  58. {
  59. get { return false; }
  60. }
  61. /// <summary>
  62. /// Gets a value indicating whether access to the collection is synchronized (thread-safe).
  63. /// </summary>
  64. /// <value>
  65. /// true if access to the PropertySpecCollection is synchronized (thread-safe); otherwise, false.
  66. /// </value>
  67. public bool IsSynchronized
  68. {
  69. get { return false; }
  70. }
  71. /// <summary>
  72. /// Gets an object that can be used to synchronize access to the collection.
  73. /// </summary>
  74. /// <value>
  75. /// An object that can be used to synchronize access to the collection.
  76. /// </value>
  77. object ICollection.SyncRoot
  78. {
  79. get { return null; }
  80. }
  81. /// <summary>
  82. /// Gets or sets the element at the specified index.
  83. /// In C#, this property is the indexer for the PropertySpecCollection class.
  84. /// </summary>
  85. /// <param name="index">The zero-based index of the element to get or set.</param>
  86. /// <value>
  87. /// The element at the specified index.
  88. /// </value>
  89. public PropertySpec this[int index]
  90. {
  91. get { return (PropertySpec)innerArray[index]; }
  92. set { innerArray[index] = value; }
  93. }
  94. /// <summary>
  95. /// Adds a PropertySpec to the end of the PropertySpecCollection.
  96. /// </summary>
  97. /// <param name="value">The PropertySpec to be added to the end of the PropertySpecCollection.</param>
  98. /// <returns>The PropertySpecCollection index at which the value has been added.</returns>
  99. public int Add(PropertySpec value)
  100. {
  101. int index = innerArray.Add(value);
  102. return index;
  103. }
  104. /// <summary>
  105. /// Adds the elements of an array of PropertySpec objects to the end of the PropertySpecCollection.
  106. /// </summary>
  107. /// <param name="array">The PropertySpec array whose elements should be added to the end of the
  108. /// PropertySpecCollection.</param>
  109. public void AddRange(PropertySpec[] array)
  110. {
  111. innerArray.AddRange(array);
  112. }
  113. /// <summary>
  114. /// Removes all elements from the PropertySpecCollection.
  115. /// </summary>
  116. public void Clear()
  117. {
  118. innerArray.Clear();
  119. }
  120. /// <summary>
  121. /// Determines whether a PropertySpec is in the PropertySpecCollection.
  122. /// </summary>
  123. /// <param name="item">The PropertySpec to locate in the PropertySpecCollection. The element to locate
  124. /// can be a null reference (Nothing in Visual Basic).</param>
  125. /// <returns>true if item is found in the PropertySpecCollection; otherwise, false.</returns>
  126. public bool Contains(PropertySpec item)
  127. {
  128. return innerArray.Contains(item);
  129. }
  130. /// <summary>
  131. /// Determines whether a PropertySpec with the specified name is in the PropertySpecCollection.
  132. /// </summary>
  133. /// <param name="name">The name of the PropertySpec to locate in the PropertySpecCollection.</param>
  134. /// <returns>true if item is found in the PropertySpecCollection; otherwise, false.</returns>
  135. public bool Contains(string name)
  136. {
  137. foreach (PropertySpec spec in innerArray)
  138. if (spec.Name == name)
  139. return true;
  140. return false;
  141. }
  142. /// <summary>
  143. /// Copies the entire PropertySpecCollection to a compatible one-dimensional Array, starting at the
  144. /// beginning of the target array.
  145. /// </summary>
  146. /// <param name="array">The one-dimensional Array that is the destination of the elements copied
  147. /// fromPropertySpecCollection. The Array must have zero-based indexing.</param>
  148. public void CopyTo(PropertySpec[] array)
  149. {
  150. innerArray.CopyTo(array);
  151. }
  152. /// <summary>
  153. /// Copies the PropertySpecCollection or a portion of it to a one-dimensional array.
  154. /// </summary>
  155. /// <param name="array">The one-dimensional Array that is the destination of the elements copied
  156. /// from the collection.</param>
  157. /// <param name="index">The zero-based index in array at which copying begins.</param>
  158. public void CopyTo(PropertySpec[] array, int index)
  159. {
  160. innerArray.CopyTo(array, index);
  161. }
  162. /// <summary>
  163. /// Returns an enumerator that can iterate through the PropertySpecCollection.
  164. /// </summary>
  165. /// <returns>An IEnumerator for the entire PropertySpecCollection.</returns>
  166. public IEnumerator GetEnumerator()
  167. {
  168. return innerArray.GetEnumerator();
  169. }
  170. /// <summary>
  171. /// Searches for the specified PropertySpec and returns the zero-based index of the first
  172. /// occurrence within the entire PropertySpecCollection.
  173. /// </summary>
  174. /// <param name="value">The PropertySpec to locate in the PropertySpecCollection.</param>
  175. /// <returns>The zero-based index of the first occurrence of value within the entire PropertySpecCollection,
  176. /// if found; otherwise, -1.</returns>
  177. public int IndexOf(PropertySpec value)
  178. {
  179. return innerArray.IndexOf(value);
  180. }
  181. /// <summary>
  182. /// Searches for the PropertySpec with the specified name and returns the zero-based index of
  183. /// the first occurrence within the entire PropertySpecCollection.
  184. /// </summary>
  185. /// <param name="name">The name of the PropertySpec to locate in the PropertySpecCollection.</param>
  186. /// <returns>The zero-based index of the first occurrence of value within the entire PropertySpecCollection,
  187. /// if found; otherwise, -1.</returns>
  188. public int IndexOf(string name)
  189. {
  190. int i = 0;
  191. foreach (PropertySpec spec in innerArray)
  192. {
  193. if (spec.Name == name)
  194. return i;
  195. i++;
  196. }
  197. return -1;
  198. }
  199. /// <summary>
  200. /// Inserts a PropertySpec object into the PropertySpecCollection at the specified index.
  201. /// </summary>
  202. /// <param name="index">The zero-based index at which value should be inserted.</param>
  203. /// <param name="value">The PropertySpec to insert.</param>
  204. public void Insert(int index, PropertySpec value)
  205. {
  206. innerArray.Insert(index, value);
  207. }
  208. /// <summary>
  209. /// Removes the first occurrence of a specific object from the PropertySpecCollection.
  210. /// </summary>
  211. /// <param name="obj">The PropertySpec to remove from the PropertySpecCollection.</param>
  212. public void Remove(PropertySpec obj)
  213. {
  214. innerArray.Remove(obj);
  215. }
  216. /// <summary>
  217. /// Removes the property with the specified name from the PropertySpecCollection.
  218. /// </summary>
  219. /// <param name="name">The name of the PropertySpec to remove from the PropertySpecCollection.</param>
  220. public void Remove(string name)
  221. {
  222. int index = IndexOf(name);
  223. RemoveAt(index);
  224. }
  225. /// <summary>
  226. /// Removes the object at the specified index of the PropertySpecCollection.
  227. /// </summary>
  228. /// <param name="index">The zero-based index of the element to remove.</param>
  229. public void RemoveAt(int index)
  230. {
  231. innerArray.RemoveAt(index);
  232. }
  233. /// <summary>
  234. /// Copies the elements of the PropertySpecCollection to a new PropertySpec array.
  235. /// </summary>
  236. /// <returns>A PropertySpec array containing copies of the elements of the PropertySpecCollection.</returns>
  237. public PropertySpec[] ToArray()
  238. {
  239. return (PropertySpec[])innerArray.ToArray(typeof(PropertySpec));
  240. }
  241. #region Explicit interface implementations for ICollection and IList
  242. /// <summary>
  243. /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
  244. /// </summary>
  245. void ICollection.CopyTo(Array array, int index)
  246. {
  247. CopyTo((PropertySpec[])array, index);
  248. }
  249. /// <summary>
  250. /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
  251. /// </summary>
  252. int IList.Add(object value)
  253. {
  254. return Add((PropertySpec)value);
  255. }
  256. /// <summary>
  257. /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
  258. /// </summary>
  259. bool IList.Contains(object obj)
  260. {
  261. return Contains((PropertySpec)obj);
  262. }
  263. /// <summary>
  264. /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
  265. /// </summary>
  266. object IList.this[int index]
  267. {
  268. get
  269. {
  270. return ((PropertySpecCollection)this)[index];
  271. }
  272. set
  273. {
  274. ((PropertySpecCollection)this)[index] = (PropertySpec)value;
  275. }
  276. }
  277. /// <summary>
  278. /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
  279. /// </summary>
  280. int IList.IndexOf(object obj)
  281. {
  282. return IndexOf((PropertySpec)obj);
  283. }
  284. /// <summary>
  285. /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
  286. /// </summary>
  287. void IList.Insert(int index, object value)
  288. {
  289. Insert(index, (PropertySpec)value);
  290. }
  291. /// <summary>
  292. /// This member supports the .NET Framework infrastructure and is not intended to be used directly from your code.
  293. /// </summary>
  294. void IList.Remove(object value)
  295. {
  296. Remove((PropertySpec)value);
  297. }
  298. #endregion
  299. }
  300. #endregion
  301. #region PropertySpecDescriptor class definition
  302. private class PropertySpecDescriptor : PropertyDescriptor
  303. {
  304. private PropertyBag bag;
  305. private PropertySpec item;
  306. public PropertySpecDescriptor(PropertySpec item, PropertyBag bag, string name, Attribute[] attrs)
  307. :
  308. base(name, attrs)
  309. {
  310. this.bag = bag;
  311. this.item = item;
  312. }
  313. public override Type ComponentType
  314. {
  315. get { return item.GetType(); }
  316. }
  317. public override bool IsReadOnly
  318. {
  319. get { return (Attributes.Matches(ReadOnlyAttribute.Yes)); }
  320. }
  321. public override Type PropertyType
  322. {
  323. get { return Type.GetType(item.TypeName); }
  324. }
  325. public override bool CanResetValue(object component)
  326. {
  327. if (item.DefaultValue == null)
  328. return false;
  329. else
  330. return !this.GetValue(component).Equals(item.DefaultValue);
  331. }
  332. public override object GetValue(object component)
  333. {
  334. // Have the property bag raise an event to get the current value
  335. // of the property.
  336. PropertySpecEventArgs e = new PropertySpecEventArgs(item, null);
  337. bag.OnGetValue(e);
  338. base.AttributeArray = e.Property.Attributes;
  339. return e.Value;
  340. }
  341. public override void ResetValue(object component)
  342. {
  343. SetValue(component, item.DefaultValue);
  344. }
  345. public override void SetValue(object component, object value)
  346. {
  347. // Have the property bag raise an event to set the current value
  348. // of the property.
  349. PropertySpecEventArgs e = new PropertySpecEventArgs(item, value);
  350. bag.OnSetValue(e);
  351. }
  352. public override bool ShouldSerializeValue(object component)
  353. {
  354. object val = this.GetValue(component);
  355. if (item.DefaultValue == null && val == null)
  356. return false;
  357. else
  358. return !val.Equals(item.DefaultValue);
  359. }
  360. }
  361. #endregion
  362. private string defaultProperty;
  363. private PropertySpecCollection properties;
  364. /// <summary>
  365. /// Initializes a new instance of the PropertyBag class.
  366. /// </summary>
  367. public PropertyBag()
  368. {
  369. defaultProperty = null;
  370. properties = new PropertySpecCollection();
  371. }
  372. /// <summary>
  373. /// Gets or sets the name of the default property in the collection.
  374. /// </summary>
  375. public string DefaultProperty
  376. {
  377. get { return defaultProperty; }
  378. set { defaultProperty = value; }
  379. }
  380. /// <summary>
  381. /// Gets the collection of properties contained within this PropertyBag.
  382. /// </summary>
  383. public PropertySpecCollection Properties
  384. {
  385. get { return properties; }
  386. }
  387. /// <summary>
  388. /// Occurs when a PropertyGrid requests the value of a property.
  389. /// </summary>
  390. public event PropertySpecEventHandler GetValue;
  391. /// <summary>
  392. /// Occurs when the user changes the value of a property in a PropertyGrid.
  393. /// </summary>
  394. public event PropertySpecEventHandler SetValue;
  395. /// <summary>
  396. /// Raises the GetValue event.
  397. /// </summary>
  398. /// <param name="e">A PropertySpecEventArgs that contains the event data.</param>
  399. protected virtual void OnGetValue(PropertySpecEventArgs e)
  400. {
  401. if (GetValue != null)
  402. GetValue(this, e);
  403. }
  404. /// <summary>
  405. /// Raises the SetValue event.
  406. /// </summary>
  407. /// <param name="e">A PropertySpecEventArgs that contains the event data.</param>
  408. protected virtual void OnSetValue(PropertySpecEventArgs e)
  409. {
  410. if (SetValue != null)
  411. SetValue(this, e);
  412. }
  413. #region ICustomTypeDescriptor explicit interface definitions
  414. // Most of the functions required by the ICustomTypeDescriptor are
  415. // merely pssed on to the default TypeDescriptor for this type,
  416. // which will do something appropriate. The exceptions are noted
  417. // below.
  418. AttributeCollection ICustomTypeDescriptor.GetAttributes()
  419. {
  420. return TypeDescriptor.GetAttributes(this, true);
  421. }
  422. string ICustomTypeDescriptor.GetClassName()
  423. {
  424. return TypeDescriptor.GetClassName(this, true);
  425. }
  426. string ICustomTypeDescriptor.GetComponentName()
  427. {
  428. return TypeDescriptor.GetComponentName(this, true);
  429. }
  430. System.ComponentModel.TypeConverter ICustomTypeDescriptor.GetConverter()
  431. {
  432. return TypeDescriptor.GetConverter(this, true);
  433. }
  434. EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
  435. {
  436. return TypeDescriptor.GetDefaultEvent(this, true);
  437. }
  438. PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
  439. {
  440. // This function searches the property list for the property
  441. // with the same name as the DefaultProperty specified, and
  442. // returns a property descriptor for it. If no property is
  443. // found that matches DefaultProperty, a null reference is
  444. // returned instead.
  445. if(defaultProperty == null && properties.Count != 0)
  446. {
  447. defaultProperty = properties[0].Name;
  448. }
  449. PropertySpec propertySpec = null;
  450. if (defaultProperty != null)
  451. {
  452. int index = properties.IndexOf(defaultProperty);
  453. propertySpec = properties[index];
  454. }
  455. if (propertySpec != null)
  456. return new PropertySpecDescriptor(propertySpec, this, propertySpec.Name, null);
  457. else
  458. return null;
  459. }
  460. object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
  461. {
  462. return TypeDescriptor.GetEditor(this, editorBaseType, true);
  463. }
  464. EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
  465. {
  466. return TypeDescriptor.GetEvents(this, true);
  467. }
  468. EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
  469. {
  470. return TypeDescriptor.GetEvents(this, attributes, true);
  471. }
  472. PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
  473. {
  474. return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
  475. }
  476. PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
  477. {
  478. // Rather than passing this function on to the default TypeDescriptor,
  479. // which would return the actual properties of PropertyBag, I construct
  480. // a list here that contains property descriptors for the elements of the
  481. // Properties list in the bag.
  482. ArrayList props = new ArrayList();
  483. foreach (PropertySpec property in properties)
  484. {
  485. ArrayList attrs = new ArrayList();
  486. // If a category, description, editor, or type converter are specified
  487. // in the PropertySpec, create attributes to define that relationship.
  488. if (property.Category != null)
  489. attrs.Add(new CategoryAttribute(property.Category));
  490. if (property.Description != null)
  491. attrs.Add(new DescriptionAttribute(property.Description));
  492. if (property.EditorTypeName != null)
  493. attrs.Add(new EditorAttribute(property.EditorTypeName, typeof(UITypeEditor)));
  494. if (property.ConverterTypeName != null)
  495. attrs.Add(new TypeConverterAttribute(property.ConverterTypeName));
  496. // Additionally, append the custom attributes associated with the
  497. // PropertySpec, if any.
  498. if (property.Attributes != null)
  499. attrs.AddRange(property.Attributes);
  500. Attribute[] attrArray = (Attribute[])attrs.ToArray(typeof(Attribute));
  501. // Create a new property descriptor for the property item, and add
  502. // it to the list.
  503. PropertySpecDescriptor pd = new PropertySpecDescriptor(property,
  504. this, property.Name, attrArray);
  505. props.Add(pd);
  506. }
  507. // Convert the list of PropertyDescriptors to a collection that the
  508. // ICustomTypeDescriptor can use, and return it.
  509. PropertyDescriptor[] propArray = (PropertyDescriptor[])props.ToArray(
  510. typeof(PropertyDescriptor));
  511. return new PropertyDescriptorCollection(propArray);
  512. }
  513. object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
  514. {
  515. return this;
  516. }
  517. #endregion
  518. }
  519. }