Visual Studio C# Custom Control property is grayed out - c#

I have a custom control which works fine until I added the following section for a PointF variable but it shows up as grayed out in the properties list and I can't change the values at design time. See image below.
[DefaultValue(0)]
[Description("Gets or sets the jetting sword position")]
public virtual PointF jettingPosition
{
get
{
return jettingCentrePos;
}
set
{
jettingCentrePos = value;
Refresh();
}
}
How do I make this property enabled? I need something like the inherited Location property but the values for X and Y need to be float type.

If you used the type Point instead of PointF I would say just specify a TypeConverter of type PointConverter and be done with it.
[DefaultValue(0)]
[Description("Gets or sets the jetting sword position")]
[TypeConverter(typeof(PointConverter))]
public virtual PointF jettingPosition
However, you will see the error Object does not match target type. if you did that. Unfortunately you will need to create your own PointF type converter. Luckily we can just copy the PointConverter and change the type to PointF which is what we have here:
using System;
using System.Collections;
using System.Drawing;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
using System.Reflection;
using System.Windows.Forms;
public class PointFConverter : TypeConverter {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
return ((sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
return ((destinationType == typeof(InstanceDescriptor)) || base.CanConvertTo(context, destinationType));
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
string str = value as string;
if (value == null) return base.ConvertFrom(context, culture, value);
str = str.Trim();
if (str.Length == 0) return null;
if (culture == null) culture = CultureInfo.CurrentCulture;
char ch = culture.TextInfo.ListSeparator[0];
string[] strArray = str.Split(new char[] { ch });
int[] numArray = new int[strArray.Length];
TypeConverter converter = TypeDescriptor.GetConverter(typeof(float));
for (int i = 0; i < numArray.Length; i++) {
numArray[i] = (int)converter.ConvertFromString(context, culture, strArray[i]);
}
if (numArray.Length != 2) throw new ArgumentException("Invalid format");
return new PointF(numArray[0], numArray[1]);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
if (destinationType == null) throw new ArgumentNullException("destinationType");
if (value is Point) {
if (destinationType == typeof(string)) {
PointF point = (PointF)value;
if (culture == null) culture = CultureInfo.CurrentCulture;
string separator = culture.TextInfo.ListSeparator + " ";
TypeConverter converter = TypeDescriptor.GetConverter(typeof(float));
string[] strArray = new string[2];
int num = 0;
strArray[num++] = converter.ConvertToString(context, culture, point.X);
strArray[num++] = converter.ConvertToString(context, culture, point.Y);
return string.Join(separator, strArray);
}
if (destinationType == typeof(InstanceDescriptor)) {
PointF point2 = (PointF)value;
ConstructorInfo constructor = typeof(PointF).GetConstructor(new Type[] { typeof(float), typeof(float) });
if (constructor != null) return new InstanceDescriptor(constructor, new object[] { point2.X, point2.Y });
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) {
if (propertyValues == null) throw new ArgumentNullException("propertyValues");
object xvalue = propertyValues["X"];
object yvalue = propertyValues["Y"];
if (((xvalue == null) || (yvalue == null)) || (!(xvalue is float) || !(yvalue is float))) {
throw new ArgumentException("Invalid property value entry");
}
return new PointF((float)xvalue, (float)yvalue);
}
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) {
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) {
return TypeDescriptor.GetProperties(typeof(PointF), attributes).Sort(new string[] { "X", "Y" });
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context) {
return true;
}
}
Then you can use this:
[DefaultValue(0)]
[Description("Gets or sets the jetting sword position")]
[TypeConverter(typeof(PointFConverter))]
public virtual PointF jettingPosition
Here is more information about custom Type Converters. This specific case covers string to point:
https://learn.microsoft.com/en-us/previous-versions/ayybcxe5(v=vs.140)?redirectedfrom=MSDN

Related

How to display an enum with duplicate values in a .NET PropertyGrid?

I have an enum that contains duplicate values. For example:
public enum DataVals : byte
{
C1_Route1to2 = 1,
C4_Route3to5 = 1,
C6_Route1to2 = 2,
C7_Route3to5 = 2
}
The values C# are just internal values within my application. Depending on which route is selected by the user, route is another property in the class, a 1 could mean use C1 or C4. The problem is I am using a PropertyGrid in my Winform and this property displays the duplicate values as having the same name. So C1_Route1to2 shows up twice instead of both C1_Route1to2 and C4_Route3to5.
How do I tell the PropertyGrid to display each unique name, rather than duplicating the values?
Although I agree with Gabriel, you could achieve what you need using the TypeConverter as I mentioned before. You might need to change the editor to allow selecting more than one enum if it has the FlagsAttribute...
Place the attribute:
[TypeConverter(typeof(ComplexEnumConverter ))]
public enum DataVals : byte
{
C1_Route1to2 = 1,
C4_Route3to5 = 1,
C6_Route1to2 = 2,
C7_Route3to5 = 2
}
And here is the converter:
public class ComplexEnumConverter : EnumConverter
{
public bool IsFlagged { get; }
public string[] EnumValues { get; }
public ComplexEnumConverter(Type type)
: base(type)
{
IsFlagged = TypeDescriptor.GetAttributes(type).OfType<FlagsAttribute>().Any();
EnumValues = Enum.GetNames(type);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
var str = value as string;
if (!string.IsNullOrWhiteSpace(str))
{
var values = str.Split(',').Select(s => s.Trim());
var enumValue = Enum.Parse(EnumType, values.First());
if (IsFlagged)
{
var temp = (int)enumValue;
foreach (var item in values.Skip(1))
{
temp |= (int)Enum.Parse(EnumType, item);
}
enumValue = temp;
}
return enumValue;
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var type = value?.GetType();
if (type == EnumType)
{
var list = new List<string>();
int k = (int)value;
foreach (var item in Enum.GetNames(type))
{
var current = (int)Enum.Parse(type, item);
if ((k & current) == current)
{
list.Add(item);
}
}
return list.Aggregate((c, n) => $"{c}, {n}");
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return context.PropertyDescriptor.PropertyType.IsEnum;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return context.PropertyDescriptor.PropertyType.IsEnum;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(EnumValues);
}
}

How does DefaultValueAttriubte work for Encoding?

I've a property decorated with DefaultValueAttribute.
The code looks like this:
[DefaultValue(typeof(Encoding), "utf-8")]
public Encoding Encoding { get; set; }
There's a Reset-Method that restores all default values of all properties:
public void Reset()
{
foreach (var property in TypeDescriptor.GetProperties(typeof(ILoggedChannelValueFileExportInfo)).Cast<PropertyDescriptor>())
{
property.ResetValue(this);
}
}
This works perfect for all standard properties including a property with ReturnType CultureInfo.
I tried to specify utf-8, utf8 and both in upper case but nothing works.
What do I have to specify to make it work?
I'm able to do this via reflection but I hope that there's a way to use the TypeDescriptor.
EDIT:
Due to answer of Hans Passant I wrote a TypeConverter. That's the whole code:
public class Foo
{
[TypeConverter(typeof(EncodingTypeConverter))]
[DefaultValue(typeof(Encoding), "UTF-8")]
public Encoding Encoding { get; set; }
public void Reset()
{
foreach (var property in TypeDescriptor.GetProperties(this).Cast<PropertyDescriptor>())
{
property.ResetValue(this);
}
}
}
public class EncodingTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return value is string ? Encoding.GetEncoding((string)value) : base.ConvertFrom(context, culture, value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return destinationType == typeof(string) ? value.ToString() : base.ConvertTo(context, culture, value, destinationType);
}
}
After creating an instance and call Reset Encoding is still null and no method is called from EncodingTypeConverter. What should I do?`
There is no UI involved - no property grid. The only thing is the TypeDescriptor that should reset the value.
EDIT:
It is nearly impossible to write my own TypeConverter-call because the DefaultValueAttribute doesn't store the specified string value. It hurts. Here's my implementation that doesn't work. Maybe there's someone who can use it to create a solution:
public virtual void ResetValues()
{
foreach (var property in TypeDescriptor.GetProperties(typeof(TAttributedType)).Cast<PropertyDescriptor>())
{
if (property.CanResetValue(this))
{
property.ResetValue(this);
continue;
}
var defaultValueAttribute = (DefaultValueAttribute)property.Attributes[typeof(DefaultValueAttribute)];
var typeConverterAttribute = (TypeConverterAttribute)property.Attributes[typeof(TypeConverterAttribute)];
if (defaultValueAttribute == null || !(defaultValueAttribute.Value is string) ||
typeConverterAttribute == null || string.IsNullOrWhiteSpace(typeConverterAttribute.ConverterTypeName))
{
continue;
}
var typeConverterType = Type.GetType(typeConverterAttribute.ConverterTypeName);
if (typeConverterType == null)
{
continue;
}
var typeConverter = (TypeConverter)Activator.CreateInstance(typeConverterType);
if (typeConverter.CanConvertFrom(typeof(string)))
{
var propertyValue = typeConverter.ConvertFrom(defaultValueAttribute.Value);
if (propertyValue != null)
{
property.SetValue(this, propertyValue);
}
}
}
}
Adding a TypeConverterAttribute and defining a DefaultValue(typeof(Encoding), "UTF-8") will not work. The constructor for DefaultValue tries to find a TypeConverter from the TypeDescriptor that doesn't use the TypeConverterAttribute.
Here is an implementation of the ResetValues-method that uses a string-value in DefaultValueAttributeand the TypeConverterAttribute to load a default value.
public virtual void ResetValues()
{
foreach (var property in TypeDescriptor.GetProperties(typeof(TAttributedType)).Cast<PropertyDescriptor>())
{
var defaultValueAttribute = (DefaultValueAttribute)property.Attributes[typeof(DefaultValueAttribute)];
var typeConverterAttribute = (TypeConverterAttribute)property.Attributes[typeof(TypeConverterAttribute)];
if (defaultValueAttribute != null && defaultValueAttribute.Value is string &&
typeConverterAttribute != null && !string.IsNullOrWhiteSpace(typeConverterAttribute.ConverterTypeName))
{
var typeConverterType = Type.GetType(typeConverterAttribute.ConverterTypeName);
if (typeConverterType != null)
{
var typeConverter = (TypeConverter)Activator.CreateInstance(typeConverterType);
if (typeConverter.CanConvertFrom(typeof(string)))
{
var propertyValue = typeConverter.ConvertFrom(defaultValueAttribute.Value);
if (propertyValue != null)
{
property.SetValue(this, propertyValue);
continue;
}
}
}
}
if (property.CanResetValue(this))
{
property.ResetValue(this);
}
}
}
The used TypeConverter is:
public class EncodingTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return value is string ? Encoding.GetEncoding((string)value) : base.ConvertFrom(context, culture, value);
}
}
And the attibuted property is:
[TypeConverter(typeof(EncodingTypeConverter))]
[DefaultValue("UTF-8")]
public Encoding Encoding { get; set; }
Note that the ResetValues-method first tests for a TypeConverter and a specified DefaultValue. That's needed because the PropertyDescriptor.CanResetValue() returns true if a string is specified as default value. This is not convertable to Encoding.

How do you use NodaTime Classes in a PropertyGrid?

I'm converting some application code to use NodaTime classes instead of System.DateTime. Part of my application uses the PropertyGrid control to allow a user to edit a class containing both a LocalDate and an Instant. Without changing anything, the PropertyGrid displays the properties okay, but they are no longer editable. What's the best way of allowing the user to edit these fields.
For the sake of exposition, we can use this class as a representative of the type of thing I'd like to display and edit:
public class User
{
public string Name { get; set; }
public LocalDate BirthDate { get; set; }
public Instant NextAppointment { get; set; }
}
Best I've come up with so far:
Step 1: Create TypeConverter's so that the Noda classes are editable
public class ToAndFromStringTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
else
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
else
return base.CanConvertTo(context, destinationType);
}
}
public class LocalDateTypeConverter : ToAndFromStringTypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
DateTime parsed;
if (!DateTime.TryParse((string)value, out parsed))
throw new ArgumentException("Cannot convert '" + (string)value + "' to LocalDate.");
else
return new LocalDate(parsed.Year, parsed.Month, parsed.Day);
}
else
{
return base.ConvertFrom(context, culture, value);
}
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
var tvalue = (LocalDate)value;
try
{
var x = tvalue.ToString("yyyy-MM-dd");
return x;
}
catch (NullReferenceException)
{
return "1900-1-1";
}
catch
{
throw new ArgumentException("Could not convert '" + value.ToString() + "' to LocalDate.");
}
}
else
return base.ConvertTo(context, culture, value, destinationType);
}
public class InstantTypeConverter : ToAndFromStringTypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
try
{
DateTime parsed = DateTime.Parse((string)value);
LocalDateTime dt = LocalDateTime.FromDateTime(parsed);
Instant i = dt.InZoneLeniently(DateTimeZoneProviders.Default.GetSystemDefault()).ToInstant();
return i;
}
catch
{
throw new ArgumentException("Cannot convert '" + (string)value + "' to Instant.");
}
}
else
{
return base.ConvertFrom(context, culture, value);
}
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
try
{
Instant tvalue = (Instant)value;
LocalDateTime local = tvalue.InZone(DateTimeZoneProviders.Default.GetSystemDefault()).LocalDateTime;
string output = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss.FFFFFF").Format(local);
return output;
}
catch
{
throw new ArgumentException("Could not convert '" + value.ToString() + "' to LocalDate.");
}
}
else
return base.ConvertTo(context, culture, value, destinationType);
}
}
Step 2: Register TypeConverters
Put this code at the top of your app:
TypeDescriptor.AddAttributes(typeof(LocalDate), new TypeConverterAttribute(typeof(LocalDateTypeConverter)));
TypeDescriptor.AddAttributes(typeof(Instant), new TypeConverterAttribute(typeof(InstantTypeConverter)));
Step 3: Use custom collection editor to handle things like List
public class NodaCollectionEditor : System.ComponentModel.Design.CollectionEditor
{
public NodaCollectionEditor(Type collection_type) : base(collection_type) { }
protected override object CreateInstance(Type itemType)
{
if (itemType == typeof(LocalDate))
return LocalDateHelper.MinValue;
else
return base.CreateInstance(itemType);
}
}
This can be registered by adding this attribute to any appropriate properties:
[System.ComponentModel.Editor(typeof(NodaCollectionEditor),typeof(System.Drawing.Design.UITypeEditor))]

PropertyGrid how do you add an editable List<Class>?

I have a class MyClassA that has an IList property. I am using a PropertyGrid control to display all the properties of MyClassA and I would like the list of MyClassB to be displayed and editable via the PropertyGrid for MyClassA.
I currently have all the other properties being displayed in the Property grid except for the property that is the list of MyClassB. How do I go about adding the List of MyClassB to the property grid where the user can add/edit/remove items from the List?
I haven't really been able to find any examples that go into detail on this as of yet although I am still digging.
Here is a solution I have worked out so far, although it still doesn't fit in 100% to what I am looking for.
I found this reference to modify for my liking: http://www.codeproject.com/KB/tabs/customizingcollectiondata.aspx
What I did was create a new class that inherits from CollectionBase and that uses an ICustomTypeDescriptor.
After I did this and implemented the basic functionality, I had to create a PropertyDescriptor for the class.
Here is the code:
public class ZoneCollection : CollectionBase, ICustomTypeDescriptor
{
#region Collection Implementation
/// <summary>
/// Adds an zone object to the collection
/// </summary>
/// <param name="emp"></param>
public void Add(Zone zone)
{
this.List.Add(zone);
}
/// <summary>
/// Removes an zone object from the collection
/// </summary>
/// <param name="emp"></param>
public void Remove(Zone zone)
{
this.List.Remove(zone);
}
/// <summary>
/// Returns an zone object at index position.
/// </summary>
public Zone this[int index]
{
get
{
return (Zone)this.List[index];
}
}
#endregion
// Implementation of interface ICustomTypeDescriptor
#region ICustomTypeDescriptor impl
public String GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public String GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
/// <summary>
/// Called to get the properties of this type. Returns properties with certain
/// attributes. this restriction is not implemented here.
/// </summary>
/// <param name="attributes"></param>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
/// <summary>
/// Called to get the properties of this type.
/// </summary>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties()
{
// Create a collection object to hold property descriptors
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);
// Iterate the list of employees
for (int i = 0; i < this.List.Count; i++)
{
// Create a property descriptor for the zone item and add to the property descriptor collection
ZoneCollectionPropertyDescriptor pd = new ZoneCollectionPropertyDescriptor(this, i);
pds.Add(pd);
}
// return the property descriptor collection
return pds;
}
#endregion
}
/// <summary>
/// Summary description for CollectionPropertyDescriptor.
/// </summary>
public class ZoneCollectionPropertyDescriptor : PropertyDescriptor
{
private ZoneCollection collection = null;
private int index = -1;
public ZoneCollectionPropertyDescriptor(ZoneCollection coll, int idx) :
base("#" + idx.ToString(), null)
{
this.collection = coll;
this.index = idx;
}
public override AttributeCollection Attributes
{
get
{
return new AttributeCollection(null);
}
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get
{
return this.collection.GetType();
}
}
public override string DisplayName
{
get
{
Zone zone = this.collection[index];
return zone.ID.ToString();
}
}
public override string Description
{
get
{
Zone zone = this.collection[index];
StringBuilder sb = new StringBuilder();
sb.Append(zone.ID.ToString());
if ( zone.Streets.Route != String.Empty || zone.Streets.Crossing != String.Empty)
sb.Append("::");
if (zone.Streets.Route != String.Empty)
sb.Append(zone.Streets.Route);
if ( zone.Streets.Crossing != String.Empty)
{
sb.Append(" and ");
sb.Append(zone.Streets.Crossing);
}
return sb.ToString();
}
}
public override object GetValue(object component)
{
return this.collection[index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return "#" + index.ToString(); }
}
public override Type PropertyType
{
get { return this.collection[index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
// this.collection[index] = value;
}
}
Intersection now contains a ZoneCollection instead of an IList and I can now edit/add/remove the zones contained within the collection.
Now, if I could make this more generic I'd be relatively happy. Another hindrance for my model is that I had to inherit from Collection base using this, instead of IList. This completely broke my mapping of my class for NHibernate and I'm now having to try and figure out how to remap this list using the method mentioned above.
If anyone wants to elaborate this any further I'd greatly appreciate some more insight.
I know this Topic is more than 2 years old, but maybe this could be interesting for you.
I had a similar Problem.
Starting with: I need a Point in 3D-Space which should be configurable in Property-Grid
For this I created a Class Koord. To make it changeable in PropertyGrid, I created a new Class "KoordConverter : TypeConverter"
This is used in a Vexel (check Wikipedia to find out what it's for :-) )
To create an TestBock (some 3D-Object) I'm using a List of Vexels.
Unfortunately I need a List of TestBlocks in my Program, Visible through the Property-Grid.
To start Topmost:
public partial class FormMain : Form
{
private BlockProperties _bp = new BlockProperties();
public FormMain()
{
InitializeComponent();
pgProperties.SelectedObject = _bp;
}
[...]
}
The Class BlockProperties includes the List of TestBocks which I filled a bit to show you what's inside.
class BlockProperties
{
public List<TestBocks> Testing { get; set; }
public BlockProperties()
{
Testing = new List<TestBocks>(3);
List<Vexel> t1 = new List<Vexel>(1);
t1.Add(new Vexel(new Koord(1,0,1), 1));
List<Vexel> t2 = new List<Vexel>(2);
t2.Add(new Vexel(new Koord(2, 0, 1), 2));
t2.Add(new Vexel(new Koord(2, 0, 2), 2));
List<Vexel> t3 = new List<Vexel>(3);
t3.Add(new Vexel(new Koord(3, 0, 1), 3));
t3.Add(new Vexel(new Koord(3, 0, 2), 3));
t3.Add(new Vexel(new Koord(3, 0, 3), 3));
TestBocks tb1 = new TestBocks();
tb1.Koords = t1;
TestBocks tb2 = new TestBocks();
tb2.Koords = t2;
TestBocks tb3 = new TestBocks();
tb3.Koords = t3;
Testing.Add(tb1);
Testing.Add(tb2);
Testing.Add(tb3);
[...]
}
[...]
}
Next is my TestBlock class, which is simply straight forward
[Serializable]
public class TestBocks
{
public List<Vexel> Vexels{ get; set; }
public TestBocks()
{
Vexels = new List<Vexel>();
}
}
In the Vexels is most of the magic I need for my Program:
I even put a ToString() here to make it easy during debugging.
public class Vexel
{
private Koord _origin;
private double _extent;
public Koord Origin { get { return _origin; } set { _origin = value; } }
public double Extent { get { return _extent; } set { _extent = value; } }
public string ToString()
{
NumberFormatInfo nFormatInfo = new NumberFormatInfo
{
NumberDecimalSeparator = ".",
NumberGroupSeparator = ""
};
return String.Format(nFormatInfo, "Origin;{0};{1};{2};Extent;{3}", _origin.X, _origin.Y, _origin.Z, _extent);
}
public Vexel()
{
_origin = new Koord(0,0,0);
Extent = 0;
}
public Vexel(Koord origin, double extent)
{
//TODO do some checking
_origin = origin;
_extent = extent;
}
So far everything worked fine for the PropertyGrid, but I could not edit the Koords.
The Class was pretty simple but not editable in the PropertyGrid.
Adding a TypeConverterClass solved this Problem (you can find the TypeConverter below the code of the Koord)
[TypeConverter(typeof(KoordConverter))]
[Serializable]
public class Koord
{
private double p_1;
private double p_2;
private double p_3;
public Koord(double x, double y, double z)
{
this.p_1 = x;
this.p_2 = y;
this.p_3 = z;
}
public string ToString()
{
return String.Format("X;{0};Y;{1};Z;{2}", p_1, p_2, p_3);
}
public double X { get { return p_1; } }
public double Y { get { return p_2; } }
public double Z { get { return p_3; } }
}
The Typeconverter was the most complicated code to write. You can find it below:
public class KoordConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(InstanceDescriptor) || base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string text = value as string;
if (text == null)
{
return base.ConvertFrom(context, culture, value);
}
string text2 = text.Trim();
if (text2.Length == 0)
{
return null;
}
if (culture == null)
{
culture = CultureInfo.CurrentCulture;
}
char c = culture.TextInfo.ListSeparator[0];
string[] array = text2.Split(new char[]
{
c
});
int[] array2 = new int[array.Length];
TypeConverter converter = TypeDescriptor.GetConverter(typeof(int));
for (int i = 0; i < array2.Length; i++)
{
array2[i] = (int)converter.ConvertFromString(context, culture, array[i]);
}
if (array2.Length == 3)
{
return new Koord(array2[0], array2[1], array2[2]);
}
throw new ArgumentException("TextParseFailedFormat");
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null)
{
throw new ArgumentNullException("destinationType");
}
if (value is Koord)
{
if (destinationType == typeof(string))
{
Koord Koord = (Koord)value;
if (culture == null)
{
culture = CultureInfo.CurrentCulture;
}
string separator = culture.TextInfo.ListSeparator + " ";
TypeConverter converter = TypeDescriptor.GetConverter(typeof(int));
string[] array = new string[3];
int num = 0;
array[num++] = converter.ConvertToString(context, culture, Koord.X);
array[num++] = converter.ConvertToString(context, culture, Koord.Y);
array[num++] = converter.ConvertToString(context, culture, Koord.Z);
return string.Join(separator, array);
}
if (destinationType == typeof(InstanceDescriptor))
{
Koord Koord2 = (Koord)value;
ConstructorInfo constructor = typeof(Koord).GetConstructor(new Type[]
{
typeof(double),
typeof(double),
typeof(double)
});
if (constructor != null)
{
return new InstanceDescriptor(constructor, new object[]
{
Koord2.X,
Koord2.Y,
Koord2.Z
});
}
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
{
if (propertyValues == null)
{
throw new ArgumentNullException("propertyValues");
}
object obj = propertyValues["X"];
object obj2 = propertyValues["Y"];
object obj3 = propertyValues["Z"];
if (obj == null || obj2 == null || obj3 == null || !(obj is double) || !(obj2 is double) || !(obj3 is double))
{
throw new ArgumentException("PropertyValueInvalidEntry");
}
return new Koord((double)obj, (double)obj2, (double)obj3);
}
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Koord), attributes);
return properties.Sort(new string[]
{
"X",
"Y",
"Z"
});
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
}
Basically after all of this was set up, It was no ploblem to modify any List of objects (TestBlocks or the Vexels within each TestBlock)
Hope it helps someone if they step over this Thread.
Best Regards
Robin Blood
PS:Editing is no problem in the PropertyGrid, maybe you just didn't get your constructors right !?
http://i.stack.imgur.com/LD3zf.png

How can I use TypeConverters with a ConfigurationSection?

So I've got a ConfigurationSection/ConfigurationElementCollection that has a configuration like this:
<mimeFormats>
<add mimeFormat="text/html" />
</mimeFormats>
And here is how I handle the mimeFormats:
public class MimeFormatElement: ConfigurationElement
{
#region Constructors
/// <summary>
/// Predefines the valid properties and prepares
/// the property collection.
/// </summary>
static MimeFormatElement()
{
// Predefine properties here
_mimeFormat = new ConfigurationProperty(
"mimeFormat",
typeof(MimeFormat),
"*/*",
ConfigurationPropertyOptions.IsRequired
);
}
private static ConfigurationProperty _mimeFormat;
private static ConfigurationPropertyCollection _properties;
[ConfigurationProperty("mimeFormat", IsRequired = true)]
public MimeFormat MimeFormat
{
get { return (MimeFormat)base[_mimeFormat]; }
}
}
public class MimeFormat
{
public string Format
{
get
{
return Type + "/" + SubType;
}
}
public string Type;
public string SubType;
public MimeFormat(string mimeFormatStr)
{
var parts = mimeFormatStr.Split('/');
if (parts.Length != 2)
{
throw new Exception("Invalid MimeFormat");
}
Type = parts[0];
SubType = parts[1];
}
}
And obviously I need a TypeConverter that actually does something (instead of this empty shell):
public class MimeFormatConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
throw new NotImplementedException();
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
throw new NotImplementedException();
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
throw new NotImplementedException();
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
throw new NotImplementedException();
}
}
How do I set up a TypeConverter that will allow type conversion from/to string? I've tried using the MSDN examples but I keep getting error message:
TypeConverter cannot convert from System.String.
Essentially, how can it be set up so that it will just work with whatever ConfigurationSection is trying to do?
You can put TypeConverterAttribute on the property to tell the serializer how to handle it.
[TypeConverter(typeof(MimeFormatConverter))]
[ConfigurationProperty("mimeFormat", IsRequired = true)]
public MimeFormat MimeFormat
{
get { return (MimeFormat)base[_mimeFormat]; }
}
Try this:
TestSection.cs
public class TestSection : ConfigurationSection
{
private static readonly ConfigurationProperty sFooProperty = new ConfigurationProperty("Foo",
typeof(Foo),
null,
new FooTypeConverter(),
null,
ConfigurationPropertyOptions.None);
public static readonly ConfigurationPropertyCollection sProperties = new ConfigurationPropertyCollection();
static TestSection()
{
sProperties.Add(sFooProperty);
}
public Foo Foo
{
get { return (Foo)this[sFooProperty]; }
set { this[sFooProperty] = value; }
}
protected override ConfigurationPropertyCollection Properties
{
get { return sProperties; }
}
}
Foo.cs
public class Foo
{
public string First { get; set; }
public string Second { get; set; }
public override string ToString()
{
return First + ',' + Second;
}
}
FooTypeConverter.cs
public class FooTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(string));
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string val = value as string;
if (val != null)
{
string[] parts = val.Split(',');
if (parts.Length != 2)
{
// Throw an exception
}
return new Foo { First = parts[0], Second = parts[1] };
}
return null;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(string));
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
Foo val = value as Foo;
if (val != null)
return val.ToString();
return null;
}
}
I figured it out. Here is the solution:
public class MimeFormatElement: ConfigurationElement
{
#region Constructors
/// <summary>
/// Predefines the valid properties and prepares
/// the property collection.
/// </summary>
static MimeFormatElement()
{
// Predefine properties here
_mimeFormat = new ConfigurationProperty(
"mimeFormat",
typeof(MimeFormat),
"*/*",
ConfigurationPropertyOptions.IsRequired
);
_properties = new ConfigurationPropertyCollection {
_mimeFormat, _enabled
};
}
private static ConfigurationProperty _mimeFormat;
private static ConfigurationPropertyCollection _properties;
[ConfigurationProperty("mimeFormat", IsRequired = true)]
public MimeFormat MimeFormat
{
get { return (MimeFormat)base[_mimeFormat]; }
}
}
/*******************************************/
[TypeConverter(typeof(MimeFormatConverter))]
/*******************************************/
public class MimeFormat
{
public string Format
{
get
{
return Type + "/" + SubType;
}
}
public string Type;
public string SubType;
public MimeFormat(string mimeFormatStr)
{
var parts = mimeFormatStr.Split('/');
if (parts.Length != 2)
{
throw new Exception("Invalid MimeFormat");
}
Type = parts[0];
SubType = parts[1];
}
}
public class MimeFormatConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return new MimeFormat((string)value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var val = (MimeFormat)value;
return val.Type + "/" + val.SubType;
}
}
From this point, you have to create the convert sections within the ConvertTo and ConvertFrom methods
public override object ConvertFrom( ITypeDescriptorContext context, CultureInfo culture, object value ) {
if ( value == null )
return null;
try {
if ( value is string ) {
string s = (string)value;
// here is where you look at the string to figure out the MimeFormat
// like so....
return new MimeFormat( s );
}
throw new NotSupportedException( NotSupportedException( value.GetType(), typeof(MimeFormat) );
}
public override object ConvertTo( ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType ) {
if ( value == null )
return null;
MimeFormat p = (MimeFormat)value;
if ( destinationType == typeof( String ) )
return p.ToString();
throw new NotSupportedException( NotSupportedException( typeof(MimeFormat), destinationType ) );
}
EDITED
You also need to override the CanConvert functions as well.
public override bool CanConvertFrom( ITypeDescriptorContext context, Type sourceType ) {
if ( sourceType == typeof( string ) )
return true;
return false;
}
public override bool CanConvertTo( ITypeDescriptorContext context, Type destinationType ) {
if ( destinationType == typeof( string ) )
return true;
return false;
}

Categories