控制 WinForms 设计器如何为自定义 class 属性 值编写 InitializeComponent 代码?

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

我正在开发一个自定义用户控件,它使用线性渐变画笔绘制其背景。出于说明目的,我将使用一个面板作为控件的基础。 我已经定义了一个 ColorPair class 来保存用于渐变的一对颜色,并在显示 ColorPair 的控件上创建了一个 属性。 我还添加了一个基于 ExpandableObjectConverter 的 ColorPairConverter class 和一个基于 UITypeEditor 的 ColorPairEditor class。 将控件实例添加到窗体时,设计器的行为符合预期,并允许在 ColorPair 中选择两种颜色。

我对功能没有任何问题,但设计师生成的代码还有一些不足之处。

我希望看到 ColorPair 属性 值在设计器代码中被初始化,类似于初始化点值的方式。

控件上的一个点 属性 在一个清晰易懂的行中初始化:

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

但是 ColorPair 属性 使用默认的无参数构造函数,然后单独分配 属性 值:

    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);

    }

注意线条

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

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

但我更愿意看到这个:

    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);
    }

注意一行:

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

如何控制自定义 属性 值在设计器中的序列化方式,这样我就不会在每次使用此 属性 时都得到 4 行代码并使只看设计器代码更容易追溯?

涉及的相关类如下:

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);
        }
    }
}

我相信您可以通过进行三处更改来按照您希望的方式运行。

  1. 从用户控件 GradientColorGradientBackColor 属性 中删除 TypeConverter 属性].实施现在看起来像:
[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. [TypeConverter(typeof(ColorPairConverter))] 属性添加到 class ColorPair.
[TypeConverter(typeof(ColorPairConverter))]
public class ColorPair
{
...

}
  1. CanConvertToConvertTo中的两个函数classColorPairConverter是更新为包含:
if (destinationType == typeof(InstanceDescriptor) ||
    destinationType == typeof(String))
{
...
}

通过这些更改并在 re-adding 对表单进行控制后,我能够让设计人员生成此内容: