Home > OS >  Control how WinForms Designer writes the InitializeComponent code for custom class property values?
Control how WinForms Designer writes the InitializeComponent code for custom class property values?

Time:05-04

I'm developing a custom user control that draws its background with a linear gradient brush. For illustration purposes, I'll use a panel as the base for the control. I've defined a ColorPair class to hold the pair of colors used for the gradient and created a property on the control which exposes a ColorPair. I've also added a ColorPairConverter class based on ExpandableObjectConverter and a ColorPairEditor class based on UITypeEditor. When adding an instance of the control to a form, the designer behaves as expected and allows selection of the two colors in the ColorPair.

I don't have any problems with functionality, but the code generated by the designer leaves something to be desired.

I'd like to see the ColorPair property value be initialized in the designer code similar to the way Point valies are initialized.

A point property on a control is initialized in one clear to understand line:

this.gradientPanel1.Location = new System.Drawing.Point(77, 42);

But the ColorPair property uses the default parameterless constructor and later assigns property values individually:

    private void InitializeComponent()
    {
        WindowsFormsApp2.ColorPair colorPair1 = new WindowsFormsApp2.ColorPair();
        this.gradientPanel1 = new WindowsFormsApp2.GradientPanel();
        this.SuspendLayout();
        // 
        // gradientPanel1
        // 
        colorPair1.ColorA = System.Drawing.Color.Red;
        colorPair1.ColorB = System.Drawing.Color.Yellow;
        this.gradientPanel1.GradientBackColor = colorPair1;
        this.gradientPanel1.Location = new System.Drawing.Point(77, 42);
        this.gradientPanel1.Name = "gradientPanel1";
        this.gradientPanel1.Size = new System.Drawing.Size(469, 313);
        this.gradientPanel1.TabIndex = 0;
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(800, 450);
        this.Controls.Add(this.gradientPanel1);
        this.Name = "Form1";
        this.Text = "Form1";
        this.ResumeLayout(false);

    }

Notice the lines

        WindowsFormsApp2.ColorPair colorPair1 = new WindowsFormsApp2.ColorPair();

        colorPair1.ColorA = System.Drawing.Color.Red;
        colorPair1.ColorB = System.Drawing.Color.Yellow;
        this.gradientPanel1.GradientBackColor = colorPair1;

But I would much rather prefer to see this:

    private void InitializeComponent()
    {
        this.gradientPanel1 = new WindowsFormsApp2.GradientPanel();
        this.SuspendLayout();
        // 
        // gradientPanel1
        // 
        this.gradientPanel1.GradientBackColor = new ColorPair("Red;Yellow");
        this.gradientPanel1.Location = new System.Drawing.Point(77, 42);
        this.gradientPanel1.Name = "gradientPanel1";
        this.gradientPanel1.Size = new System.Drawing.Size(469, 313);
        this.gradientPanel1.TabIndex = 0;
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(800, 450);
        this.Controls.Add(this.gradientPanel1);
        this.Name = "Form1";
        this.Text = "Form1";
        this.ResumeLayout(false);
    }

Notice the one line:

        this.gradientPanel1.GradientBackColor = new ColorPair("Red;Yellow");

How to I control how the custom property value is serialized in the designer so I don't get 4 lines of code for every use of this property and make it easier to trace when simply looking at the designer code?

Here are the relevant clases involved:

public class ColorPair
{
    public ColorPair() { }

    public ColorPair(Color ColorA, Color ColorB)
    {
        colorA = ColorA;
        colorB = ColorB;
    }

    public ColorPair(string valueString) : this(valueString.Split(';')[0], valueString.Split(';')[1]) { }

    public ColorPair(string colorStringA, string colorStringB)
    {
        ColorConverter converter = new ColorConverter();
        this.colorA = (Color)converter.ConvertFromString(colorStringA);
        this.colorB = (Color)converter.ConvertFromString(colorStringB);
    }

    private Color colorA = Color.Blue;
    public Color ColorA
    {
        get { return this.colorA; }
        set { this.colorA = value; }
    }

    private Color colorB = Color.White;
    public Color ColorB
    {
        get { return this.colorB; }
        set { this.colorB = value; }
    }

    public override string ToString()
    {
        return string.Format("{0};{1}", GetColorString(colorA), GetColorString(colorB));
    }

    public static string GetColorString(Color color)
    {
        ColorConverter converter = new ColorConverter();
        return converter.ConvertToString(color);
    }

    public override bool Equals(object obj)
    {
        return (ToString() ?? "") == (((ColorPair)obj).ToString() ?? "");
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

public partial class ColorPairConverter : ExpandableObjectConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (ReferenceEquals(sourceType, typeof(string)))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
        {
            return new ColorPair((string)value);
        }
        return base.ConvertFrom(context, culture, value);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(InstanceDescriptor))
        {
            return true;
        }
        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(InstanceDescriptor))
        {
            var ci = typeof(ColorPair).GetConstructor(new Type[] { typeof(string) });
            ColorPair i = (ColorPair)value;
            return new InstanceDescriptor(ci, new object[] { i.ToString() });
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
    {
        if (propertyValues is null)
        {
            throw new ArgumentNullException(nameof(propertyValues));
        }
        Color colorA = (Color)propertyValues[nameof(ColorPair.ColorA)];
        Color colorB = (Color)propertyValues[nameof(ColorPair.ColorB)];
        return new ColorPair(colorA, colorB);
    }
}

public class ColorPairEditor : UITypeEditor
{
    public override bool GetPaintValueSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override void PaintValue(PaintValueEventArgs e)
    {
        // Erase the area.
        e.Graphics.FillRectangle(Brushes.White, e.Bounds);
        ColorPair colorPair;
        if (e.Context == null)
        {
            colorPair = new ColorPair();
        }
        else
        {
            colorPair = (ColorPair)e.Value;
        }
        // Draw the example.
        using (var br = new LinearGradientBrush(e.Bounds, colorPair.ColorA, colorPair.ColorB, LinearGradientMode.Horizontal))
        {
            e.Graphics.FillRectangle(br, e.Bounds);
        }
        using (var border_pen = new Pen(Color.Black, 1f))
        {
            e.Graphics.DrawRectangle(border_pen, 1, 1, e.Bounds.Width - 1, e.Bounds.Height - 1);
        }
    }
}
public partial class GradientPanel : Panel
{
    public GradientPanel()
    {
        InitializeComponent();
    }

    private ColorPair gradientBackColor = new ColorPair("Navy; White");
    [Category("Appearance")]
    [Description("Gradient Back Color")]
    [Editor(typeof(ColorPairEditor), typeof(UITypeEditor))]
    [TypeConverter(typeof(ColorPairConverter))]
    [DefaultValue(typeof(ColorPair), "Navy; White")]
    public ColorPair GradientBackColor
    {
        get { return this.gradientBackColor; }
        set
        {
            this.gradientBackColor = value;
            Invalidate();
        }
    }

    protected override void OnPaintBackground(PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        Rectangle r = this.ClientRectangle;
        // Background
        using (var b = new LinearGradientBrush(r, this.gradientBackColor.ColorB, this.gradientBackColor.ColorA, 90F))
        {
            g.FillRectangle(b, r);
        }
    }
}

CodePudding user response:

I believe you can get this working the way you would like by making three changes.

  1. Remove the TypeConverter attribute from the GradientBackColor property of your UserControl GradientColor. The implementation now looks like:
[Category("Appearance")]
[Description("Gradient Back Color")]
[Editor(typeof(ColorPairEditor), typeof(UITypeEditor))]
//[TypeConverter(typeof(ColorPairConverter))]
[DefaultValue(typeof(ColorPair), "Navy; White")]
public ColorPair GradientBackColor
{
    get { return this.gradientBackColor; }
    set
    {
        this.gradientBackColor = value;
        Invalidate();    
    }
}
  1. Add the [TypeConverter(typeof(ColorPairConverter))] attribute to the class ColorPair.
[TypeConverter(typeof(ColorPairConverter))]
public class ColorPair
{
...

}
  1. The two functions CanConvertTo and ConvertTo in the class ColorPairConverter were updated to contain:
if (destinationType == typeof(InstanceDescriptor) ||
    destinationType == typeof(String))
{
...
}

With these changes and after re-adding the control to the form, I was able to get the designer to produce this:

enter image description here

  • Related