Hello all,
Goal:
Make a custom property like
Size property in many of windows desktop application controls, and has the same behavior in design time and runtime exactly. I named it,
StartLength.
Note: I skipped any changed event handlers.
Error:
I change the value of
StartLength property from PropertyGrid at design time and then click 'Save All' toolbar button in Visual Studio 2013 (installed on Windows 10 x64), and finally rebuild solution, everything is OK.
But, when repeat the same for second time, I take this error message:
Code generation for property 'StartLength' failed. Error was: ''StartLengthConverter' is unable to convert 'TestApp2.MyClasses.StartLength' to 'System.ComponentModel.Design.Serialization.InstanceDescriptor'.'
How:
I create a new C# Windows Form application (.Net framework 4) with VS2013 and named it
TestApp2. Then I add two folders to my project. One is
MyClasses and another is
MyControls. Now, I add two classes (
StartLength and
StartLengthConverter) to
MyClasses folder, and a Custom Control (
MyTextBox) to
MyControls folder. The source of these classes are below. Now I rebuild solution and appear
MyTextBox custom control in Toolbox. Drag and drop it on Form1, save and rebuild project, then try to change the
StartLength property in
MyTextBox.
Note: This is the
TestApp2.zip download link.
StartLength source:
#region using
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
#endregion
namespace TestApp2.MyClasses
{
[ComVisible(true)]
[Serializable]
[SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates")]
[TypeConverter(typeof(StartLengthConverter))]
public struct StartLength
{
public static readonly StartLength Empty = new StartLength();
#region Properties
#region Start
public int Start
{
get
{
return _start;
}
set
{
_start = value;
}
}
private int _start;
#endregion
#region Length
public int Length
{
get
{
return _length;
}
set
{
_length = value;
}
}
private int _length;
#endregion
#region IsEmpty
[Browsable(false)]
public bool IsEmpty
{
get
{
return (_start == 0) && (_length == 0);
}
}
#endregion
#endregion
#region Methods
#region StartLength
public StartLength(int start, int length)
{
_start = start;
_length = length;
}
#endregion
#region Add
public static StartLength Add(StartLength left, StartLength right)
{
return new StartLength(left.Start + right.Start, left.Length + right.Length);
}
#endregion
#region Subtract
public static StartLength Subtract(StartLength left, StartLength right)
{
return new StartLength(left.Start - right.Start, left.Length - right.Length);
}
#endregion
#region operator +
public static StartLength operator +(StartLength left, StartLength right)
{
return Add(left, right);
}
#endregion
#region operator -
public static StartLength operator -(StartLength left, StartLength right)
{
return Subtract(left, right);
}
#endregion
#region operator ==
public static bool operator ==(StartLength left, StartLength right)
{
return (left.Start == right.Start) && (left.Length == right.Length);
}
#endregion
#region operator !=
public static bool operator !=(StartLength left, StartLength right)
{
return !(left == right);
}
#endregion
#region ToString
public override string ToString()
{
return "{Start=" + _start.ToString(CultureInfo.CurrentCulture) + ", Length=" + _length.ToString(CultureInfo.CurrentCulture) + "}";
}
#endregion
#region Equals
public override bool Equals(object obj)
{
if (obj is StartLength)
{
StartLength startLength = (StartLength)obj;
return (_start == startLength.Start) && (_length == startLength.Length);
}
return false;
}
#endregion
#region GetHashCode
public override int GetHashCode()
{
return _start ^ _length;
}
#endregion
#endregion
}
}
StartLengthConverter source:
#region using
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
#endregion
namespace TestApp2.MyClasses
{
public class StartLengthConverter : TypeConverter
{
#region Methods
#region CanConvertFrom
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
#endregion
#region CanConvertTo
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor))
return true;
return base.CanConvertTo(context, destinationType);
}
#endregion
#region ConvertFrom
[SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes")]
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string strValue = value as string;
if (strValue != null)
{
string text = strValue.Trim();
if (text.Length == 0)
return null;
if (culture == null)
culture = CultureInfo.CurrentCulture;
char sep = culture.TextInfo.ListSeparator[0];
string[] tokens = text.Split(new char[] { sep });
int[] values = new int[tokens.Length];
TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
for (int i = 0; i < values.Length; i++)
values[i] = (int)intConverter.ConvertFromString(context, culture, tokens[i]);
if (values.Length == 2)
return new StartLength(values[0], values[1]);
throw new ArgumentException("The format error. It must be 'Start,Length'.");
}
return base.ConvertFrom(context, culture, value);
}
#endregion
#region ConvertTo
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null)
throw new ArgumentNullException("destinationType");
if (value is StartLength)
{
if (destinationType == typeof(string))
{
if (culture == null)
culture = CultureInfo.CurrentCulture;
StartLength startLength = (StartLength)value;
string sep = culture.TextInfo.ListSeparator + " ";
TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
string[] args = new string[2];
int nArg = 0;
args[nArg++] = intConverter.ConvertToString(context, culture, startLength.Start);
args[nArg++] = intConverter.ConvertToString(context, culture, startLength.Length);
return string.Join(sep, args);
}
if (destinationType == typeof(InstanceDescriptor))
{
StartLength startLength = (StartLength)value;
ConstructorInfo ctor = typeof(StartLength).GetConstructor(new Type[] { typeof(int), typeof(int) });
if (ctor != null)
return new InstanceDescriptor(ctor, new object[] { startLength.Start, startLength.Length });
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
#endregion
#region CreateInstance
[SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes")]
[SuppressMessage("Microsoft.Security", "CA2102:CatchNonClsCompliantExceptionsInGeneralHandlers")]
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
{
if (propertyValues == null)
throw new ArgumentNullException("propertyValues");
object start = propertyValues["Start"];
object length = propertyValues["Length"];
if ((start == null) || (length == null) || (!(start is int)) || (!(length is int)))
throw new ArgumentException("Invalid propertyValues entry.");
return new StartLength((int)start, (int)length);
}
#endregion
#region GetCreateInstanceSupported
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
return true;
}
#endregion
#region GetProperties
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(StartLength), attributes);
return props.Sort(new string[] { "Start", "Length" });
}
#endregion
#region GetPropertiesSupported
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
#endregion
#endregion
}
}
MyTextBox source:
#region using
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using TestApp2.MyClasses;
#endregion
namespace TestApp2.MyControls
{
[DefaultEvent("TextChanged")]
[DefaultProperty("Text")]
[ToolboxItem(true)]
[ToolboxBitmap(typeof(TextBox))]
public partial class MyTextBox : TextBox
{
#region Properties
#region StartLength
[Browsable(true)]
[Category("My")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[EditorBrowsable(EditorBrowsableState.Always)]
public virtual StartLength StartLength
{
get
{
return _startLength;
}
set
{
_startLength = value;
}
}
private StartLength _startLength = new StartLength(0, 0);
#region DefaultStartLength
private StartLength DefaultStartLength
{
get
{
return new StartLength(0, 0);
}
}
#endregion
#region ResetStartLength
private void ResetStartLength()
{
this.StartLength = DefaultStartLength;
}
#endregion
#region ShouldSerializeStartLength
private bool ShouldSerializeStartLength()
{
return !(this.StartLength.Equals(DefaultStartLength));
}
#endregion
#endregion
#endregion
#region Methods
#region MyTextBox
public MyTextBox()
{
InitializeComponent();
}
#endregion
#endregion
}
}
What I have tried:
Make a custom property like the 'Size' property behaviors.