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:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
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.
20 // Copyright (c) 2004-2008 Novell, Inc.
23 // Jonathan Chambers (jonathan.chambers@ansys.com)
24 // Ivan N. Zlatev (contact@i-nz.net)
30 using System.Collections;
32 using System.Drawing.Design;
33 using System.Windows.Forms;
34 using System.Windows.Forms.Design;
35 using System.ComponentModel;
36 using System.ComponentModel.Design;
37 using System.Globalization;
39 namespace System.Windows.Forms.PropertyGridInternal
41 internal class GridEntry : GridItem, ITypeDescriptorContext
43 #region Local Variables
44 private PropertyGrid property_grid;
45 private bool expanded;
46 private GridItemCollection grid_items;
47 private GridItem parent;
48 private PropertyDescriptor[] property_descriptors;
50 private Rectangle plus_minus_bounds;
51 private GridItemCollection child_griditems_cache;
52 #endregion // Local Variables
55 protected GridEntry (PropertyGrid propertyGrid, GridEntry parent)
57 if (propertyGrid == null)
58 throw new ArgumentNullException ("propertyGrid");
59 property_grid = propertyGrid;
60 plus_minus_bounds = new Rectangle (0,0,0,0);
62 grid_items = new GridItemCollection ();
65 child_griditems_cache = null;
68 // Cannot use one PropertyDescriptor for all owners, because the
69 // propertydescriptors might have different Invokees. Check
70 // ReflectionPropertyDescriptor.GetInvokee and how it's used.
72 public GridEntry (PropertyGrid propertyGrid, PropertyDescriptor[] properties,
73 GridEntry parent) : this (propertyGrid, parent)
75 if (properties == null || properties.Length == 0)
76 throw new ArgumentNullException ("prop_desc");
77 property_descriptors = properties;
79 #endregion // Constructors
82 public override bool Expandable {
84 TypeConverter converter = GetConverter ();
85 if (converter == null || !converter.GetPropertiesSupported ((ITypeDescriptorContext)this))
88 if (GetChildGridItemsCached ().Count > 0)
95 public override bool Expanded {
96 get { return expanded; }
98 if (expanded != value) {
100 PopulateChildGridItems ();
102 property_grid.OnExpandItem (this);
104 property_grid.OnCollapseItem (this);
109 public override GridItemCollection GridItems {
111 PopulateChildGridItems ();
116 public override GridItemType GridItemType {
117 get { return GridItemType.Property; }
120 public override string Label {
122 PropertyDescriptor property = this.PropertyDescriptor;
123 if (property != null) {
124 string label = property.DisplayName;
125 ParenthesizePropertyNameAttribute parensAttr =
126 property.Attributes[typeof (ParenthesizePropertyNameAttribute)] as ParenthesizePropertyNameAttribute;
127 if (parensAttr != null && parensAttr.NeedParenthesis)
128 label = "(" + label + ")";
135 public override GridItem Parent {
136 get { return parent; }
139 public GridEntry ParentEntry {
141 if (parent != null && parent.GridItemType == GridItemType.Category)
142 return parent.Parent as GridEntry;
143 return parent as GridEntry;
147 public override PropertyDescriptor PropertyDescriptor {
148 get { return property_descriptors != null ? property_descriptors[0] : null; }
151 public PropertyDescriptor[] PropertyDescriptors {
152 get { return property_descriptors; }
155 public object PropertyOwner {
157 object[] owners = PropertyOwners;
164 public object[] PropertyOwners {
166 if (ParentEntry == null)
169 object[] owners = ParentEntry.Values;
170 PropertyDescriptor[] properties = PropertyDescriptors;
171 object newOwner = null;
172 for (int i=0; i < owners.Length; i++) {
173 if (owners[i] is ICustomTypeDescriptor) {
174 newOwner = ((ICustomTypeDescriptor)owners[i]).GetPropertyOwner (properties[i]);
175 if (newOwner != null)
176 owners[i] = newOwner;
183 // true if the value is the same among all properties
184 public bool HasMergedValue {
189 object[] values = this.Values;
190 for (int i=0; i+1 < values.Length; i++) {
191 if (!Object.Equals (values[i], values[i+1]))
198 public virtual bool IsMerged {
199 get { return (PropertyDescriptors != null && PropertyDescriptors.Length > 1); }
202 // If IsMerged this will return all values for all properties in all owners
203 public virtual object[] Values {
205 if (PropertyDescriptor == null || this.PropertyOwners == null)
208 object[] owners = this.PropertyOwners;
209 PropertyDescriptor[] properties = PropertyDescriptors;
210 object[] values = new object[owners.Length];
211 for (int i=0; i < owners.Length; i++)
212 values[i] = properties[i].GetValue (owners[i]);
215 return new object[] { this.Value };
220 // Returns the first value for the first propertyowner and propertydescriptor
222 public override object Value {
224 if (PropertyDescriptor == null || PropertyOwner == null)
227 return PropertyDescriptor.GetValue (PropertyOwner);
231 public string ValueText {
235 text = ConvertToString (this.Value);
245 public override bool Select ()
247 property_grid.SelectedGridItem = this;
251 #region ITypeDescriptorContext
252 void ITypeDescriptorContext.OnComponentChanged ()
256 bool ITypeDescriptorContext.OnComponentChanging ()
261 IContainer ITypeDescriptorContext.Container {
263 if (PropertyOwner == null)
266 IComponent component = property_grid.SelectedObject as IComponent;
267 if (component != null && component.Site != null)
268 return component.Site.Container;
273 object ITypeDescriptorContext.Instance {
275 if (ParentEntry != null && ParentEntry.PropertyOwner != null)
276 return ParentEntry.PropertyOwner;
277 return PropertyOwner;
281 PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor {
283 if (ParentEntry != null && ParentEntry.PropertyDescriptor != null)
284 return ParentEntry.PropertyDescriptor;
285 return PropertyDescriptor;
290 #region IServiceProvider Members
292 object IServiceProvider.GetService (Type serviceType) {
293 IComponent selectedComponent = property_grid.SelectedObject as IComponent;
294 if (selectedComponent != null && selectedComponent.Site != null)
295 return selectedComponent.Site.GetService (serviceType);
309 internal Rectangle PlusMinusBounds {
310 get { return plus_minus_bounds; }
311 set { plus_minus_bounds = value; }
314 public void SetParent (GridItem parent)
316 this.parent = parent;
319 public ICollection AcceptedValues {
321 TypeConverter converter = GetConverter ();
322 if (PropertyDescriptor != null && converter != null &&
323 converter.GetStandardValuesSupported ((ITypeDescriptorContext)this)) {
324 ArrayList values = new ArrayList ();
325 string stringVal = null;
326 ICollection standardValues = converter.GetStandardValues ((ITypeDescriptorContext)this);
327 if (standardValues != null) {
328 foreach (object value in standardValues) {
329 stringVal = ConvertToString (value);
330 if (stringVal != null)
331 values.Add (stringVal);
334 return values.Count > 0 ? values : null;
340 private string ConvertToString (object value)
343 return (string)value;
345 if (PropertyDescriptor != null && PropertyDescriptor.Converter != null &&
346 PropertyDescriptor.Converter.CanConvertTo ((ITypeDescriptorContext)this, typeof (string))) {
348 return PropertyDescriptor.Converter.ConvertToString ((ITypeDescriptorContext)this, value);
350 // XXX: Happens too often...
351 // property_grid.ShowError ("Property value of '" + property_descriptor.Name + "' is not convertible to string.");
359 public bool HasCustomEditor {
360 get { return EditorStyle != UITypeEditorEditStyle.None; }
363 public UITypeEditorEditStyle EditorStyle {
365 UITypeEditor editor = GetEditor ();
366 if (editor != null) {
368 return editor.GetEditStyle ((ITypeDescriptorContext)this);
370 // Some of our Editors throw NotImplementedException
373 return UITypeEditorEditStyle.None;
377 public bool EditorResizeable {
379 if (this.EditorStyle == UITypeEditorEditStyle.DropDown) {
380 UITypeEditor editor = GetEditor ();
381 if (editor != null && editor.IsDropDownResizable)
388 public bool EditValue (IWindowsFormsEditorService service)
391 throw new ArgumentNullException ("service");
393 IServiceContainer parent = ((ITypeDescriptorContext)this).GetService (typeof (IServiceContainer)) as IServiceContainer;
394 ServiceContainer container = null;
397 container = new ServiceContainer (parent);
399 container = new ServiceContainer ();
401 container.AddService (typeof (IWindowsFormsEditorService), service);
403 UITypeEditor editor = GetEditor ();
404 if (editor != null) {
406 object value = editor.EditValue ((ITypeDescriptorContext)this,
410 return SetValue (value, out error);
411 } catch { //(Exception e) {
412 // property_grid.ShowError (e.Message + Environment.NewLine + e.StackTrace);
418 private UITypeEditor GetEditor ()
420 if (PropertyDescriptor != null) {
421 try { // can happen, because we are missing some editors
422 if (PropertyDescriptor != null)
423 return (UITypeEditor) PropertyDescriptor.GetEditor (typeof (UITypeEditor));
425 // property_grid.ShowError ("Unable to load UITypeEditor for property '" + PropertyDescriptor.Name + "'.");
431 private TypeConverter GetConverter ()
433 if (PropertyDescriptor != null)
434 return PropertyDescriptor.Converter;
438 public bool ToggleValue ()
440 if (IsReadOnly || (IsMerged && !HasMergedValue))
443 bool success = false;
445 object value = this.Value;
446 if (PropertyDescriptor.PropertyType == typeof(bool))
447 success = SetValue (!(bool)value, out error);
449 TypeConverter converter = GetConverter ();
450 if (converter != null &&
451 converter.GetStandardValuesSupported ((ITypeDescriptorContext)this)) {
452 TypeConverter.StandardValuesCollection values =
453 (TypeConverter.StandardValuesCollection) converter.GetStandardValues ((ITypeDescriptorContext)this);
454 if (values != null) {
455 for (int i = 0; i < values.Count; i++) {
456 if (value != null && value.Equals (values[i])){
457 if (i < values.Count-1)
458 success = SetValue (values[i+1], out error);
460 success = SetValue (values[0], out error);
467 if (!success && error != null)
468 property_grid.ShowError (error);
472 public bool SetValue (object value, out string error)
478 if (SetValueCore (value, out error)) {
479 InvalidateChildGridItemsCache ();
480 property_grid.OnPropertyValueChangedInternal (this, this.Value);
486 protected virtual bool SetValueCore (object value, out string error)
490 TypeConverter converter = GetConverter ();
491 Type valueType = value != null ? value.GetType () : null;
492 // if the new value is not of the same type try to convert it
493 if (valueType != null && this.PropertyDescriptor.PropertyType != null &&
494 !this.PropertyDescriptor.PropertyType.IsAssignableFrom (valueType)) {
495 bool conversionError = false;
497 if (converter != null &&
498 converter.CanConvertFrom ((ITypeDescriptorContext)this, valueType))
499 value = converter.ConvertFrom ((ITypeDescriptorContext)this,
500 CultureInfo.CurrentCulture, value);
502 conversionError = true;
503 } catch (Exception e) {
505 conversionError = true;
507 if (conversionError) {
508 string valueText = ConvertToString (value);
509 string errorShortDescription = null;
510 if (valueText != null) {
511 errorShortDescription = "Property value '" + valueText + "' of '" +
512 PropertyDescriptor.Name + "' is not convertible to type '" +
513 this.PropertyDescriptor.PropertyType.Name + "'";
516 errorShortDescription = "Property value of '" +
517 PropertyDescriptor.Name + "' is not convertible to type '" +
518 this.PropertyDescriptor.PropertyType.Name + "'";
520 error = errorShortDescription + Environment.NewLine + Environment.NewLine + error;
525 bool changed = false;
526 bool current_changed = false;
527 object[] propertyOwners = this.PropertyOwners;
528 PropertyDescriptor[] properties = PropertyDescriptors;
529 for (int i=0; i < propertyOwners.Length; i++) {
530 object currentVal = properties[i].GetValue (propertyOwners[i]);
531 current_changed = false;
532 if (!Object.Equals (currentVal, value)) {
533 if (this.ShouldCreateParentInstance) {
534 Hashtable updatedParentProperties = new Hashtable ();
535 PropertyDescriptorCollection parentProperties = TypeDescriptor.GetProperties (propertyOwners[i]);
536 foreach (PropertyDescriptor property in parentProperties) {
537 if (property.Name == properties[i].Name)
538 updatedParentProperties[property.Name] = value;
540 updatedParentProperties[property.Name] = property.GetValue (propertyOwners[i]);
542 object updatedParentValue = this.ParentEntry.PropertyDescriptor.Converter.CreateInstance (
543 (ITypeDescriptorContext)this, updatedParentProperties);
544 if (updatedParentValue != null)
545 current_changed = this.ParentEntry.SetValueCore (updatedParentValue, out error);
548 properties[i].SetValue (propertyOwners[i], value);
550 // MS seems to swallow this
552 // string valueText = ConvertToString (value);
553 // if (valueText != null)
554 // error = "Property value '" + valueText + "' of '" + properties[i].Name + "' is invalid.";
556 // error = "Property value of '" + properties[i].Name + "' is invalid.";
560 if (IsValueType (this.ParentEntry))
561 current_changed = ParentEntry.SetValueCore (propertyOwners[i], out error);
563 current_changed = Object.Equals (properties[i].GetValue (propertyOwners[i]), value);
572 private bool IsValueType (GridEntry item)
574 if (item != null && item.PropertyDescriptor != null &&
575 (item.PropertyDescriptor.PropertyType.IsValueType ||
576 item.PropertyDescriptor.PropertyType.IsPrimitive))
581 public bool ResetValue ()
584 object[] owners = this.PropertyOwners;
585 PropertyDescriptor[] properties = PropertyDescriptors;
586 for (int i=0; i < owners.Length; i++) {
587 properties[i].ResetValue (owners[i]);
588 if (IsValueType (this.ParentEntry)) {
590 if (!ParentEntry.SetValueCore (owners[i], out error) && error != null)
591 property_grid.ShowError (error);
594 property_grid.OnPropertyValueChangedInternal (this, this.Value);
600 public bool HasDefaultValue {
602 if (PropertyDescriptor != null)
603 return !PropertyDescriptor.ShouldSerializeValue (PropertyOwner);
608 // Determines if the current value can be reset
610 public virtual bool IsResetable {
611 get { return (!IsReadOnly && PropertyDescriptor.CanResetValue (PropertyOwner)); }
615 // If false the entry can be modified only by the means of a predefined values
616 // and not such inputed by the user.
618 public virtual bool IsEditable {
620 TypeConverter converter = GetConverter ();
621 if (PropertyDescriptor == null)
623 else if (PropertyDescriptor.PropertyType.IsArray)
625 else if (PropertyDescriptor.IsReadOnly && !this.ShouldCreateParentInstance)
627 else if (converter == null ||
628 !converter.CanConvertFrom ((ITypeDescriptorContext)this, typeof (string)))
630 else if (converter.GetStandardValuesSupported ((ITypeDescriptorContext)this) &&
631 converter.GetStandardValuesExclusive ((ITypeDescriptorContext)this))
638 // If true the the entry cannot be modified at all
640 public virtual bool IsReadOnly {
642 TypeConverter converter = GetConverter ();
643 if (PropertyDescriptor == null || PropertyOwner == null)
645 else if (PropertyDescriptor.IsReadOnly &&
646 (EditorStyle != UITypeEditorEditStyle.Modal || PropertyDescriptor.PropertyType.IsValueType) &&
647 !this.ShouldCreateParentInstance)
649 else if (PropertyDescriptor.IsReadOnly &&
650 TypeDescriptor.GetAttributes (PropertyDescriptor.PropertyType)
651 [typeof(ImmutableObjectAttribute)].Equals (ImmutableObjectAttribute.Yes))
653 else if (ShouldCreateParentInstance && ParentEntry.IsReadOnly)
655 else if (!HasCustomEditor && converter == null)
657 else if (converter != null &&
658 !converter.GetStandardValuesSupported ((ITypeDescriptorContext)this) &&
659 !converter.CanConvertFrom ((ITypeDescriptorContext)this,
663 } else if (PropertyDescriptor.PropertyType.IsArray && !HasCustomEditor)
670 public bool IsPassword {
672 if (PropertyDescriptor != null)
673 return PropertyDescriptor.Attributes.Contains (PasswordPropertyTextAttribute.Yes);
677 // This is a way to set readonly properties (e.g properties without a setter).
678 // The way it works is that if CreateInstance is supported by the parent's converter
679 // it gets passed a list of properties and their values which it uses to create an
680 // instance (e.g by passing them to the ctor of that object type).
682 // This is used for e.g Font
684 public virtual bool ShouldCreateParentInstance {
686 if (this.ParentEntry != null && ParentEntry.PropertyDescriptor != null) {
687 TypeConverter parentConverter = ParentEntry.GetConverter ();
688 if (parentConverter != null && parentConverter.GetCreateInstanceSupported ((ITypeDescriptorContext)this))
695 public virtual bool PaintValueSupported {
697 UITypeEditor editor = GetEditor ();
698 if (editor != null) {
700 return editor.GetPaintValueSupported ();
702 // Some of our Editors throw NotImplementedException
709 public virtual void PaintValue (Graphics gfx, Rectangle rect)
711 UITypeEditor editor = GetEditor ();
712 if (editor != null) {
714 editor.PaintValue (this.Value, gfx, rect);
716 // Some of our Editors throw NotImplementedException
722 protected void PopulateChildGridItems ()
724 grid_items = GetChildGridItemsCached ();
727 private void InvalidateChildGridItemsCache ()
729 if (child_griditems_cache != null) {
730 child_griditems_cache = null;
731 PopulateChildGridItems ();
735 private GridItemCollection GetChildGridItemsCached ()
737 if (child_griditems_cache == null) {
738 child_griditems_cache = GetChildGridItems ();
739 // foreach (GridEntry item in child_griditems_cache)
740 // PrintDebugInfo (item);
743 return child_griditems_cache;
746 // private static void PrintDebugInfo (GridEntry item)
748 // if (item.PropertyDescriptor != null) {
749 // Console.WriteLine ("=== [" + item.PropertyDescriptor.Name + "] ===");
751 // TypeConverter converter = item.GetConverter ();
752 // Console.WriteLine ("IsReadOnly: " + item.IsReadOnly);
753 // Console.WriteLine ("IsEditable: " + item.IsEditable);
754 // Console.WriteLine ("PropertyDescriptor.IsReadOnly: " + item.PropertyDescriptor.IsReadOnly);
755 // if (item.ParentEntry != null)
756 // Console.WriteLine ("ParentEntry.IsReadOnly: " + item.ParentEntry.IsReadOnly);
757 // Console.WriteLine ("ImmutableObjectAttribute.Yes: " + TypeDescriptor.GetAttributes (item.PropertyDescriptor.PropertyType)
758 // [typeof(ImmutableObjectAttribute)].Equals (ImmutableObjectAttribute.Yes));
759 // UITypeEditor editor = item.GetEditor ();
760 // Console.WriteLine ("Editor: " + (editor == null ? "none" : editor.GetType ().Name));
761 // if (editor != null)
762 // Console.WriteLine ("Editor.EditorStyle: " + editor.GetEditStyle ((ITypeDescriptorContext)item));
763 // Console.WriteLine ("Converter: " + (converter == null ? "none" : converter.GetType ().Name));
764 // if (converter != null) {
765 // Console.WriteLine ("Converter.GetStandardValuesSupported: " + converter.GetStandardValuesSupported ((ITypeDescriptorContext)item).ToString ());
766 // Console.WriteLine ("Converter.GetStandardValuesExclusive: " + converter.GetStandardValuesExclusive ((ITypeDescriptorContext)item).ToString ());
767 // Console.WriteLine ("ShouldCreateParentInstance: " + item.ShouldCreateParentInstance);
768 // Console.WriteLine ("CanConvertFrom (string): " + converter.CanConvertFrom ((ITypeDescriptorContext)item, typeof (string)));
770 // Console.WriteLine ("IsArray: " + item.PropertyDescriptor.PropertyType.IsArray.ToString ());
771 // } catch { /* Some converters and editor throw NotImplementedExceptions */ }
775 private GridItemCollection GetChildGridItems ()
777 object[] propertyOwners = this.Values;
778 string[] propertyNames = GetMergedPropertyNames (propertyOwners);
779 GridItemCollection items = new GridItemCollection ();
781 foreach (string propertyName in propertyNames) {
782 PropertyDescriptor[] properties = new PropertyDescriptor[propertyOwners.Length];
783 for (int i=0; i < propertyOwners.Length; i++)
784 properties[i] = GetPropertyDescriptor (propertyOwners[i], propertyName);
785 items.Add (new GridEntry (property_grid, properties, this));
791 private bool IsPropertyMergeable (PropertyDescriptor property)
793 if (property == null)
796 MergablePropertyAttribute attrib = property.Attributes [typeof (MergablePropertyAttribute)] as MergablePropertyAttribute;
797 if (attrib != null && !attrib.AllowMerge)
803 private string[] GetMergedPropertyNames (object [] objects)
805 if (objects == null || objects.Length == 0)
806 return new string[0];
808 ArrayList intersection = new ArrayList ();
809 for (int i = 0; i < objects.Length; i ++) {
810 if (objects [i] == null)
813 PropertyDescriptorCollection properties = GetProperties (objects[i], property_grid.BrowsableAttributes);
814 ArrayList new_intersection = new ArrayList ();
816 foreach (PropertyDescriptor currentProperty in (i == 0 ? (ICollection)properties : (ICollection)intersection)) {
817 PropertyDescriptor matchingProperty = (i == 0 ? currentProperty : properties [currentProperty.Name]);
818 if (objects.Length > 1 && !IsPropertyMergeable (matchingProperty))
820 if (matchingProperty.PropertyType == currentProperty.PropertyType)
821 new_intersection.Add (matchingProperty);
824 intersection = new_intersection;
827 string[] propertyNames = new string [intersection.Count];
828 for (int i=0; i < intersection.Count; i++)
829 propertyNames[i] = ((PropertyDescriptor)intersection[i]).Name;
831 return propertyNames;
834 private PropertyDescriptor GetPropertyDescriptor (object propertyOwner, string propertyName)
836 if (propertyOwner == null || propertyName == null)
839 PropertyDescriptorCollection properties = GetProperties (propertyOwner, property_grid.BrowsableAttributes);
840 if (properties != null)
841 return properties[propertyName];
845 private PropertyDescriptorCollection GetProperties (object propertyOwner, AttributeCollection attributes)
847 if (propertyOwner == null || property_grid.SelectedTab == null)
848 return new PropertyDescriptorCollection (null);
850 Attribute[] atts = new Attribute[attributes.Count];
851 attributes.CopyTo (atts, 0);
852 return property_grid.SelectedTab.GetProperties ((ITypeDescriptorContext)this, propertyOwner, atts);