GridEntry.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. // Permission is hereby granted, free of charge, to any person obtaining
  2. // a copy of this software and associated documentation files (the
  3. // "Software"), to deal in the Software without restriction, including
  4. // without limitation the rights to use, copy, modify, merge, publish,
  5. // distribute, sublicense, and/or sell copies of the Software, and to
  6. // permit persons to whom the Software is furnished to do so, subject to
  7. // the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be
  10. // included in all copies or substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  13. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  14. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  15. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  16. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  17. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  18. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  19. //
  20. // Copyright (c) 2004-2008 Novell, Inc.
  21. //
  22. // Authors:
  23. // Jonathan Chambers ([email protected])
  24. // Ivan N. Zlatev ([email protected])
  25. //
  26. // NOT COMPLETE
  27. using System;
  28. using System.Collections;
  29. using System.Drawing;
  30. using System.Drawing.Design;
  31. using System.Windows.Forms;
  32. using System.Windows.Forms.Design;
  33. using System.ComponentModel;
  34. using System.ComponentModel.Design;
  35. namespace System.Windows.Forms.PropertyGridInternal
  36. {
  37. internal class GridEntry : GridItem, ITypeDescriptorContext
  38. {
  39. #region Local Variables
  40. private PropertyGrid property_grid;
  41. private bool expanded;
  42. private GridItemCollection grid_items;
  43. private GridItem parent;
  44. private PropertyDescriptor[] property_descriptors;
  45. private int top;
  46. private Rectangle plus_minus_bounds;
  47. #endregion // Local Variables
  48. #region Contructors
  49. protected GridEntry (PropertyGrid propertyGrid, GridEntry parent)
  50. {
  51. if (propertyGrid == null)
  52. throw new ArgumentNullException ("propertyGrid");
  53. property_grid = propertyGrid;
  54. plus_minus_bounds = new Rectangle (0,0,0,0);
  55. top = -1;
  56. grid_items = new GridItemCollection ();
  57. expanded = false;
  58. this.parent = parent;
  59. }
  60. // Cannot use one PropertyDescriptor for all owners, because the
  61. // propertydescriptors might have different Invokees. Check
  62. // ReflectionPropertyDescriptor.GetInvokee and how it's used.
  63. //
  64. public GridEntry (PropertyGrid propertyGrid, PropertyDescriptor[] properties,
  65. GridEntry parent) : this (propertyGrid, parent)
  66. {
  67. if (properties == null || properties.Length == 0)
  68. throw new ArgumentNullException ("prop_desc");
  69. property_descriptors = properties;
  70. }
  71. #endregion // Constructors
  72. public override bool Expandable {
  73. get { return grid_items.Count > 0; }
  74. }
  75. public override bool Expanded {
  76. get { return expanded; }
  77. set {
  78. if (expanded != value) {
  79. if (value)
  80. property_grid.OnExpandItem (this);
  81. else
  82. property_grid.OnCollapseItem (this);
  83. expanded = value;
  84. }
  85. }
  86. }
  87. public override GridItemCollection GridItems {
  88. get { return grid_items; }
  89. }
  90. public override GridItemType GridItemType {
  91. get { return GridItemType.Property; }
  92. }
  93. public override string Label {
  94. get { return PropertyDescriptor.Name; }
  95. }
  96. public override GridItem Parent {
  97. get { return parent; }
  98. }
  99. public GridEntry ParentEntry {
  100. get {
  101. if (parent != null && parent.GridItemType == GridItemType.Category)
  102. return parent.Parent as GridEntry;
  103. return parent as GridEntry;
  104. }
  105. }
  106. public override PropertyDescriptor PropertyDescriptor {
  107. get { return property_descriptors != null ? property_descriptors[0] : null; }
  108. }
  109. public PropertyDescriptor[] PropertyDescriptors {
  110. get { return property_descriptors; }
  111. }
  112. public object PropertyOwner {
  113. get {
  114. object[] owners = PropertyOwners;
  115. if (owners != null)
  116. return owners[0];
  117. return null;
  118. }
  119. }
  120. public object[] PropertyOwners {
  121. get {
  122. if (ParentEntry != null)
  123. return ParentEntry.Values;
  124. return null;
  125. }
  126. }
  127. // true if the value is the same among all properties
  128. public bool HasMergedValue {
  129. get {
  130. if (!IsMerged)
  131. return false;
  132. object[] values = this.Values;
  133. for (int i=0; i+1 < values.Length; i++) {
  134. if (!Object.Equals (values[i], values[i+1]))
  135. return false;
  136. }
  137. return true;
  138. }
  139. }
  140. public virtual bool IsMerged {
  141. get { return (PropertyDescriptors != null && PropertyDescriptors.Length > 1); }
  142. }
  143. // If IsMerged this will return all values for all properties in all owners
  144. public virtual object[] Values {
  145. get {
  146. if (PropertyDescriptor == null || this.PropertyOwners == null)
  147. return null;
  148. if (IsMerged) {
  149. object[] owners = this.PropertyOwners;
  150. PropertyDescriptor[] properties = PropertyDescriptors;
  151. object[] values = new object[owners.Length];
  152. for (int i=0; i < owners.Length; i++)
  153. values[i] = properties[i].GetValue (owners[i]);
  154. return values;
  155. } else {
  156. return new object[] { this.Value };
  157. }
  158. }
  159. }
  160. // Returns the first value for the first propertyowner and propertydescriptor
  161. //
  162. public override object Value {
  163. get {
  164. if (PropertyDescriptor == null || PropertyOwner == null)
  165. return null;
  166. return PropertyDescriptor.GetValue (PropertyOwner);
  167. }
  168. }
  169. public string ValueText {
  170. get { return ConvertToString (this.Value); }
  171. }
  172. public override bool Select ()
  173. {
  174. property_grid.SelectedGridItem = this;
  175. return true;
  176. }
  177. #region ITypeDescriptorContext
  178. void ITypeDescriptorContext.OnComponentChanged ()
  179. {
  180. }
  181. bool ITypeDescriptorContext.OnComponentChanging ()
  182. {
  183. return false;
  184. }
  185. IContainer ITypeDescriptorContext.Container {
  186. get {
  187. if (PropertyOwner == null)
  188. return null;
  189. IComponent component = property_grid.SelectedObject as IComponent;
  190. if (component != null && component.Site != null)
  191. return component.Site.Container;
  192. return null;
  193. }
  194. }
  195. object ITypeDescriptorContext.Instance {
  196. get { return PropertyOwner; }
  197. }
  198. PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor {
  199. get { return PropertyDescriptor; }
  200. }
  201. #endregion
  202. #region IServiceProvider Members
  203. object IServiceProvider.GetService (Type serviceType) {
  204. IComponent selectedComponent = property_grid.SelectedObject as IComponent;
  205. if (selectedComponent != null && selectedComponent.Site != null)
  206. return selectedComponent.Site.GetService (serviceType);
  207. return null;
  208. }
  209. #endregion
  210. internal int Top {
  211. get { return top; }
  212. set {
  213. if (top != value)
  214. top = value;
  215. }
  216. }
  217. internal Rectangle PlusMinusBounds {
  218. get { return plus_minus_bounds; }
  219. set { plus_minus_bounds = value; }
  220. }
  221. public void SetParent (GridItem parent)
  222. {
  223. this.parent = parent;
  224. }
  225. public ICollection AcceptedValues {
  226. get {
  227. if (PropertyDescriptor != null && PropertyDescriptor.Converter != null &&
  228. PropertyDescriptor.Converter.GetStandardValuesSupported ()) {
  229. ArrayList values = new ArrayList ();
  230. string stringVal = null;
  231. ICollection standardValues = PropertyDescriptor.Converter.GetStandardValues ();
  232. if (standardValues != null) {
  233. foreach (object value in standardValues) {
  234. stringVal = ConvertToString (value);
  235. if (stringVal != null)
  236. values.Add (stringVal);
  237. }
  238. }
  239. return values.Count > 0 ? values : null;
  240. }
  241. return null;
  242. }
  243. }
  244. private string ConvertToString (object value)
  245. {
  246. if (value is string)
  247. return (string)value;
  248. if (PropertyDescriptor != null && PropertyDescriptor.Converter != null &&
  249. PropertyDescriptor.Converter.CanConvertTo (typeof (string))) {
  250. try {
  251. return PropertyDescriptor.Converter.ConvertToString (value);
  252. } catch {
  253. // XXX: Happens too often...
  254. // property_grid.ShowError ("Property value of '" + property_descriptor.Name + "' is not convertible to string.");
  255. return null;
  256. }
  257. }
  258. return null;
  259. }
  260. public bool HasCustomEditor {
  261. get { return GetEditor() != null; }
  262. }
  263. public UITypeEditorEditStyle EditorStyle {
  264. get {
  265. UITypeEditor editor = GetEditor ();
  266. if (editor != null) {
  267. try {
  268. return editor.GetEditStyle ((ITypeDescriptorContext)this);
  269. } catch {
  270. // Some of our Editors throw NotImplementedException
  271. }
  272. }
  273. return UITypeEditorEditStyle.None;
  274. }
  275. }
  276. public bool EditValue (IWindowsFormsEditorService service)
  277. {
  278. if (service == null)
  279. throw new ArgumentNullException ("service");
  280. IServiceContainer parent = ((ITypeDescriptorContext)this).GetService (typeof (IServiceContainer)) as IServiceContainer;
  281. ServiceContainer container = null;
  282. if (parent != null)
  283. container = new ServiceContainer (parent);
  284. else
  285. container = new ServiceContainer ();
  286. container.AddService (typeof (IWindowsFormsEditorService), service);
  287. UITypeEditor editor = GetEditor ();
  288. if (editor != null) {
  289. try {
  290. object value = editor.EditValue ((ITypeDescriptorContext)this,
  291. container,
  292. this.Value);
  293. string error = null;
  294. return SetValue (value, out error);
  295. } catch (Exception e) {
  296. property_grid.ShowError (e.Message + Environment.NewLine + e.StackTrace);
  297. }
  298. }
  299. return false;
  300. }
  301. private UITypeEditor GetEditor ()
  302. {
  303. if (PropertyDescriptor != null) {
  304. try { // can happen, because we are missing some editors
  305. return PropertyDescriptor.GetEditor (typeof (UITypeEditor)) as UITypeEditor;
  306. } catch {
  307. // property_grid.ShowError ("Unable to load UITypeEditor for property '" + PropertyDescriptor.Name + "'.");
  308. }
  309. }
  310. return null;
  311. }
  312. public bool ToggleValue ()
  313. {
  314. if (IsReadOnly || (IsMerged && !HasMergedValue))
  315. return false;
  316. bool success = false;
  317. string error = null;
  318. if (PropertyDescriptor.PropertyType == typeof(bool))
  319. success = SetValue (!(bool)this.Value, out error);
  320. else if (PropertyDescriptor.Converter != null &&
  321. PropertyDescriptor.Converter.GetStandardValuesSupported ()) {
  322. TypeConverter.StandardValuesCollection values =
  323. (TypeConverter.StandardValuesCollection) PropertyDescriptor.Converter.GetStandardValues();
  324. if (values != null) {
  325. for (int i = 0; i < values.Count; i++) {
  326. if (this.Value != null && this.Value.Equals (values[i])){
  327. if (i < values.Count-1)
  328. success = SetValue (values[i+1], out error);
  329. else
  330. success = SetValue (values[0], out error);
  331. break;
  332. }
  333. }
  334. }
  335. }
  336. if (!success && error != null)
  337. property_grid.ShowError (error);
  338. return success;
  339. }
  340. public bool SetValue (object value, out string error)
  341. {
  342. error = null;
  343. if (this.IsReadOnly)
  344. return false;
  345. if (SetValueCore (value, out error)) {
  346. property_grid.OnPropertyValueChangedInternal (this, this.Value);
  347. return true;
  348. }
  349. return false;
  350. }
  351. protected virtual bool SetValueCore (object value, out string error)
  352. {
  353. error = null;
  354. TypeConverter converter = PropertyDescriptor.Converter;
  355. // if the new value is not of the same type try to convert it
  356. if (value != null &&
  357. (this.Value == null ||
  358. this.Value != null && value.GetType () != this.Value.GetType ())) {
  359. bool conversionError = false;
  360. if (converter != null &&
  361. converter.CanConvertFrom (value.GetType ())) {
  362. try {
  363. value = converter.ConvertFrom (value);
  364. } catch {
  365. conversionError = true;
  366. }
  367. } else {
  368. conversionError = true;
  369. }
  370. if (conversionError) {
  371. // MS swallows those
  372. //
  373. // string valueText = ConvertToString (value);
  374. // if (valueText != null) {
  375. // error = "Property value '" + valueText + "' of '" +
  376. // PropertyDescriptor.Name + "' is not convertible to type '" +
  377. // this.PropertyDescriptor.PropertyType.Name + "'";
  378. //
  379. // } else {
  380. // error = "Property value of '" +
  381. // PropertyDescriptor.Name + "' is not convertible to type '" +
  382. // this.PropertyDescriptor.PropertyType.Name + "'";
  383. // }
  384. return false;
  385. }
  386. }
  387. bool changed = false;
  388. bool current_changed = false;
  389. object[] propertyOwners = this.PropertyOwners;
  390. PropertyDescriptor[] properties = PropertyDescriptors;
  391. for (int i=0; i < propertyOwners.Length; i++) {
  392. object currentVal = properties[i].GetValue (propertyOwners[i]);
  393. current_changed = false;
  394. if (!Object.Equals (currentVal, value)) {
  395. if (this.ShouldCreateParentInstance) {
  396. Hashtable updatedParentProperties = new Hashtable ();
  397. PropertyDescriptorCollection parentProperties = TypeDescriptor.GetProperties (propertyOwners[i]);
  398. foreach (PropertyDescriptor property in parentProperties) {
  399. if (property.Name == properties[i].Name)
  400. updatedParentProperties[property.Name] = value;
  401. else
  402. updatedParentProperties[property.Name] = property.GetValue (propertyOwners[i]);
  403. }
  404. object updatedParentValue = this.ParentEntry.PropertyDescriptor.Converter.CreateInstance (updatedParentProperties);
  405. if (updatedParentValue != null)
  406. current_changed = this.ParentEntry.SetValueCore (updatedParentValue, out error);
  407. } else {
  408. try {
  409. properties[i].SetValue (propertyOwners[i], value);
  410. } catch {
  411. // MS seems to swallow this
  412. //
  413. // string valueText = ConvertToString (value);
  414. // if (valueText != null)
  415. // error = "Property value '" + valueText + "' of '" + properties[i].Name + "' is invalid.";
  416. // else
  417. // error = "Property value of '" + properties[i].Name + "' is invalid.";
  418. return false;
  419. }
  420. if (IsValueType (this.ParentEntry))
  421. current_changed = ParentEntry.SetValueCore (propertyOwners[i], out error);
  422. else
  423. current_changed = Object.Equals (properties[i].GetValue (propertyOwners[i]), value);
  424. }
  425. // restore original value if doesn't get set
  426. if (!current_changed && !PropertyDescriptor.IsReadOnly)
  427. properties[i].SetValue (propertyOwners[i], currentVal);
  428. }
  429. if (current_changed)
  430. changed = true;
  431. }
  432. return changed;
  433. }
  434. private bool IsValueType (GridEntry item)
  435. {
  436. if (item != null && item.PropertyDescriptor != null &&
  437. (item.PropertyDescriptor.PropertyType.IsValueType ||
  438. item.PropertyDescriptor.PropertyType.IsPrimitive))
  439. return true;
  440. return false;
  441. }
  442. public bool ResetValue ()
  443. {
  444. if (IsResetable) {
  445. object[] owners = this.PropertyOwners;
  446. PropertyDescriptor[] properties = PropertyDescriptors;
  447. for (int i=0; i < owners.Length; i++) {
  448. properties[i].ResetValue (owners[i]);
  449. if (IsValueType (this.ParentEntry)) {
  450. string error = null;
  451. if (!ParentEntry.SetValueCore (owners[i], out error) && error != null)
  452. property_grid.ShowError (error);
  453. }
  454. }
  455. property_grid.OnPropertyValueChangedInternal (this, this.Value);
  456. return true;
  457. }
  458. return false;
  459. }
  460. public bool HasDefaultValue {
  461. get {
  462. if (PropertyDescriptor != null &&
  463. PropertyDescriptor.Attributes[typeof (DefaultValueAttribute)] != null)
  464. return true;
  465. return false;
  466. }
  467. }
  468. // Determines if the current value can be reset
  469. //
  470. public virtual bool IsResetable {
  471. get { return (!IsReadOnly && PropertyDescriptor.CanResetValue (PropertyOwner)); }
  472. }
  473. // If false the entry can be modified only by the means of a predefined values
  474. // and not such inputed by the user.
  475. //
  476. public virtual bool IsEditable {
  477. get {
  478. if (PropertyDescriptor == null)
  479. return false;
  480. else if (PropertyDescriptor.PropertyType.IsArray)
  481. return false;
  482. else if (PropertyDescriptor.IsReadOnly && this.ShouldCreateParentInstance)
  483. return true;
  484. else if (PropertyDescriptor.Converter == null ||
  485. !PropertyDescriptor.Converter.CanConvertFrom (this, typeof (string)))
  486. return false;
  487. else if (PropertyDescriptor.Converter.GetStandardValuesSupported () &&
  488. PropertyDescriptor.Converter.GetStandardValuesExclusive ())
  489. return false;
  490. else
  491. return true;
  492. }
  493. }
  494. // If true the the entry cannot be modified at all
  495. //
  496. public virtual bool IsReadOnly {
  497. get {
  498. // if (PropertyDescriptor != null) {
  499. // Console.WriteLine ("=== [" + PropertyDescriptor.Name + "]");
  500. // Console.WriteLine ("PropertyDescriptor.IsReadOnly: " + PropertyDescriptor.IsReadOnly);
  501. // Console.WriteLine ("Editor: " + (GetEditor () == null ? "none" : GetEditor ().GetType ().Name));
  502. // Console.WriteLine ("Converter: " + (PropertyDescriptor.Converter == null ? "none" : PropertyDescriptor.Converter.GetType ().Name));
  503. // Console.WriteLine ("Converter.GetStandardValuesSupported: " + PropertyDescriptor.Converter.GetStandardValuesSupported ().ToString ());
  504. // Console.WriteLine ("Converter.GetStandardValuesExclusive: " + PropertyDescriptor.Converter.GetStandardValuesExclusive ().ToString ());
  505. // Console.WriteLine ("ShouldCreateParentInstance: " + this.ShouldCreateParentInstance);
  506. // Console.WriteLine ("CanConvertFrom (string): " + PropertyDescriptor.Converter.CanConvertFrom ((ITypeDescriptorContext)this, typeof (string)));
  507. // Console.WriteLine ("IsArray: " + PropertyDescriptor.PropertyType.IsArray.ToString ());
  508. // }
  509. if (PropertyDescriptor == null || PropertyOwner == null ||
  510. (PropertyDescriptor.IsReadOnly && !this.ShouldCreateParentInstance))
  511. return true;
  512. else if (!HasCustomEditor && PropertyDescriptor.Converter == null)
  513. return true;
  514. else if (PropertyDescriptor.Converter != null &&
  515. !PropertyDescriptor.Converter.GetStandardValuesSupported () &&
  516. !PropertyDescriptor.Converter.CanConvertFrom ((ITypeDescriptorContext)this,
  517. typeof (string)) &&
  518. !HasCustomEditor) {
  519. return true;
  520. } else if (PropertyDescriptor.PropertyType.IsArray && !HasCustomEditor)
  521. return true;
  522. else
  523. return false;
  524. }
  525. }
  526. // This is a way to set readonly properties (e.g properties without a setter).
  527. // The way it works is that if CreateInstance is supported by the parent's converter
  528. // it gets passed a list of properties and their values which it uses to create an
  529. // instance (e.g by passing them to the ctor of that object type).
  530. //
  531. // This is used for e.g Font
  532. //
  533. public virtual bool ShouldCreateParentInstance {
  534. get {
  535. if (this.ParentEntry != null && ParentEntry.PropertyDescriptor != null) {
  536. TypeConverter parentConverter = Parent.PropertyDescriptor.Converter;
  537. if (parentConverter != null && parentConverter.GetCreateInstanceSupported ((ITypeDescriptorContext)this))
  538. return true;
  539. }
  540. return false;
  541. }
  542. }
  543. public virtual bool PaintValueSupported {
  544. get {
  545. UITypeEditor editor = GetEditor ();
  546. if (editor != null) {
  547. try {
  548. return editor.GetPaintValueSupported ();
  549. } catch {
  550. // Some of our Editors throw NotImplementedException
  551. }
  552. }
  553. return false;
  554. }
  555. }
  556. public virtual void PaintValue (Graphics gfx, Rectangle rect)
  557. {
  558. UITypeEditor editor = GetEditor ();
  559. if (editor != null) {
  560. try {
  561. editor.PaintValue (this.Value, gfx, rect);
  562. } catch {
  563. // Some of our Editors throw NotImplementedException
  564. }
  565. }
  566. }
  567. }
  568. }