一.为属性提供编辑类
弹出式和下拉式是如何实现的呢,这需要为属性提供一个专门的编辑类。.Net为我们提供了一个System.Drawing.Design.UITypeEditor类,它是所有编辑类的基类,从他继承出了诸如ColorEditor、FontEditor的类,因此我们可以在属性框中编辑颜色和字体。定义了这样的类,我们也可以为自己的属性实现弹出式和下拉式编辑方式。
先看一下MSDN中对UITypeEditor的介绍:提供可用于设计值编辑器的基类,这些编辑器可提供用户界面 (UI),用来表示和编辑所支持的数据类型的对象值。
UITypeEditor 类提供一种基类,可以从该基类派生和进行扩展,以便为设计时环境实现自定义类型编辑器。通常,您的自定义类型编辑器与 PropertyGrid 控件进行交互。在文本框值编辑器不足以有效地选择某些类型的值的情况下,自定义类型编辑器非常有用。
继承者说明:若要实现自定义设计时 UI 类型编辑器,必须执行下列步骤
using System;
using System.Drawing;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
public class MyCustomEditor : UITypeEditor
{
// 1. 指定编辑器样式:Modal 表示使用弹出对话框
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
// 2. 处理属性值的编辑
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
// 3. 支持绘制值的表示形式
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
// 4. 绘制值的表示形式
public override void PaintValue(PaintValueEventArgs e)
//5 .设置默认值或执行其他初始化逻辑
public MyCustomEditor()
}
1. 定义一个从 UITypeEditor
派生的类
- 目的:创建一个新的编辑器类,用于自定义编辑属性值的方式。
- 步骤:从
UITypeEditor
基类继承,通常会定义一个新类,例如MyCustomEditor
。
2. 重写 EditValue
方法
-
目的:处理用户界面和用户输入操作,以及对属性值的分配。
-
步骤:在
EditValue
方法中,你将实现弹出对话框或其他用户界面,允许用户输入新值,然后将这个值应用到目标属性上。例如:public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) { // 实现自定义编辑逻辑 // 例如,显示对话框让用户输入新值 // 更新并返回新的值 }
3. 重写 GetEditStyle
方法
-
目的:通知“属性”窗口该编辑器使用的编辑器样式。
-
步骤:
GetEditStyle
方法返回一个UITypeEditorEditStyle
枚举值,指示编辑器的类型(如UITypeEditorEditStyle.Modal
、UITypeEditorEditStyle.DropDown
或UITypeEditorEditStyle.None
)。例如:public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) { return UITypeEditorEditStyle.Modal; // 或其他适当的样式 }
4. 支持值的表示形式(可选)
-
目的:在属性窗口中显示值的可视化表示形式。
-
步骤:实现这部分功能时,你需要:
-
重写
GetPaintValueSupported
方法:指示编辑器是否支持显示值的表示形式。返回true
表示支持,false
表示不支持。public override bool GetPaintValueSupported(ITypeDescriptorContext context) { return true; // 表示支持绘制值的表示形式 }
-
重写
PaintValue
方法:实现如何绘制值的表示形式。例如,你可以绘制一个图标或图像,代表属性的当前值。public override void PaintValue(PaintValueEventArgs e) { // 实现绘制逻辑 // 例如绘制一个矩形来显示颜色值 e.Graphics.FillRectangle(new SolidBrush(Color.Red),e.Bounds); }
-
5. 初始化行为(可选)
-
目的:如果编辑器需要特定的初始化行为,你可以重写
UITypeEditor
的构造函数。 -
步骤:在自定义构造函数中,你可以设置默认值或执行其他初始化逻辑。
public MyCustomEditor() { // 实现初始化逻辑 }
这些步骤为你提供了创建和自定义 UITypeEditor
的基础,以便在设计时属性窗口中更好地管理和展示属性值。
接下来我们来实现下拉式编辑方式,接下来举一个具体的例子来说明:
首先假如我们有一个控件类MyControl:
public class MyControl : System.Windows.Forms.UserControl
{
private double _angle;
[BrowsableAttribute(true)]
public double Angle
{
get
{ return _angle; }
set
{ _angle = value; }
}
public MyControl()
{
this._angle = 90;
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.DrawString("The Angle is " + _angle, this.Font, Brushes.Red,0,0);
}
}
其中有个属性Angle,我们要为其提供下拉式和弹出式编辑方式,当然了,一般都是较为复杂的属性才需要,这里只是为了举例说明。
二.下拉式编辑方式
我们为其定义一个派生的编辑类AngleEditor,EditValue和GetEditStyle两个函数,这是必须要重写的:
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public class AngleEditor : System.Drawing.Design.UITypeEditor
{
public AngleEditor()
{
}
//下拉式还是弹出式
public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.DropDown;
}
// 为属性显示UI编辑框
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
//值类型不为double,直接返回value
if (value.GetType() != typeof(double))
return value;
//值为double,显示下拉式或弹出编辑框
IWindowsFormsEditorService edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (edSvc != null)
{
// 显示编辑框并初始化编辑框的值
AngleControl angleControl = new AngleControl((double)value);
edSvc.DropDownControl(angleControl);
// 返回编辑框中编辑的值.
if (value.GetType() == typeof(double))
return angleControl.angle;
}
return value;
}
//下面两个函数是为了在PropertyGrid中显示一个辅助的效果
//可以不用重写
public override void PaintValue(System.Drawing.Design.PaintValueEventArgs e)
{
int normalX = (e.Bounds.Width / 2);
int normalY = (e.Bounds.Height / 2);
e.Graphics.FillRectangle(new SolidBrush(Color.DarkBlue), e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);
e.Graphics.FillEllipse(new SolidBrush(Color.White), e.Bounds.X + 1, e.Bounds.Y + 1, e.Bounds.Width - 3, e.Bounds.Height - 3);
e.Graphics.FillEllipse(new SolidBrush(Color.SlateGray), normalX + e.Bounds.X - 1, normalY + e.Bounds.Y - 1, 3, 3);
double radians = ((double)e.Value * Math.PI) / (double)180;
e.Graphics.DrawLine(new Pen(new SolidBrush(Color.Red), 1), normalX + e.Bounds.X, normalY + e.Bounds.Y,
e.Bounds.X + (normalX + (int)((double)normalX * Math.Cos(radians))),
e.Bounds.Y + (normalY + (int)((double)normalY * Math.Sin(radians))));
}
public override bool GetPaintValueSupported(System.ComponentModel.ITypeDescriptorContext context)
{
return true;
}
}
// 这里是我们要显示出来的编辑器,把它作为一个内置类,本质上他就是一个显示form
//从UserControl继承,要在上面EditValue函数中使用的
internal class AngleControl : System.Windows.Forms.UserControl
{
public double angle; //编辑的角度
private float x; //鼠标位置
private float y;
public AngleControl(double initial_angle)
{
this.angle = initial_angle;
}
//显现时,显示属性的当前值
protected override void OnLoad(EventArgs e)
{
int originX = (this.Width / 2);
int originY = (this.Height / 2);
this.x = (float)(50 * Math.Cos(this.angle * Math.PI / 180) + originX);
this.y = (float)(50 * Math.Sin(this.angle * Math.PI / 180) + originY);
base.OnLoad(e);
}
//绘制控件,用来显示编辑角度
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
int originX = (this.Width / 2);
int originY = (this.Height / 2);
e.Graphics.DrawEllipse(Pens.Black, originX - 50, originY - 50, 100, 100);
e.Graphics.DrawLine(Pens.Black, originX, originY, x, y);
e.Graphics.DrawString("Angle:" + this.angle, this.Font, Brushes.Red, 0, 0);
}
//鼠标移动时设置角度
protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
int originX = (this.Width / 2);
int originY = (this.Height / 2);
double len = Math.Sqrt(Math.Pow(e.X - originX, 2) + Math.Pow(e.Y - originY, 2));
double h = e.Y - originY;
this.angle = Math.Asin(h / len);
if ((e.X >= originX && e.Y >= originY))
{
this.x = (float)(50 * Math.Cos(this.angle) + originX);
this.y = (float)(50 * Math.Sin(this.angle) + originY);
this.angle = this.angle * 180 / Math.PI;
}
else if (e.X < originX && e.Y > originY)
{
this.x = (float)(originX - 50 * Math.Cos(this.angle));
this.y = (float)(50 * Math.Sin(this.angle) + originY);
this.angle = 180 - this.angle * 180 / Math.PI;
}
else if (e.X < originX && e.Y < originY)
{
this.x = (float)(originX - 50 * Math.Cos(this.angle));
this.y = (float)(originY + 50 * Math.Sin(this.angle));
this.angle = 180 - this.angle * 180 / Math.PI;
}
else if (e.X >= originX && e.Y <= originY)
{
this.x = (float)(originX + 50 * Math.Cos(this.angle));
this.y = (float)(originY + 50 * Math.Sin(this.angle));
this.angle = 360 + this.angle * 180 / Math.PI;
}
this.Invalidate();
}
}
}
这里有两个类,一个是AngleEditor,他就是我们要用于到属性特性中的编辑类,一个是AngleControl,他是辅助AngleEditor来编辑属性的,也就是说,他就是一个form,当我们编辑属性时,他会显现出来,供我们编辑属性值,可以是textBox,图形,表格,各种方式,只需要最后他返回编辑好的属性值给AngleEditor类。在AngleEditor类中的EditValue函数中,我们会调用AngleControl类,并接受它返回的值。
设计好编辑类后,把它应用到MyControl类中的Angle属性中,在Angle属性中增加特性[EditorAttribute(typeof(AngleEditor), typeof(System.Drawing.Design.UITypeEditor))],相当于告诉PropertyGrid我们要用AngleEditor来编辑这个属性。
[BrowsableAttribute(true)]
[EditorAttribute(typeof(AngleEditor), typeof(System.Drawing.Design.UITypeEditor))]
public double Angle
{
get
{ return _angle; }
set
{ _angle = value; }
}
效果如下:
三.弹出式编辑方式
实现了下拉式,我们来实现弹出式,弹出式和下拉式非常的相像,现在要弹出一个编辑框,因此我们的AngleControl类不能从UserControl继承,而从From继承,让他成为一个对话框,在AngleEditor的EditValue函数中,弹出他并接受返回值,如下:
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public class AngleEditor : System.Drawing.Design.UITypeEditor
{
public AngleEditor()
{
}
//下拉式还是弹出式.
//这里是第一个修改的地方,改为了弹出式。
public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;//弹出式
}
// 为属性显示UI编辑框
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
//值类型不为double,直接返回value
if (value.GetType() != typeof(double))
return value;
//值为double,显示下拉式或弹出编辑框
IWindowsFormsEditorService edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (edSvc != null)
{
// 显示编辑框并初始化编辑框的值,
//这里是第二个修改点,改为showDialog
AngleControl2 angleControl = new AngleControl2((double)value);
edSvc.ShowDialog(angleControl);
// 返回编辑框中编辑的值.
if (value.GetType() == typeof(double))
return angleControl.angle;
}
return value;
}
//下面两个函数是为了在PropertyGrid中显示一个辅助的效果
//可以不用重写
public override void PaintValue(System.Drawing.Design.PaintValueEventArgs e)
{
int normalX = (e.Bounds.Width / 2);
int normalY = (e.Bounds.Height / 2);
e.Graphics.FillRectangle(new SolidBrush(Color.DarkBlue), e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height);
e.Graphics.FillEllipse(new SolidBrush(Color.White), e.Bounds.X + 1, e.Bounds.Y + 1, e.Bounds.Width - 3, e.Bounds.Height - 3);
e.Graphics.FillEllipse(new SolidBrush(Color.SlateGray), normalX + e.Bounds.X - 1, normalY + e.Bounds.Y - 1, 3, 3);
double radians = ((double)e.Value * Math.PI) / (double)180;
e.Graphics.DrawLine(new Pen(new SolidBrush(Color.Red), 1), normalX + e.Bounds.X, normalY + e.Bounds.Y,
e.Bounds.X + (normalX + (int)((double)normalX * Math.Cos(radians))),
e.Bounds.Y + (normalY + (int)((double)normalY * Math.Sin(radians))));
}
public override bool GetPaintValueSupported(System.ComponentModel.ITypeDescriptorContext context)
{
return true;
}
}
// 这里是我们要显示出来的编辑器,把它作为一个内置类
//从UserControl继承,要在上面EditValue函数中使用的
//这里是第三个修改点,让他显示为对话框,所以继承了form类
internal class AngleControl2 : System.Windows.Forms.Form
{
public double angle; //编辑的角度
private float x; //鼠标位置
private float y;
public AngleControl(double initial_angle)
{
this.angle = initial_angle;
}
//显现时,显示属性的当前值
protected override void OnLoad(EventArgs e)
{
int originX = (this.Width / 2);
int originY = (this.Height / 2);
this.x = (float)(50 * Math.Cos(this.angle * Math.PI / 180) + originX);
this.y = (float)(50 * Math.Sin(this.angle * Math.PI / 180) + originY);
base.OnLoad(e);
}
//绘制控件,用来显示编辑角度
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
int originX = (this.Width / 2);
int originY = (this.Height / 2);
e.Graphics.DrawEllipse(Pens.Black, originX - 50, originY - 50, 100, 100);
e.Graphics.DrawLine(Pens.Black, originX, originY, x, y);
e.Graphics.DrawString("Angle:" + this.angle, this.Font, Brushes.Red, 0, 0);
}
//鼠标移动时设置角度
protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
int originX = (this.Width / 2);
int originY = (this.Height / 2);
double len = Math.Sqrt(Math.Pow(e.X - originX, 2) + Math.Pow(e.Y - originY, 2));
double h = e.Y - originY;
this.angle = Math.Asin(h / len);
if ((e.X >= originX && e.Y >= originY))
{
this.x = (float)(50 * Math.Cos(this.angle) + originX);
this.y = (float)(50 * Math.Sin(this.angle) + originY);
this.angle = this.angle * 180 / Math.PI;
}
else if (e.X < originX && e.Y > originY)
{
this.x = (float)(originX - 50 * Math.Cos(this.angle));
this.y = (float)(50 * Math.Sin(this.angle) + originY);
this.angle = 180 - this.angle * 180 / Math.PI;
}
else if (e.X < originX && e.Y < originY)
{
this.x = (float)(originX - 50 * Math.Cos(this.angle));
this.y = (float)(originY + 50 * Math.Sin(this.angle));
this.angle = 180 - this.angle * 180 / Math.PI;
}
else if (e.X >= originX && e.Y <= originY)
{
this.x = (float)(originX + 50 * Math.Cos(this.angle));
this.y = (float)(originY + 50 * Math.Sin(this.angle));
this.angle = 360 + this.angle * 180 / Math.PI;
}
this.Invalidate();
}
}
}
可以看到,只修改了三个地方:
1.AngleEditor类中GetEditStyle函数返回Modal,表明是以弹出式编辑。
2.AngleEditor类中EditValue函数中调用编辑控件的方式:
AngleControl2 angleControl = new AngleControl2((double)value);
edSvc.ShowDialog(angleControl);
3.AngleControl2从Form继承,让他显示为对话框,这里为了区别上面的AngleControl类,将其改成了AngleControl2。
效果如下: