工作上遇到了给控制绘制圆角的需求。然后让我们来寻找如何更好的解决圆角的这个问题。
在控件的处理上借鉴https://github.com/kwonganding/winform.controls这个项目里面的东西。
首先在这个开源项目中有个public struct CornerRadius{} 的结构体。我们需要对它做一些修改,来实现自己想做的东西。
我们想做成什么样呢,这个CornerRadius 定义的属性可以像 组件的Padding属性一样在设计时的属性栏中显示和设置。效果类似于:
然后对照Padding的源码,将CornerRadius类型修改为:
[Serializable] public struct CornerRadius { #region Field private bool _all; private int topLeft; private int topRight; private int bottomLeft; private int bottomRight; #endregion #region Initializes /// <summary> /// (构造函数).Initializes a new instance of the <see cref="CornerRadius"/> struct. /// 设置四个角为相同的圆角半径 /// </summary> /// <param name="radius">The radius.</param> /// User:Ryan CreateTime:2011-07-19 11:32. public CornerRadius(int radius) : this(radius, radius, radius, radius) { _all = true; } /// <summary> /// (构造函数).Initializes a new instance of the <see cref="CornerRadius"/> struct. /// 初始化四个角的圆角半径 /// </summary> /// <param name="topLeft">The top left.</param> /// <param name="topRight">The top right.</param> /// <param name="bottomLeft">The bottom left.</param> /// <param name="bottomRight">The bottom right.</param> /// User:Ryan CreateTime:2011-07-19 11:35. public CornerRadius(int topLeft, int topRight, int bottomLeft, int bottomRight) { this.topLeft = topLeft; this.topRight = topRight; this.bottomLeft = bottomLeft; this.bottomRight = bottomRight; _all = topLeft == topRight && topLeft == bottomLeft && bottomLeft == bottomRight; } #endregion /// <summary> /// 左上角圆角半径 /// </summary> [RefreshProperties(RefreshProperties.All)] public int TopLeft { get { return topLeft; } set { topLeft = value; } } /// <summary> /// 右上角圆角半径 /// </summary> [RefreshProperties(RefreshProperties.All)] public int TopRight { get { return topRight; } set { topRight = value; } } /// <summary> /// 左下角圆角半径 /// </summary> [RefreshProperties(RefreshProperties.All)] public int BottomLeft { get { return bottomLeft; } set { bottomLeft = value; } } /// <summary> /// 右下角圆角半径 /// </summary> [RefreshProperties(RefreshProperties.All)] public int BottomRight { get { return bottomRight; } set { bottomRight = value; } } [RefreshProperties(RefreshProperties.All)] public int All { get { return _all ? TopLeft : -1; } set { if (_all != true || topLeft != value) { _all = true; topLeft = topRight = bottomLeft = bottomRight = value; } } } internal bool ShouldSerializeAll() { return _all; } }
想要该类型的属性显示在属性栏中可以编辑还需要实现针对该类型的类型转换特性,TypeConverter的派生类。可以看出,照抄Padding的Converter的实现,修改一点就行了。
public class CornerRadiusConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) { return true; } return base.CanConvertFrom(context, sourceType); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(CornerRadius)) { return true; } return base.CanConvertTo(context, destinationType); } [ SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes") // ConvertFromString returns an object ] public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { string valueStr = value as string; if (valueStr != null) { valueStr = valueStr.Trim(); if (valueStr.Length == 0) { return null; } else { // Parse 4 integer values. if (culture == null) { culture = CultureInfo.CurrentCulture; } char sep = culture.TextInfo.ListSeparator[0]; string[] tokens = valueStr.Split(new char[] { sep }); int[] values = new int[tokens.Length]; TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int)); for (int i = 0; i < values.Length; i++) { // Note: ConvertFromString will raise exception if value cannot be converted. values[i] = (int)intConverter.ConvertFromString(context, culture, tokens[i]); } if (values.Length == 4) { return new CornerRadius(values[0], values[1], values[2], values[3]); } else { throw new ArgumentException("value length is error."); } } } //throw new ArgumentException(value.GetType().ToString()); return base.ConvertFrom(context, culture, value); } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == null) { throw new ArgumentNullException("destinationType"); } if (value is CornerRadius) { if (destinationType == typeof(string)) { CornerRadius cornerRadius = (CornerRadius)value; if (culture == null) { culture = CultureInfo.CurrentCulture; } string sep = culture.TextInfo.ListSeparator + " "; TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int)); string[] args = new string[4]; int nArg = 0; // Note: ConvertToString will raise exception if value cannot be converted. args[nArg++] = intConverter.ConvertToString(context, culture, cornerRadius.TopLeft); args[nArg++] = intConverter.ConvertToString(context, culture, cornerRadius.TopRight); args[nArg++] = intConverter.ConvertToString(context, culture, cornerRadius.BottomLeft); args[nArg++] = intConverter.ConvertToString(context, culture, cornerRadius.BottomRight); return string.Join(sep, args); } else if (destinationType == typeof(CornerRadius)) { CornerRadius cornerRadius = (CornerRadius)value; if (cornerRadius.ShouldSerializeAll()) { return new InstanceDescriptor( typeof(CornerRadius).GetConstructor(new Type[] { typeof(int) }), new object[] { cornerRadius.All }); } else { return new InstanceDescriptor( typeof(CornerRadius).GetConstructor(new Type[] { typeof(int), typeof(int), typeof(int), typeof(int) }), new object[] { cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomLeft, cornerRadius.BottomRight }); } } } return base.ConvertTo(context, culture, value, destinationType); } public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) { if (context == null) { throw new ArgumentNullException("context"); } if (propertyValues == null) { throw new ArgumentNullException("propertyValues"); } CornerRadius original = (CornerRadius)context.PropertyDescriptor.GetValue(context.Instance); int all = (int)propertyValues["All"]; if (original.All != all) { return new CornerRadius(all); } else { return new CornerRadius( (int)propertyValues["TopLeft"], (int)propertyValues["TopRight"], (int)propertyValues["BottomLeft"], (int)propertyValues["BottomRight"]); } } public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) { return true; } public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(CornerRadius), attributes); return props.Sort(new string[] { "All", "TopLeft", "TopRight", "BottomLeft", "BottomRight" }); } public override bool GetPropertiesSupported(ITypeDescriptorContext context) { return true; } }
有几点需要注意的是:
1、如果使用CornerRadius类型的组件和CornerRadius类型不在一个项目里面。每次重新编译项目时会报错。
2、[TypeConverterAttribute(typeof(CornerRadiusConverter))]特性需要附加到CornerRadius类型的属性上,不能添加到CornerRadius类型上。不然重新编译组件项目时,会导致已有的组件属性栏显示出问题。
然后把CornerRadius类型和组件放在一个项目,并且将[TypeConverterAttribute(typeof(CornerRadiusConverter))]属性附加给CornerRadius类型的属性,就可以实现开始时图片的效果了。组件代码如下:
public class TestCornerRadiusDesigner:UserControl { CornerRadius radius; public TestCornerRadiusDesigner() :base() { //SetStyle(ControlStyles.AllPaintingInWmPaint | // ControlStyles.OptimizedDoubleBuffer | // ControlStyles.ResizeRedraw | // ControlStyles.UserPaint, true); radius = new CornerRadius(0); } [TypeConverterAttribute(typeof(CornerRadiusConverter))] public CornerRadius Radius { get { return radius; } set { radius = value; //Invalidate(); } } }
That‘s all.