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;
38 namespace System.Windows.Forms.PropertyGridInternal
40 internal class GridEntry : GridItem, ITypeDescriptorContext
42 #region Local Variables
43 private PropertyGrid property_grid;
44 private bool expanded;
45 private GridItemCollection grid_items;
46 private GridItem parent;
47 private PropertyDescriptor[] property_descriptors;
49 private Rectangle plus_minus_bounds;
50 #endregion // Local Variables
53 protected GridEntry (PropertyGrid propertyGrid, GridEntry parent)
55 if (propertyGrid == null)
56 throw new ArgumentNullException ("propertyGrid");
57 property_grid = propertyGrid;
58 plus_minus_bounds = new Rectangle (0,0,0,0);
60 grid_items = new GridItemCollection ();
65 // Cannot use one PropertyDescriptor for all owners, because the
66 // propertydescriptors might have different Invokees. Check
67 // ReflectionPropertyDescriptor.GetInvokee and how it's used.
69 public GridEntry (PropertyGrid propertyGrid, PropertyDescriptor[] properties,
70 GridEntry parent) : this (propertyGrid, parent)
72 if (properties == null || properties.Length == 0)
73 throw new ArgumentNullException ("prop_desc");
74 property_descriptors = properties;
76 #endregion // Constructors
79 public override bool Expandable {
80 get { return grid_items.Count > 0; }
83 public override bool Expanded {
84 get { return expanded; }
86 if (expanded != value) {
89 property_grid.OnExpandItem (this);
91 property_grid.OnCollapseItem (this);
96 public override GridItemCollection GridItems {
97 get { return grid_items; }
100 public override GridItemType GridItemType {
101 get { return GridItemType.Property; }
104 public override string Label {
106 PropertyDescriptor property = this.PropertyDescriptor;
107 if (property != null) {
108 string label = property.DisplayName;
109 ParenthesizePropertyNameAttribute parensAttr =
110 property.Attributes[typeof (ParenthesizePropertyNameAttribute)] as ParenthesizePropertyNameAttribute;
111 if (parensAttr != null && parensAttr.NeedParenthesis)
112 label = "(" + label + ")";
119 public override GridItem Parent {
120 get { return parent; }
123 public GridEntry ParentEntry {
125 if (parent != null && parent.GridItemType == GridItemType.Category)
126 return parent.Parent as GridEntry;
127 return parent as GridEntry;
131 public override PropertyDescriptor PropertyDescriptor {
132 get { return property_descriptors != null ? property_descriptors[0] : null; }
135 public PropertyDescriptor[] PropertyDescriptors {
136 get { return property_descriptors; }
139 public object PropertyOwner {
141 object[] owners = PropertyOwners;
148 public object[] PropertyOwners {
150 if (ParentEntry != null)
151 return ParentEntry.Values;
156 // true if the value is the same among all properties
157 public bool HasMergedValue {
162 object[] values = this.Values;
163 for (int i=0; i+1 < values.Length; i++) {
164 if (!Object.Equals (values[i], values[i+1]))
171 public virtual bool IsMerged {
172 get { return (PropertyDescriptors != null && PropertyDescriptors.Length > 1); }
175 // If IsMerged this will return all values for all properties in all owners
176 public virtual object[] Values {
178 if (PropertyDescriptor == null || this.PropertyOwners == null)
181 object[] owners = this.PropertyOwners;
182 PropertyDescriptor[] properties = PropertyDescriptors;
183 object[] values = new object[owners.Length];
184 for (int i=0; i < owners.Length; i++)
185 values[i] = properties[i].GetValue (owners[i]);
188 return new object[] { this.Value };
193 // Returns the first value for the first propertyowner and propertydescriptor
195 public override object Value {
197 if (PropertyDescriptor == null || PropertyOwner == null)
200 return PropertyDescriptor.GetValue (PropertyOwner);
204 public string ValueText {
208 text = ConvertToString (this.Value);
218 public override bool Select ()
220 property_grid.SelectedGridItem = this;
224 #region ITypeDescriptorContext
225 void ITypeDescriptorContext.OnComponentChanged ()
229 bool ITypeDescriptorContext.OnComponentChanging ()
234 IContainer ITypeDescriptorContext.Container {
236 if (PropertyOwner == null)
239 IComponent component = property_grid.SelectedObject as IComponent;
240 if (component != null && component.Site != null)
241 return component.Site.Container;
246 object ITypeDescriptorContext.Instance {
247 get { return PropertyOwner; }
250 PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor {
251 get { return PropertyDescriptor; }
255 #region IServiceProvider Members
257 object IServiceProvider.GetService (Type serviceType) {
258 IComponent selectedComponent = property_grid.SelectedObject as IComponent;
259 if (selectedComponent != null && selectedComponent.Site != null)
260 return selectedComponent.Site.GetService (serviceType);
274 internal Rectangle PlusMinusBounds {
275 get { return plus_minus_bounds; }
276 set { plus_minus_bounds = value; }
279 public void SetParent (GridItem parent)
281 this.parent = parent;
284 public ICollection AcceptedValues {
286 if (PropertyDescriptor != null && PropertyDescriptor.Converter != null &&
287 PropertyDescriptor.Converter.GetStandardValuesSupported ()) {
288 ArrayList values = new ArrayList ();
289 string stringVal = null;
290 ICollection standardValues = PropertyDescriptor.Converter.GetStandardValues ();
291 if (standardValues != null) {
292 foreach (object value in standardValues) {
293 stringVal = ConvertToString (value);
294 if (stringVal != null)
295 values.Add (stringVal);
298 return values.Count > 0 ? values : null;
304 private string ConvertToString (object value)
307 return (string)value;
309 if (PropertyDescriptor != null && PropertyDescriptor.Converter != null &&
310 PropertyDescriptor.Converter.CanConvertTo (typeof (string))) {
312 return PropertyDescriptor.Converter.ConvertToString (value);
314 // XXX: Happens too often...
315 // property_grid.ShowError ("Property value of '" + property_descriptor.Name + "' is not convertible to string.");
323 public bool HasCustomEditor {
324 get { return GetEditor() != null; }
327 public UITypeEditorEditStyle EditorStyle {
329 UITypeEditor editor = GetEditor ();
330 if (editor != null) {
332 return editor.GetEditStyle ((ITypeDescriptorContext)this);
334 // Some of our Editors throw NotImplementedException
337 return UITypeEditorEditStyle.None;
341 public bool EditorResizeable {
344 if (this.EditorStyle == UITypeEditorEditStyle.DropDown) {
345 UITypeEditor editor = GetEditor ();
346 if (editor != null && editor.IsDropDownResizable)
354 public bool EditValue (IWindowsFormsEditorService service)
357 throw new ArgumentNullException ("service");
359 IServiceContainer parent = ((ITypeDescriptorContext)this).GetService (typeof (IServiceContainer)) as IServiceContainer;
360 ServiceContainer container = null;
363 container = new ServiceContainer (parent);
365 container = new ServiceContainer ();
367 container.AddService (typeof (IWindowsFormsEditorService), service);
369 UITypeEditor editor = GetEditor ();
370 if (editor != null) {
372 object value = editor.EditValue ((ITypeDescriptorContext)this,
376 return SetValue (value, out error);
377 } catch { //(Exception e) {
378 // property_grid.ShowError (e.Message + Environment.NewLine + e.StackTrace);
384 private UITypeEditor GetEditor ()
386 if (PropertyDescriptor != null) {
387 try { // can happen, because we are missing some editors
388 return PropertyDescriptor.GetEditor (typeof (UITypeEditor)) as UITypeEditor;
390 // property_grid.ShowError ("Unable to load UITypeEditor for property '" + PropertyDescriptor.Name + "'.");
396 public bool ToggleValue ()
398 if (IsReadOnly || (IsMerged && !HasMergedValue))
401 bool success = false;
403 object value = this.Value;
404 if (PropertyDescriptor.PropertyType == typeof(bool))
405 success = SetValue (!(bool)value, out error);
406 else if (PropertyDescriptor.Converter != null &&
407 PropertyDescriptor.Converter.GetStandardValuesSupported ()) {
408 TypeConverter.StandardValuesCollection values =
409 (TypeConverter.StandardValuesCollection) PropertyDescriptor.Converter.GetStandardValues();
410 if (values != null) {
411 for (int i = 0; i < values.Count; i++) {
412 if (value != null && value.Equals (values[i])){
413 if (i < values.Count-1)
414 success = SetValue (values[i+1], out error);
416 success = SetValue (values[0], out error);
422 if (!success && error != null)
423 property_grid.ShowError (error);
427 public bool SetValue (object value, out string error)
433 if (SetValueCore (value, out error)) {
434 property_grid.OnPropertyValueChangedInternal (this, this.Value);
440 protected virtual bool SetValueCore (object value, out string error)
444 TypeConverter converter = PropertyDescriptor.Converter;
445 Type valueType = value != null ? value.GetType () : null;
446 // if the new value is not of the same type try to convert it
447 if (valueType != null && this.PropertyDescriptor.PropertyType != null &&
448 !this.PropertyDescriptor.PropertyType.IsAssignableFrom (valueType)) {
449 bool conversionError = false;
451 if (converter != null &&
452 converter.CanConvertFrom (valueType))
453 value = converter.ConvertFrom (value);
455 conversionError = true;
457 conversionError = true;
459 if (conversionError) {
460 string valueText = ConvertToString (value);
461 if (valueText != null) {
462 error = "Property value '" + valueText + "' of '" +
463 PropertyDescriptor.Name + "' is not convertible to type '" +
464 this.PropertyDescriptor.PropertyType.Name + "'";
467 error = "Property value of '" +
468 PropertyDescriptor.Name + "' is not convertible to type '" +
469 this.PropertyDescriptor.PropertyType.Name + "'";
475 bool changed = false;
476 bool current_changed = false;
477 object[] propertyOwners = this.PropertyOwners;
478 PropertyDescriptor[] properties = PropertyDescriptors;
479 for (int i=0; i < propertyOwners.Length; i++) {
480 object currentVal = properties[i].GetValue (propertyOwners[i]);
481 current_changed = false;
482 if (!Object.Equals (currentVal, value)) {
483 if (this.ShouldCreateParentInstance) {
484 Hashtable updatedParentProperties = new Hashtable ();
485 PropertyDescriptorCollection parentProperties = TypeDescriptor.GetProperties (propertyOwners[i]);
486 foreach (PropertyDescriptor property in parentProperties) {
487 if (property.Name == properties[i].Name)
488 updatedParentProperties[property.Name] = value;
490 updatedParentProperties[property.Name] = property.GetValue (propertyOwners[i]);
492 object updatedParentValue = this.ParentEntry.PropertyDescriptor.Converter.CreateInstance (updatedParentProperties);
493 if (updatedParentValue != null)
494 current_changed = this.ParentEntry.SetValueCore (updatedParentValue, out error);
497 properties[i].SetValue (propertyOwners[i], value);
499 // MS seems to swallow this
501 // string valueText = ConvertToString (value);
502 // if (valueText != null)
503 // error = "Property value '" + valueText + "' of '" + properties[i].Name + "' is invalid.";
505 // error = "Property value of '" + properties[i].Name + "' is invalid.";
509 if (IsValueType (this.ParentEntry))
510 current_changed = ParentEntry.SetValueCore (propertyOwners[i], out error);
512 current_changed = Object.Equals (properties[i].GetValue (propertyOwners[i]), value);
514 // restore original value if doesn't get set
515 if (!current_changed && !PropertyDescriptor.IsReadOnly)
516 properties[i].SetValue (propertyOwners[i], currentVal);
524 private bool IsValueType (GridEntry item)
526 if (item != null && item.PropertyDescriptor != null &&
527 (item.PropertyDescriptor.PropertyType.IsValueType ||
528 item.PropertyDescriptor.PropertyType.IsPrimitive))
533 public bool ResetValue ()
536 object[] owners = this.PropertyOwners;
537 PropertyDescriptor[] properties = PropertyDescriptors;
538 for (int i=0; i < owners.Length; i++) {
539 properties[i].ResetValue (owners[i]);
540 if (IsValueType (this.ParentEntry)) {
542 if (!ParentEntry.SetValueCore (owners[i], out error) && error != null)
543 property_grid.ShowError (error);
546 property_grid.OnPropertyValueChangedInternal (this, this.Value);
552 public bool HasDefaultValue {
554 if (PropertyDescriptor != null &&
555 PropertyDescriptor.Attributes[typeof (DefaultValueAttribute)] != null)
561 // Determines if the current value can be reset
563 public virtual bool IsResetable {
564 get { return (!IsReadOnly && PropertyDescriptor.CanResetValue (PropertyOwner)); }
568 // If false the entry can be modified only by the means of a predefined values
569 // and not such inputed by the user.
571 public virtual bool IsEditable {
573 if (PropertyDescriptor == null)
575 else if (PropertyDescriptor.PropertyType.IsArray)
577 else if (PropertyDescriptor.IsReadOnly && this.ShouldCreateParentInstance)
579 else if (PropertyDescriptor.Converter == null ||
580 !PropertyDescriptor.Converter.CanConvertFrom (this, typeof (string)))
582 else if (PropertyDescriptor.Converter.GetStandardValuesSupported () &&
583 PropertyDescriptor.Converter.GetStandardValuesExclusive ())
590 // If true the the entry cannot be modified at all
592 public virtual bool IsReadOnly {
594 // if (PropertyDescriptor != null) {
595 // Console.WriteLine ("=== [" + PropertyDescriptor.Name + "]");
596 // Console.WriteLine ("PropertyDescriptor.IsReadOnly: " + PropertyDescriptor.IsReadOnly);
597 // Console.WriteLine ("Editor: " + (GetEditor () == null ? "none" : GetEditor ().GetType ().Name));
598 // Console.WriteLine ("Converter: " + (PropertyDescriptor.Converter == null ? "none" : PropertyDescriptor.Converter.GetType ().Name));
599 // Console.WriteLine ("Converter.GetStandardValuesSupported: " + PropertyDescriptor.Converter.GetStandardValuesSupported ().ToString ());
600 // Console.WriteLine ("Converter.GetStandardValuesExclusive: " + PropertyDescriptor.Converter.GetStandardValuesExclusive ().ToString ());
601 // Console.WriteLine ("ShouldCreateParentInstance: " + this.ShouldCreateParentInstance);
602 // Console.WriteLine ("CanConvertFrom (string): " + PropertyDescriptor.Converter.CanConvertFrom ((ITypeDescriptorContext)this, typeof (string)));
603 // Console.WriteLine ("IsArray: " + PropertyDescriptor.PropertyType.IsArray.ToString ());
605 if (ParentEntry != null && ParentEntry.GridItemType == GridItemType.Property
606 && ParentEntry.IsReadOnly)
608 else if (PropertyDescriptor == null || PropertyOwner == null ||
609 (PropertyDescriptor.IsReadOnly && !this.ShouldCreateParentInstance))
611 else if (!HasCustomEditor && PropertyDescriptor.Converter == null)
613 else if (PropertyDescriptor.Converter != null &&
614 !PropertyDescriptor.Converter.GetStandardValuesSupported () &&
615 !PropertyDescriptor.Converter.CanConvertFrom ((ITypeDescriptorContext)this,
619 } else if (PropertyDescriptor.PropertyType.IsArray && !HasCustomEditor)
626 public bool IsPassword {
629 if (PropertyDescriptor != null)
630 return PropertyDescriptor.Attributes.Contains (PasswordPropertyTextAttribute.Yes);
635 // This is a way to set readonly properties (e.g properties without a setter).
636 // The way it works is that if CreateInstance is supported by the parent's converter
637 // it gets passed a list of properties and their values which it uses to create an
638 // instance (e.g by passing them to the ctor of that object type).
640 // This is used for e.g Font
642 public virtual bool ShouldCreateParentInstance {
644 if (this.ParentEntry != null && ParentEntry.PropertyDescriptor != null) {
645 TypeConverter parentConverter = Parent.PropertyDescriptor.Converter;
646 if (parentConverter != null && parentConverter.GetCreateInstanceSupported ((ITypeDescriptorContext)this))
653 public virtual bool PaintValueSupported {
655 UITypeEditor editor = GetEditor ();
656 if (editor != null) {
658 return editor.GetPaintValueSupported ();
660 // Some of our Editors throw NotImplementedException
667 public virtual void PaintValue (Graphics gfx, Rectangle rect)
669 UITypeEditor editor = GetEditor ();
670 if (editor != null) {
672 editor.PaintValue (this.Value, gfx, rect);
674 // Some of our Editors throw NotImplementedException