控制 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);
}
}
}
我相信您可以通过进行三处更改来按照您希望的方式运行。
- 从用户控件 GradientColor 的 GradientBackColor 属性 中删除 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();
}
}
- 将 [TypeConverter(typeof(ColorPairConverter))] 属性添加到 class ColorPair.
[TypeConverter(typeof(ColorPairConverter))]
public class ColorPair
{
...
}
- CanConvertTo和ConvertTo中的两个函数classColorPairConverter是更新为包含:
if (destinationType == typeof(InstanceDescriptor) ||
destinationType == typeof(String))
{
...
}
通过这些更改并在 re-adding 对表单进行控制后,我能够让设计人员生成此内容:
我正在开发一个自定义用户控件,它使用线性渐变画笔绘制其背景。出于说明目的,我将使用一个面板作为控件的基础。 我已经定义了一个 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);
}
}
}
我相信您可以通过进行三处更改来按照您希望的方式运行。
- 从用户控件 GradientColor 的 GradientBackColor 属性 中删除 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();
}
}
- 将 [TypeConverter(typeof(ColorPairConverter))] 属性添加到 class ColorPair.
[TypeConverter(typeof(ColorPairConverter))]
public class ColorPair
{
...
}
- CanConvertTo和ConvertTo中的两个函数classColorPairConverter是更新为包含:
if (destinationType == typeof(InstanceDescriptor) ||
destinationType == typeof(String))
{
...
}
通过这些更改并在 re-adding 对表单进行控制后,我能够让设计人员生成此内容: