Search code examples
c#.netwpfdevexpresspropertygrid

DevExpress WPF PropertyGridControl: Referencing objects in another part of the bound object


I have a complex object that I'm trying to display in a DevExpress WPF PropertyGridControl. This object has other objects nested within it. Some of the nested objects refer to other nested objects, and I want to maintain the referential link between them (i.e. I don't want to resort to strings or ids to link the objects). Here is a contrived example of the object hierarchy I'm dealing with:

public class OrderSystem
{
    public List<Customer> Customers { get; set; }
    public List<Order> Orders { get; set; }
}

public class Customer
{
    public string Name { get; set; }

    public override string ToString() => Name;
}

public class Order
{
    public Customer Buyer { get; set; }
}

The path I've gone down to resolve this is to decorate the Order's Buyer property with a custom type converter that decides what the available customers are from the other part of the object:

public class Order
{
    [TypeConverter(typeof(AvailableCustomersTypeConverter))]
    public Customer Buyer { get; set; }
}

The custom type converter code looks like this:

    public class AvailableCustomersTypeConverter : TypeConverter
    {
        public static List<Customer> AvailableCustomers { get; set; }

        public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            var customers = AvailableCustomers ?? new List<Customer>();
            var availableNames = AvailableCustomers.Select(c => c.Name).ToList();
            return new StandardValuesCollection(availableNames);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) => DoConvert(value);

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) => DoConvert(value);

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => destinationType == typeof(string) || destinationType == typeof(Customer);

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string) || sourceType == typeof(Customer);

        private object DoConvert(object value)
        {
            if (value is string name)
            {
                if (AvailableCustomers != null)
                {
                    return AvailableCustomers.FirstOrDefault(c => c.Name == name);
                }
                else
                {
                    return string.Empty;
                }
            }
            else if (value is Customer customer)
            {
                return customer.Name;
            }
            else
            {
                throw new NotSupportedException($"{value.GetType().FullName}");
            }
        }
    }

The static List<Customer> AvailableCustomers property gets set when a new OrderSystem is loaded, or when the OrderSystem's Customers collection changes. I had to make this static so that the AvailableCustomersTypeConverter can access it since it is contained within an attribute.

This works great in that it does limit the customers to the ones available in the other part of the object.

Selecting an available customer

However, once the content loses focus, the selection becomes blank:

Customer selected

Customer disappears when loses focus

Note that I have confirmed that the actual object reference is still there. The reference is not broken, it just ceases to display properly in the UI.

I am trying to figure out how to get the selected Customer to display properly even after the content cell loses focus. Does anyone have any ideas?


Solution

  • I figured out the issue, which stems from my erroneous idea that the Customer objects needed to get converted back and forth from strings (their name), since I figured that's what the dropdown box would be displaying. However since I created a ToString() override on the Customer object (which displays the Name), this was not necessary.

    When I (or rather my co-worker) changed the GetStandardValues method in the AvailableCustomersTypeConverter to return the list of actual Customer objects instead of just their names, then suddenly things started working.

    Also, because I'm returning the Customer objects directly, I do not need all of these conversion methods. The updated AvailableCustomersTypeConverter code looks like this:

    public class AvailableCustomersTypeConverter : TypeConverter
    {
        public static List<Customer> AvailableCustomers { get; set; }
    
        public override bool GetPropertiesSupported(ITypeDescriptorContext context) => true;
    
        public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true;
    
        public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) => true;
    
        public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            return new StandardValuesCollection(AvailableCustomers ?? new List<Customer>());
        }
    }