Asaf
Asaf

Reputation: 4407

DataGridView changing display at runtime

I'm displaying a class with a few members (each is a class of its own) in a property grid. I have the following situation (simplified, this is not the actual design and/or classes):

public class DataType1
{
    public int Value1 { get; }
    public int Value2 { get; }
}

public class DataType2
{
    public int ValueA { get; }
    public int ValueB { get; }
}

public class DisplayedData
{
    [TypeConverter(typeof(ExpandableObjectConverter))]
    [ReadOnly(true)]
    public DataType1 Data1 { get; }

    [TypeConverter(typeof(ExpandableObjectConverter))]
    [ReadOnly(true)]
    public DataType2 Data2 { get; }
}

Each member (Data1, Data2) as you can see, is displayed as an expandable object so I can see all of its members.

Now, the problem is this: Each of these data classes is read separately from a distant source, and each read might fail (with a specific error).

I want to be able to display the composed object (DisplayedData) in the property grid, where each member is either expandable if the read was successful, or displays the error code (just a string) otherwise.

Any ideas?

Upvotes: 0

Views: 706

Answers (2)

Asaf
Asaf

Reputation: 4407

I ended up creating my own TypeConverter based on ExpandableObjectConverter as Arif suggested. My solution was somewhat simpler (I think).

I changed DataType1 and DataType2 to inherit from the same base class (let's call it BaseDataType) which holds an error code (and whether there was an error).

Then in my type converter I do something like that (again, simplified):

public class MyDynamicTypeConverter : ExpandableObjectConverter
{
    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType.Equals(typeof(string)))
        {
            BaseDataType baseDisplay = GetBaseDisplay(context);
            if (baseDisplay.ReadFailed)
            {
                // Display the error message
                return baseDisplay.ErrorMessageReadFailed;
            }
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override bool GetPropertiesSupported(ITypeDescriptorContext context)
    {
        BaseDataType baseDisplay = GetBaseDisplay(context);
        if (baseDisplay.ReadFailed)
        {
            // If read failed, do not expand the display for this object
            return false;
        }
        return base.GetPropertiesSupported(context);
    }

    private BaseDataType GetBaseDisplay(ITypeDescriptorContext context)
    {
        // Extract base data type using reflections
        object obj = context.Instance.GetType().GetProperty(context.PropertyDescriptor.Name).GetValue(context.Instance, null);
        return (BaseDataType)obj;
    }
}

That way, the converter itself queries the read data object and decides how to display it based on whether there was an error code.

Of course there's more code involved (such as setting the relevant error code when needed etc.), but that's the main idea.

Upvotes: 2

Arif Eqbal
Arif Eqbal

Reputation: 3138

I think if you implement your own TypeConverter based on ExpandableObjectConverter you can probably handle the case. For eg. say you are not able to get data for Data2 so I am assuming Data2 will be Null. You can display the property in the grid using your own converter but I was wondering how would you show a text say "Error" in front of it. Basically for a Expandable object the ToString() of the class is used to show text against it in the group.

I have a hack that you can use, might not be the most elegant solution but I hope it will work... What you can do is in your class DisplayedData add another property such that your class becomes something like this...

public class DisplayedData
{
    [TypeConverter(typeof(ExpandableObjectConverter))]
    [ReadOnly(true)]
    public DataType1 Data1 { get;  }

    [TypeConverter(typeof(ExpandableObjectConverter))]
    [ReadOnly(true)]
    [DisplayName("Data2")]
    [Browsable(true)]
    public DataType2 Data2 { get;  } //We have Browsable set to true for this

    [DisplayName("Data2")]
    [Browsable(false)]
    public string Data2Error { get;  } //An additional property with Browsable set to false
}

(The class above has not setters I was wondering how you would populate them, but then that might be a partial code)

Now when you populate data in the object of this class and you see that you are not able to read values for Data2 and so Data2 is Null. Then you write this...

 if (data.Data2 ==  null)//data is an object of DisplayedData class that we are showing in PropertyGrid
        {
            PropertyDescriptor descriptor = TypeDescriptor.GetProperties(data.GetType())["Data2"];
            BrowsableAttribute attribute = (BrowsableAttribute)descriptor.Attributes[typeof(BrowsableAttribute)];
            FieldInfo fieldToChange = attribute.GetType().GetField("Browsable",
                                  BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Public |
                                  BindingFlags.Instance);
            fieldToChange.SetValue(attribute, false);

            data.Data2Error = "Error";
            descriptor = TypeDescriptor.GetProperties(data.GetType())["Data2Error"];
            attribute = (BrowsableAttribute)descriptor.Attributes[typeof(BrowsableAttribute)];
            fieldToChange = attribute.GetType().GetField("Browsable",
                                  BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Public |
                                  BindingFlags.Instance);
            fieldToChange.SetValue(attribute, true);
        }

        propertyGrid1.SelectedObject = data; //Reassign object to PropertyGrid

Basically at run time we are trying to Hide the property Data2 and show the property Data2Error, both these properties have same Display Name so the same name is shown in the propertygrid. to do this we use reflection and get the attribute "Browsable" and set its property.

Upvotes: 2

Related Questions