This project is read-only.

Hack: Expand Nested Objects

Aug 13, 2009 at 7:01 AM
Edited Aug 13, 2009 at 8:53 PM


WPF Propertygrid is very well written and modular... once you understand how it work it's easy to do some modification.
I'll use that thread to post some of those I have made.

Here i'll show you how to use the system already in place for category in order to create expandable nested object with minimum changes to the code.
Those expandable object have their own template so you can skin them differently than category without problem


>>>>>>> Property.cs
>>>>>>> Change protection to:


protected object _instance;
protected PropertyDescriptor _property;


>>>>>>>> Implement support for displayname

public string Name
{
get { return _property.DisplayName??_property.Name; }
}


>>>>>>>> Create class
ExpandableProperty In Data

namespace Deepforest.WPF.Controls.Data
{
    class ExpandableProperty:Property
    {
        private PropertyCollection _propertyCollection;
       
        public ExpandableProperty(object instance, PropertyDescriptor property):base(instance,property)
        {
            
        }

        public ObservableCollection<Item> Items
        {
            get {
                
                if(_propertyCollection==null) {
                    //Lazy initialisation prevent from deep search and looping
                     _propertyCollection = new PropertyCollection(_property.GetValue( _instance ),true);
                }
            
               return _propertyCollection.Items;
            }
        }
 
    }
}


>>>>>>>> Add support for custom type definition in property.cs

	public Property(object instance, PropertyDescriptor property)
	{
            if (instance is ICustomTypeDescriptor) {
                this._instance = ((ICustomTypeDescriptor) instance).GetPropertyOwner(property);
            }else {
                this._instance = instance;
            }

	        this._property = property;

		this._property.AddValueChanged(_instance, instance_PropertyChanged);
	}


>>>>>>>>>> Create template for this Expanable property in Default.xaml
>>>>>>>>>> Search for <DataTemplate DataType="{x:Type data:PropertyCategory}">
>>>>>>>>>> and put this after </DataTemplate>


<DataTemplate DataType="{x:Type data:ExpandableProperty}">
<Expander Header="{Binding Name}" IsExpanded="False">
<ItemsControl ItemsSource="{Binding Path=Items}"/>
</Expander>
</DataTemplate>


>>>>>>>> Now hook PropertyCollection.CollectProperties to add your expandable property

if (descriptor.Attributes[typeof(FlatAttribute)] == null) {



if (descriptor.IsBrowsable)
{


//The proper way of doing thing would be to compare //descriptor.Converter with //TypeConverter(typeof (ExpandableObjectConverter)) // // However this logic here allow to expand automaticly nested objects // Type propertyType = descriptor.PropertyType; if(propertyType.IsClass && !propertyType.IsArray && propertyType != typeof(string)) {

//Use expandable property propertyCollection.Add(new ExpandableProperty(instance, descriptor));

}else {

//Use normal property propertyCollection.Add(new Property(instance, descriptor););
}


}
}


>>>>>>>>> Add the option to remove category and a constructor to keep validity with the old signature

public PropertyCollection(object instance):this(instance,false)
{ }

public PropertyCollection(object instance, bool noCategory)
{
// Original code } >>>>>>>> Logic for no category

>>>> Look for foreach (PropertyDescriptor propertyDescriptor in properties)
{
CollectProperties(instance, propertyDescriptor, propertyCollection);
}


>>>> After, add this
if (noCategory) {

foreach (Property property in propertyCollection) {
Items.Add(property);
}

}
else{

//Normal code }

 

Aug 13, 2009 at 7:29 AM

This next hack will implement sorting in the propertygrid

Work best if you implemented the display name of previous post
 

>>>>> In property.cs


public class Property : Item, IDisposable{


     //Original code

       private class ByCategoryThenByNameComparer: IComparer<Property> {
            
            public int Compare(Property x, Property y) {
                if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return 0;
                if (ReferenceEquals(x, y)) return 0;
                int val = x.Category.CompareTo(y.Category);
                if(val==0) return x.Name.CompareTo(y.Name);
                return val;
            }
        }

        public readonly static IComparer<Property> CompareByCategoryThenByName = new ByCategoryThenByNameComparer();

}


>>>>>>>>> In PropertyCollection Constructor

>>>>>> After


             foreach (PropertyDescriptor propertyDescriptor in properties)
            {
                CollectProperties(instance, propertyDescriptor, propertyCollection);
            }



>>>>> Insert

            propertyCollection.Sort(Property.CompareByCategoryThenByName);

Aug 13, 2009 at 4:11 PM

Fix to name truncating:

Per Category (and nested object) name autosizing:

<DataTemplate DataType="{x:Type data:PropertyCategory}"   >
        <Expander Header="{Binding Category}" IsExpanded="True" Grid.IsSharedSizeScope="True">
            <ItemsControl ItemsSource="{Binding Path=Items}" />
        </Expander>
    </DataTemplate>

    <DataTemplate DataType="{x:Type data:ExpandableProperty}" >
        <Expander Header="{Binding Name}" IsExpanded="False" Grid.IsSharedSizeScope="True" >
            <ItemsControl ItemsSource="{Binding Path=Items}"  />
        </Expander>
    </DataTemplate>

    <!-- Property -->
    
    <DataTemplate DataType="{x:Type data:Property}">
        <Grid Margin="4" Width="Auto" HorizontalAlignment="Stretch">
            <Grid.RowDefinitions/>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="PropertyGridItemName"/>
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0"  Margin="0,0,8,0" TextAlignment="Right" VerticalAlignment="Center" Text="{Binding Mode=OneTime, Path=Name}" />
            <ContentControl Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" Content="{Binding Mode=OneWay}" ContentTemplateSelector="{StaticResource propertyTemplateSelector}" />
        </Grid>
    </DataTemplate>

Oct 28, 2009 at 4:44 AM

Expender is not working for this case.

 public class Person
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public IList<string> ListOfString { get; set; }
        public string[] StringArray { get; set; }
        public List<Person> Children { get; set; }
    }

 


 Person = new Person()
            {
                ID = 1,
                Name = "Mike",
                ListOfString = new List<string> { "S1", "S2", "S3" },
                StringArray = new string[3] { "SA1", "SA2", "SA3" },
                Children = new List<Person> {
                     new Person() { Name = "P1" },
                     new Person() { Name = "P1" }
                 }
            };