|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
fig. 1: Using an Array
fig. 2: Using a customized collection IntroductionThe This time I focus myself on If you assign an array to the The first question here, how is it possible that an array provides its contained objects to the In this article, I will explore an answer to these questions. We develop a solution that will look like figure 2 (see above). The basicsAn object may provide custom information about itself by implementing an interface Information returned by a type descriptor contains information about a type regarding its properties, events, type conversion, design time editor, .... . If So, we already get half the way to the answer: We need to implement The property information will be returned by the interface method It is more easy than it sounds. I will demonstrate it on a sample project provided for download. A sample projectFor demonstration purpose I will use a sample project. This project is simple but it shows the overall concept. It is a Windows Forms based application. Here is the scenario for the sample. ScenarioIn a ConceptAn instance of
Fig. 3: Class diagram of Sample DesignThe domain is represented by the classes The As mentioned in the basics section, we have to implement To return custom information about properties, the
Fig. 4: Final design class diagram Let's do the implementation. Implementation - Part IImplementation of Person and EmployeeThe implementation of the Implementation of EmployeeCollectionThe implementation of public class EmployeeCollection : CollectionBase, ICustomTypeDescriptor
{
Inheriting from First, we add collection methods. // Collection methods
implementation public void Add( Employee emp )
{
this.List.Add( emp );
}
public void Remove( Employee emp )
{
this.List.Remove( emp);
}
public Employee this[ int index ]
{
get
{
return (Employee)this.List[index];
}
}
Then, we implement the interface // Implementation of ICustomTypeDescriptor:
public String GetClassName()
{
return TypeDescriptor.GetClassName(this,true);
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this,true);
}
public String GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
The only methods we implement in a custom way are the overloaded public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
public PropertyDescriptorCollection GetProperties()
{
// Create a new collection object PropertyDescriptorCollection
pds = new PropertyDescriptorCollection(null);
// Iterate the list of employees
for( int i=0; i<this.List.Count; i++ )
{
// For each employee create a property descriptor
// and add it to the
// PropertyDescriptorCollection instance
CollectionPropertyDescriptor pd = new
CollectionPropertyDescriptor(this,i);
pds.Add(pd);
}
return pds;
}
}
The implementation of Note: Only the employee list members will be visible to the
public class EmployeeCollectionPropertyDescriptor : PropertyDescriptor
{
private EmployeeCollection collection = null;
private int index = -1;
public CollectionPropertyDescriptor(EmployeeCollection coll,
int idx) : base( "#"+idx.ToString(), null )
{
this.collection = coll;
this.index = idx;
}
public override AttributeCollection Attributes
{
get
{
return new AttributeCollection(null);
}
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get
{
return this.collection.GetType();
}
}
public override string DisplayName
{
get
{
Employee emp = this.collection[index];
return emp.FirstName + " " + emp.LastName;
}
}
public override string Description
{
get
{
Employee emp = this.collection[index];
StringBuilder sb = new StringBuilder();
sb.Append(emp.LastName);
sb.Append(",");
sb.Append(emp.FirstName);
sb.Append(",");
sb.Append(emp.Age);
sb.Append(" years old, working for ");
sb.Append(emp.Department);
sb.Append(" as ");
sb.Append(emp.Role);
return sb.ToString();
}
}
public override object GetValue(object component)
{
return this.collection[index];
}
public override bool IsReadOnly
{
get { return true; }
}
public override string Name
{
get { return "#"+index.ToString(); }
}
public override Type PropertyType
{
get { return this.collection[index].GetType(); }
}
public override void ResetValue(object component) {}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
// this.collection[index] = value;
}
}
Have a look at the implementation of Implementation of OrganizationThe implementation of public class Organization
{
EmployeeCollection employees = new EmployeeCollection();
public Organization()
{
// Instantiate test data objects and fill employee collection
Employee emp1 = new Employee();
emp1.FirstName = "Max";
emp1.LastName = "Headroom";
emp1.Age = 42;
emp1.Department = "Sales";
emp1.Role = "Manager";
this.employees.Add(emp1);
Employee emp2 = new Employee();
emp2.FirstName = "Lara";
emp2.LastName = "Croft";
emp2.Age = 24;
emp2.Department = "Accounting";
emp2.Role = "Manager";
this.employees.Add(emp2);
emps[0] = emp1;
emps[1] = emp2;
}
[TypeConverter(typeof(ExpandableObjectConverter ))]
public EmployeeCollection Employees
{
get { return employees; }
}
}
An instance of public Form1()
{
InitializeComponent();
organization = new Organization();
PropertyGrid1.SelectedObject = organization;
}
The result of the implementation so far is shown in figure 5. The items are not displayed by sequence number anymore. The employees in the list are displayed by their full names. The collection is editable. If you select the value side of the employee node in the
Fig. 5: Edit collection content
Fig. 6: Standard collection editor You can add, remove or modify collection items. Implementation - Part IIIn figure 5 you see the class name ( Type converterA A type converter can be attached to a property or a class by using the We already used one type converter in the [TypeConverter(typeof(ExpandableObjectConverter ))]
public EmployeeCollection Employees
{
get { return employees; }
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Employee : Person
{
Now the
Fig. 8: Expandable Employee Nice, but the internal class EmployeeConverter : ExpandableObjectConverter
{
The class public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value, Type destType )
{
if( destType == typeof(string) && value is Employee )
{
Employee emp = (Employee)value;
return emp.Department + "," + emp.Role;
}
return base.ConvertTo(context,culture,value,destType);
}
}
We make sure that the value to be converted is of type To apply our type converter, we modify the previous use of the [TypeConverter(typeof(EmployeeConverter))]
public class Employee : Person
{
Now our custom type converter Our final result looks like figure 9 (I also added a custom type converter to
Fig. 9: Final look We have now:
SummaryWow, seems like a lot of stuff. But is really not as much if you have understood the concept. Type descriptors and member descriptors are central to provide dynamic information. You can customize the dynamic information by providing your own implementation. Here are the steps to customize display and description of collection content in a
To globalize the References | ||||||||||||||||||||