2

Say I have a particular type that I want to make available to the Windows Forms designer...

public class Style
{
    public CustomBrush Brush { get; set; }
}

And CustomBrush is implemented like so...

public abstract CustomBrush
{
    ...
}

public SolidCustomBrush : CustomBrush
{
    ...
}

public GradientCustomBrush : CustomBrush
{
    ...
}

Is there a way at design time that I can choose from any of the types derived from CustomBrush, instantiate an instance of the selected type, and modify it via the designer?

So far the only way I've though of to be able to do this is using an enum

enum BrushType
{
    Solid,
    Gradient
}

When the enum changes, so does type underlying the Brush property, but I don't like this approach...it's dirty!

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Matthew Layton
  • 39,871
  • 52
  • 185
  • 313

1 Answers1

1

As an option you can create a custom TypeConverter that provides a list of standard values to show in PropertyGrid.

A type converter can provide a list of values for a type in a Properties window control. When a type converter provides a set of standard values for a type, the value entry field for a property of the associated type in a Properties window control displays a down arrow that displays a list of values to set the value of the property to when clicked.

Since you want to be able to edit also sub properties of the CustomBrush in property grid, you should derive from ExpandableObjectConverter.

Result

enter image description here

enter image description here

enter image description here

Implementation

Create a CustomBrushConverter class and derive from ExpandableObjectConverter. Then override these methods:

using System;
using System.ComponentModel;
using System.Linq; 
class CustomBrushConverter : ExpandableObjectConverter
{
    CustomBrush[] standardValues = new CustomBrush[] { new SolidCustomBrush(), new GradientCustomBrush() };
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        return base.CanConvertFrom(context, sourceType);
    }
    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        var result = standardValues.Where(x => x.ToString() == value).FirstOrDefault();
        if (result != null)
            return result;
        return base.ConvertFrom(context, culture, value);
    }
    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
        return true;
    }
    public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
    {
        return true;
    }
    public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
    {
        return new StandardValuesCollection(standardValues);
    }
}

Then decorate the Brush property with TypeConverterAttribute this way:

public class Style /*: Component */
{
    [TypeConverter(typeof(CustomBrushConverter))]
    public CustomBrush Brush { get; set; }
}

You can override ToString method of your CustomBrush classes to provide more friendly names to show in dropdown list in PropertyGrid. For example:

public class GradientCustomBrush : CustomBrush
{
    public Color Color1 { get; set; }
    public Color Color2 { get; set; }
    public override string ToString()
    {
        return "Gradient";
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • While the answer is completely correct a downvote is really strange! – Reza Aghaei Jun 21 '16 at 18:24
  • with this answer, say for example someone designs their own brush that extends CustomBrush, can this example be used to dynamically find implementations of CustomBrush, say using reflection, and add them to the list? Would that work? – Matthew Layton Jun 21 '16 at 20:30
  • The solution is relied on `standardValues` array and instances of `CustomBrush` which it contains. So the answer is Yes. In an implementation I used `ITypeDiscoveryService` to find desired types and added them to the standard values collection. – Reza Aghaei Jun 21 '16 at 20:36
  • Okay, so I've implemented this, however there is a bit of a snag...the dropdown list is definitely along the right lines, but say for example each CustomBrush implementation has properties that I also want to manipulate via the designer (i.e. SolidCustomBrush has a Color property, and I want to set it's Color)...can this be achieved also? – Matthew Layton Jun 21 '16 at 22:31
  • It's enough to change the base class of your converter to `ExpandableObjectConverter`. Then it allows you to expand the property using `+` sign near the property and edit sub properties. You may find my answer [here](http://stackoverflow.com/a/33899479/3110834) useful. – Reza Aghaei Jun 21 '16 at 22:35
  • Would this not need to generate two properties in the designer? (1. a dropdown to select the custom implementation, and 2. to design the custom implementation)? – Matthew Layton Jun 21 '16 at 22:42
  • No it doesn't need. To test it in action, just change the base class to `ExpandableObjectConverter`. – Reza Aghaei Jun 21 '16 at 22:43
  • You know the requirements better, but take a look at [this post](http://stackoverflow.com/a/34002791/3110834) too. It's usefull for cases which you want to have some `CustomBrush` components like `Gradiend` and `Solid` and put an instance of one of them on the form and use it as `Brush` of the `Style` component. – Reza Aghaei Jun 21 '16 at 22:47
  • Hello @Reza Aghaei, I found something I can't resolve in approach above. When creating A `list` And each element can choose which Brush to choose. when create new element in CollectionEditor within WinForms designer the new value contains previous element value? How can I resolve this – deveton May 30 '21 at 19:16
  • Hi @deveton, at the moment I do not have a proper workstation to reproduce the problem; but as soon as have access to a proper workstation, I'll give it a try and will let you know if I have any idea about it; meanwhile, as an option for such editing feature: ... – Reza Aghaei May 30 '21 at 20:59
  • ... You can create a custom UiTypeEditor for your brush, which opens a custom dialog; in the dialog you can have a dropdown list (containing your brush types) and a PropertyGrid. After choosing a brush type from dropdown, create an instance of it and show in PropertyGrid and by pressing an OK button, close the dialog and set the brush instance as value of the property. (Something similar to font dialog.) – Reza Aghaei May 30 '21 at 21:01
  • Hello Reza thank you much much. I do it like you said its worked. But some little thing please how to serialize properties as beautiful shape. Like AddRange() for collections. Because designer always generate Brush1 then Brush1.XX = 110 then Brushes.Add(Brush1). How to do it directly as Brushes.AddRange(new Brush(xx){XX}) – deveton May 31 '21 at 03:07
  • Another question :) Did UiTypeEditor serialize properties to winforms designer. or TypeConverter should provided also – deveton May 31 '21 at 03:08
  • @deveton About better serialization of the code: it could be a whole new topic itself (a new question), but I guess implementing an instance descriptor, could be of help. Take a look at [this post](https://stackoverflow.com/a/55060816/3110834), at list it helps you to create the brush like b = new Brush(x,y,z) instead of setting b.X and b.Y and b.Z individually. – Reza Aghaei May 31 '21 at 11:54
  • 1
    @deveton About UiTypeEditor, it controls the editing part (like showing a modal dialog). Serialization is responsibility of the serialization-related attributes and the type converter. – Reza Aghaei May 31 '21 at 11:57
  • Hello @Reza Aghaei. sorry for that. But please the DefaultValue(typeof(class), "") never works with above scenario? How to allow DefaultValue attribute with complex-type specially above case in your answer please. I try ResetValueXX() not working and so on :( – deveton Jun 08 '21 at 08:07
  • For example How to make Brush Solid is a default value that appears grayed in PropertyGrid :( – deveton Jun 08 '21 at 08:08