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 {
380 if (this.EditorStyle == UITypeEditorEditStyle.DropDown) {
381 UITypeEditor editor = GetEditor ();
382 if (editor != null && editor.IsDropDownResizable)
390 public bool EditValue (IWindowsFormsEditorService service)
393 throw new ArgumentNullException ("service");
395 IServiceContainer parent = ((ITypeDescriptorContext)this).GetService (typeof (IServiceContainer)) as IServiceContainer;
396 ServiceContainer container = null;
399 container = new ServiceContainer (parent);
401 container = new ServiceContainer ();
403 container.AddService (typeof (IWindowsFormsEditorService), service);
405 UITypeEditor editor = GetEditor ();
406 if (editor != null) {
408 object value = editor.EditValue ((ITypeDescriptorContext)this,
412 return SetValue (value, out error);
413 } catch { //(Exception e) {
414 // property_grid.ShowError (e.Message + Environment.NewLine + e.StackTrace);
420 private UITypeEditor GetEditor ()
422 if (PropertyDescriptor != null) {
423 try { // can happen, because we are missing some editors
424 if (PropertyDescriptor != null)
425 return (UITypeEditor) PropertyDescriptor.GetEditor (typeof (UITypeEditor));
427 // property_grid.ShowError ("Unable to load UITypeEditor for property '" + PropertyDescriptor.Name + "'.");
433 private TypeConverter GetConverter ()
435 if (PropertyDescriptor != null)
436 return PropertyDescriptor.Converter;
440 public bool ToggleValue ()
442 if (IsReadOnly || (IsMerged && !HasMergedValue))
445 bool success = false;
447 object value = this.Value;
448 if (PropertyDescriptor.PropertyType == typeof(bool))
449 success = SetValue (!(bool)value, out error);
451 TypeConverter converter = GetConverter ();
452 if (converter != null &&
453 converter.GetStandardValuesSupported ((ITypeDescriptorContext)this)) {
454 TypeConverter.StandardValuesCollection values =
455 (TypeConverter.StandardValuesCollection) converter.GetStandardValues ((ITypeDescriptorContext)this);
456 if (values != null) {
457 for (int i = 0; i < values.Count; i++) {
458 if (value != null && value.Equals (values[i])){
459 if (i < values.Count-1)
460 success = SetValue (values[i+1], out error);
462 success = SetValue (values[0], out error);
469 if (!success && error != null)
470 property_grid.ShowError (error);
474 public bool SetValue (object value, out string error)
480 if (SetValueCore (value, out error)) {
481 InvalidateChildGridItemsCache ();
482 property_grid.OnPropertyValueChangedInternal (this, this.Value);
488 protected virtual bool SetValueCore (object value, out string error)
492 TypeConverter converter = GetConverter ();
493 Type valueType = value != null ? value.GetType () : null;
494 // if the new value is not of the same type try to convert it
495 if (valueType != null && this.PropertyDescriptor.PropertyType != null &&
496 !this.PropertyDescriptor.PropertyType.IsAssignableFrom (valueType)) {
497 bool conversionError = false;
499 if (converter != null &&
500 converter.CanConvertFrom ((ITypeDescriptorContext)this, valueType))
501 value = converter.ConvertFrom ((ITypeDescriptorContext)this,
502 CultureInfo.CurrentCulture, value);
504 conversionError = true;
505 } catch (Exception e) {
507 conversionError = true;
509 if (conversionError) {
510 string valueText = ConvertToString (value);
511 string errorShortDescription = null;
512 if (valueText != null) {
513 errorShortDescription = "Property value '" + valueText + "' of '" +
514 PropertyDescriptor.Name + "' is not convertible to type '" +
515 this.PropertyDescriptor.PropertyType.Name + "'";
518 errorShortDescription = "Property value of '" +
519 PropertyDescriptor.Name + "' is not convertible to type '" +
520 this.PropertyDescriptor.PropertyType.Name + "'";
522 error = errorShortDescription + Environment.NewLine + Environment.NewLine + error;
527 bool changed = false;
528 bool current_changed = false;
529 object[] propertyOwners = this.PropertyOwners;
530 PropertyDescriptor[] properties = PropertyDescriptors;
531 for (int i=0; i < propertyOwners.Length; i++) {
532 object currentVal = properties[i].GetValue (propertyOwners[i]);
533 current_changed = false;
534 if (!Object.Equals (currentVal, value)) {
535 if (this.ShouldCreateParentInstance) {
536 Hashtable updatedParentProperties = new Hashtable ();
537 PropertyDescriptorCollection parentProperties = TypeDescriptor.GetProperties (propertyOwners[i]);
538 foreach (PropertyDescriptor property in parentProperties) {
539 if (property.Name == properties[i].Name)
540 updatedParentProperties[property.Name] = value;
542 updatedParentProperties[property.Name] = property.GetValue (propertyOwners[i]);
544 object updatedParentValue = this.ParentEntry.PropertyDescriptor.Converter.CreateInstance (
545 (ITypeDescriptorContext)this, updatedParentProperties);
546 if (updatedParentValue != null)
547 current_changed = this.ParentEntry.SetValueCore (updatedParentValue, out error);
550 properties[i].SetValue (propertyOwners[i], value);
552 // MS seems to swallow this
554 // string valueText = ConvertToString (value);
555 // if (valueText != null)
556 // error = "Property value '" + valueText + "' of '" + properties[i].Name + "' is invalid.";
558 // error = "Property value of '" + properties[i].Name + "' is invalid.";
562 if (IsValueType (this.ParentEntry))
563 current_changed = ParentEntry.SetValueCore (propertyOwners[i], out error);
565 current_changed = Object.Equals (properties[i].GetValue (propertyOwners[i]), value);
574 private bool IsValueType (GridEntry item)
576 if (item != null && item.PropertyDescriptor != null &&
577 (item.PropertyDescriptor.PropertyType.IsValueType ||
578 item.PropertyDescriptor.PropertyType.IsPrimitive))
583 public bool ResetValue ()
586 object[] owners = this.PropertyOwners;
587 PropertyDescriptor[] properties = PropertyDescriptors;
588 for (int i=0; i < owners.Length; i++) {
589 properties[i].ResetValue (owners[i]);
590 if (IsValueType (this.ParentEntry)) {
592 if (!ParentEntry.SetValueCore (owners[i], out error) && error != null)
593 property_grid.ShowError (error);
596 property_grid.OnPropertyValueChangedInternal (this, this.Value);
602 public bool HasDefaultValue {
604 if (PropertyDescriptor != null)
605 return !PropertyDescriptor.ShouldSerializeValue (PropertyOwner);
610 // Determines if the current value can be reset
612 public virtual bool IsResetable {
613 get { return (!IsReadOnly && PropertyDescriptor.CanResetValue (PropertyOwner)); }
617 // If false the entry can be modified only by the means of a predefined values
618 // and not such inputed by the user.
620 public virtual bool IsEditable {
622 TypeConverter converter = GetConverter ();
623 if (PropertyDescriptor == null)
625 else if (PropertyDescriptor.PropertyType.IsArray)
627 else if (PropertyDescriptor.IsReadOnly && !this.ShouldCreateParentInstance)
629 else if (converter == null ||
630 !converter.CanConvertFrom ((ITypeDescriptorContext)this, typeof (string)))
632 else if (converter.GetStandardValuesSupported ((ITypeDescriptorContext)this) &&
633 converter.GetStandardValuesExclusive ((ITypeDescriptorContext)this))
640 // If true the the entry cannot be modified at all
642 public virtual bool IsReadOnly {
644 TypeConverter converter = GetConverter ();
645 if (PropertyDescriptor == null || PropertyOwner == null)
647 else if (PropertyDescriptor.IsReadOnly &&
648 (EditorStyle != UITypeEditorEditStyle.Modal || PropertyDescriptor.PropertyType.IsValueType) &&
649 !this.ShouldCreateParentInstance)
651 else if (PropertyDescriptor.IsReadOnly &&
652 TypeDescriptor.GetAttributes (PropertyDescriptor.PropertyType)
653 [typeof(ImmutableObjectAttribute)].Equals (ImmutableObjectAttribute.Yes))
655 else if (ShouldCreateParentInstance && ParentEntry.IsReadOnly)
657 else if (!HasCustomEditor && converter == null)
659 else if (converter != null &&
660 !converter.GetStandardValuesSupported ((ITypeDescriptorContext)this) &&
661 !converter.CanConvertFrom ((ITypeDescriptorContext)this,
665 } else if (PropertyDescriptor.PropertyType.IsArray && !HasCustomEditor)
672 public bool IsPassword {
675 if (PropertyDescriptor != null)
676 return PropertyDescriptor.Attributes.Contains (PasswordPropertyTextAttribute.Yes);
681 // This is a way to set readonly properties (e.g properties without a setter).
682 // The way it works is that if CreateInstance is supported by the parent's converter
683 // it gets passed a list of properties and their values which it uses to create an
684 // instance (e.g by passing them to the ctor of that object type).
686 // This is used for e.g Font
688 public virtual bool ShouldCreateParentInstance {
690 if (this.ParentEntry != null && ParentEntry.PropertyDescriptor != null) {
691 TypeConverter parentConverter = ParentEntry.GetConverter ();
692 if (parentConverter != null && parentConverter.GetCreateInstanceSupported ((ITypeDescriptorContext)this))
699 public virtual bool PaintValueSupported {
701 UITypeEditor editor = GetEditor ();
702 if (editor != null) {
704 return editor.GetPaintValueSupported ();
706 // Some of our Editors throw NotImplementedException
713 public virtual void PaintValue (Graphics gfx, Rectangle rect)
715 UITypeEditor editor = GetEditor ();
716 if (editor != null) {
718 editor.PaintValue (this.Value, gfx, rect);
720 // Some of our Editors throw NotImplementedException
726 protected void PopulateChildGridItems ()
728 grid_items = GetChildGridItemsCached ();
731 private void InvalidateChildGridItemsCache ()
733 if (child_griditems_cache != null) {
734 child_griditems_cache = null;
735 PopulateChildGridItems ();
739 private GridItemCollection GetChildGridItemsCached ()
741 if (child_griditems_cache == null) {
742 child_griditems_cache = GetChildGridItems ();
743 // foreach (GridEntry item in child_griditems_cache)
744 // PrintDebugInfo (item);
747 return child_griditems_cache;
750 // private static void PrintDebugInfo (GridEntry item)
752 // if (item.PropertyDescriptor != null) {
753 // Console.WriteLine ("=== [" + item.PropertyDescriptor.Name + "] ===");
755 // TypeConverter converter = item.GetConverter ();
756 // Console.WriteLine ("IsReadOnly: " + item.IsReadOnly);
757 // Console.WriteLine ("IsEditable: " + item.IsEditable);
758 // Console.WriteLine ("PropertyDescriptor.IsReadOnly: " + item.PropertyDescriptor.IsReadOnly);
759 // if (item.ParentEntry != null)
760 // Console.WriteLine ("ParentEntry.IsReadOnly: " + item.ParentEntry.IsReadOnly);
761 // Console.WriteLine ("ImmutableObjectAttribute.Yes: " + TypeDescriptor.GetAttributes (item.PropertyDescriptor.PropertyType)
762 // [typeof(ImmutableObjectAttribute)].Equals (ImmutableObjectAttribute.Yes));
763 // UITypeEditor editor = item.GetEditor ();
764 // Console.WriteLine ("Editor: " + (editor == null ? "none" : editor.GetType ().Name));
765 // if (editor != null)
766 // Console.WriteLine ("Editor.EditorStyle: " + editor.GetEditStyle ((ITypeDescriptorContext)item));
767 // Console.WriteLine ("Converter: " + (converter == null ? "none" : converter.GetType ().Name));
768 // if (converter != null) {
769 // Console.WriteLine ("Converter.GetStandardValuesSupported: " + converter.GetStandardValuesSupported ((ITypeDescriptorContext)item).ToString ());
770 // Console.WriteLine ("Converter.GetStandardValuesExclusive: " + converter.GetStandardValuesExclusive ((ITypeDescriptorContext)item).ToString ());
771 // Console.WriteLine ("ShouldCreateParentInstance: " + item.ShouldCreateParentInstance);
772 // Console.WriteLine ("CanConvertFrom (string): " + converter.CanConvertFrom ((ITypeDescriptorContext)item, typeof (string)));
774 // Console.WriteLine ("IsArray: " + item.PropertyDescriptor.PropertyType.IsArray.ToString ());
775 // } catch { /* Some converters and editor throw NotImplementedExceptions */ }
779 private GridItemCollection GetChildGridItems ()
781 object[] propertyOwners = this.Values;
782 string[] propertyNames = GetMergedPropertyNames (propertyOwners);
783 GridItemCollection items = new GridItemCollection ();
785 foreach (string propertyName in propertyNames) {
786 PropertyDescriptor[] properties = new PropertyDescriptor[propertyOwners.Length];
787 for (int i=0; i < propertyOwners.Length; i++)
788 properties[i] = GetPropertyDescriptor (propertyOwners[i], propertyName);
789 items.Add (new GridEntry (property_grid, properties, this));
795 private bool IsPropertyMergeable (PropertyDescriptor property)
797 if (property == null)
800 MergablePropertyAttribute attrib = property.Attributes [typeof (MergablePropertyAttribute)] as MergablePropertyAttribute;
801 if (attrib != null && !attrib.AllowMerge)
807 private string[] GetMergedPropertyNames (object [] objects)
809 if (objects == null || objects.Length == 0)
810 return new string[0];
812 ArrayList intersection = new ArrayList ();
813 for (int i = 0; i < objects.Length; i ++) {
814 if (objects [i] == null)
817 PropertyDescriptorCollection properties = GetProperties (objects[i], property_grid.BrowsableAttributes);
818 ArrayList new_intersection = new ArrayList ();
820 foreach (PropertyDescriptor currentProperty in (i == 0 ? (ICollection)properties : (ICollection)intersection)) {
821 PropertyDescriptor matchingProperty = (i == 0 ? currentProperty : properties [currentProperty.Name]);
822 if (objects.Length > 1 && !IsPropertyMergeable (matchingProperty))
824 if (matchingProperty.PropertyType == currentProperty.PropertyType)
825 new_intersection.Add (matchingProperty);
828 intersection = new_intersection;
831 string[] propertyNames = new string [intersection.Count];
832 for (int i=0; i < intersection.Count; i++)
833 propertyNames[i] = ((PropertyDescriptor)intersection[i]).Name;
835 return propertyNames;
838 private PropertyDescriptor GetPropertyDescriptor (object propertyOwner, string propertyName)
840 if (propertyOwner == null || propertyName == null)
843 PropertyDescriptorCollection properties = GetProperties (propertyOwner, property_grid.BrowsableAttributes);
844 if (properties != null)
845 return properties[propertyName];
849 private PropertyDescriptorCollection GetProperties (object propertyOwner, AttributeCollection attributes)
851 if (propertyOwner == null || property_grid.SelectedTab == null)
852 return new PropertyDescriptorCollection (null);
854 Attribute[] atts = new Attribute[attributes.Count];
855 attributes.CopyTo (atts, 0);
856 return property_grid.SelectedTab.GetProperties ((ITypeDescriptorContext)this, propertyOwner, atts);