I have a custom control with the following property
public ulong Mask { get; set; }
When I use the control, the property shows up in the editor as a decimal number.
Is there a way to display this property value as a hexidecimal? If there's a way to break up the hexidecimal number into groups of four digits, that would be even better. Thanks!
The UInt64Converter Class provides most of what you need as it supports conversions from a hexadecimal format. All that is necessary is to override the ConvertTo to method to display as hexadecimal.
public class UInt64HexConverter : UInt64Converter
{
private static Type typeUInt64 = typeof(UInt64);
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null)
{
throw new ArgumentNullException("destinationType");
}
if (((destinationType == typeof(string)) && (value != null)) && typeUInt64.IsInstanceOfType(value))
{
UInt64 val = (UInt64)value;
return "0x" + val.ToString("X");
}
if (destinationType.IsPrimitive)
{
return Convert.ChangeType(value, destinationType, culture);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
Example usage:
class BitControl : Control
{
[TypeConverter(typeof(UInt64HexConverter))]
public ulong Mask { get; set; }
}
Related
As a followup to this question on SO, I need help making a property grid interact with the user the way I want.
I have a property grid display formatted values as such
that come from class properties of type double
public interface IDisplayUnits
{
/// <summary>
/// The current unit system
/// </summary>
UnitSystem Units { get; }
bool UseRounding { get; set; }
string Formatting { get; set; }
}
class TestUnits : IDisplayUnits
{
public double A {get; set;}
public double B {get; set;}
public double C => A+B;
public bool UseRounding { get; set; }
public string Formatting { get; set; }
public UnitSystem Units { get; set; }
}
public enum UnitSystem
{
Metric,
Inch
}
and implementing a custom TypeConverter that goes between double and string using the formatting I want.
Now what I want is, when the user edits a value to edit the raw (unformatted) value and not the rounded formatted value. For example when editing the A field to look like this
I have not been able to hook an event that triggers when the users starts to edit, or presses F4. The event PropertyValueChanged triggers after the edit.
It should be possible to do this, maybe by implementing a custom UITypeEditor?
Here is the current TypeConverter code that I have:
public class UnitValueConverter : TypeConverter
{
// This checks if conversion is possible based on types only
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(string))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
// This checks if conversion is possible based on specific values
public bool CanConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value, out double result)
{
if(value is string input)
{
var parts = input.Split(' ');
var has = context.Instance as IDisplayUnits;
// Custom extension that parses a text like "10 mm"
// into `x=10.0` and `sys = UnitSystem.Metric`
if(input.TryParse(out double x, out UnitSystem sys))
{
// irrelevent code for unit conversions
double f = Factor(sys, has.Units);
result = f*x;
return true;
}
}
result = 0;
return false;
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if(CanConvertFrom(context, culture, value, out double result))
{
return result;
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if(destinationType==typeof(string))
{
double.TryParse(value.ToString(), out double x);
if(context.Instance is IDisplayUnits has)
{
var f = has.UseRounding ? has.Formatting : "g";
// irrelevent code for formatting values with units
return Show(x, f);
}
return x.ToString("g");
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
PS. I have seen commercial software out there that does exactly this.
I have following enumeration.
public enum Digits
{One, Two, Three}
and a property with two entries.
public List<Digits> DigitList{get;set;}
DigitList.Add(Digits.One); DigitList.Add(Digits.Three);
When this object is bound to PropertyGrid it is displayed as (Collection) and when it is opened (using small browse button) an exception with (no useful message) is displayed. I am confused how the PropertyGrid interprets list of enumerations.
I searched for a solution, but all i could find was about how to bind a enum value, not list of enums.
You have to create a TypeConverter Class that will help the PropertyEditor to parse the Enum into a PropertyEditor.
Sample TypeConverter
public class FooDataTypeConverter : TypeConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return base.GetStandardValues(context);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return (sourceType.Equals(typeof(Enum)));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType.Equals(typeof(String)));
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
return (ValidationDataType)Enum.Parse(typeof(ValidationDataType), value.ToString(), true);
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (!destinationType.Equals(typeof(string)))
throw new ArgumentException("Can only convert to string", "destinationType");
if (!value.GetType().BaseType.Equals(typeof(Enum)))
throw new ArgumentException("Can only convert an instance of enum", "value");
string name = value.ToString();
object[] attr =
value.GetType().GetField(name).GetCustomAttributes(typeof(DescriptionAttribute), false);
return (attr.Length > 0) ? ((DescriptionAttribute)attr[0]).Description : name;
}
}
After add this declaration to the enums that you want to parse in a propertyeditor.
[TypeConverter(typeof(FooDataTypeConverter ))]
public enum ValidationDataType
{
/// <summary>
/// None
/// </summary>
[Description("None")]
None,
.....
}
The last step is to add it to the property of your component that will show in the propertyeditor
[Category("Behavior")]
[Description("Gets or sets the type of data that will be compared")]
[TypeConverter(typeof(DataTypeConverter))]
[EditorAttribute(typeof(ValidatorTypeEditor), typeof(System.Drawing.Design.UITypeEditor))]
public ValidationDataType Type
{
get { return this.type; }
set
{
this.type = value;
if (this is RangeValidator)
{
this.SetRange();
}
}
}
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.
I'm using some third party code which uses TypeConverters to "cast" objects to types specified as generic parameters.
The 3rd party code gets the string type converter, and expects to do all conversions through that e.g.
var typeConverter = TypeDescriptor.GetConverter(typeof(string));
I've written a custom type, and type converter for it (and registered it with the TypeDescriptor attribute) but it's not getting used by the 3rd party code, which fails on the call to typeConverter.CanConvertTo(MyCustomType)
Until today I'd only encountered TypeConverters in the abstract, I've seen mentions of them but never built or used one.
Has anyone any idea what I'm doing wrong here?
My - cut down - code
using System;
using System.ComponentModel;
namespace IoNoddy
{
[TypeConverter(typeof(TypeConverterForMyCustomType))]
public class MyCustomType
{
public Guid Guid { get; private set; }
public MyCustomType(Guid guid)
{
Guid = guid;
}
public static MyCustomType Parse(string value)
{
return new MyCustomType(Guid.Parse(value));
}
}
public class TypeConverterForMyCustomType
: TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return ((sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType));
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
string strValue;
if ((strValue = value as string) != null)
try
{
return MyCustomType.Parse(strValue);
}
catch (FormatException ex)
{
throw new FormatException(string.Format("ConvertInvalidPrimitive: Could not convert {0} to MyCustomType", value), ex);
}
return base.ConvertFrom(context, culture, value);
}
}
}
static void Main(string[] args)
{
// Analogous to what 3rd party code is doing:
var typeConverter = TypeDescriptor.GetConverter(typeof(string));
// writes "Am I convertible? false"
Console.WriteLine("Am I convertible? {0}", typeConverter.CanConvertTo(typeof(MyCustomType)));
}
You check CanConvertTo so add to yours converter:
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(MyCustomType)) || base.CanConvertTo(context, destinationType);
}
to some where:
public static void Register<T, TC>() where TC : TypeConverter
{
Attribute[] attr = new Attribute[1];
TypeConverterAttribute vConv = new TypeConverterAttribute(typeof(TC));
attr[0] = vConv;
TypeDescriptor.AddAttributes(typeof(T), attr);
}
and to main:
Register<string, TypeConverterForMyCustomType>();
var typeConverter = TypeDescriptor.GetConverter(typeof(string));
yours sample shuld work after that.
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))]