Thursday, 19 February 2009

Integers As Hex In The PropertyGrid (C# .NET)





I've been working on a utility at work that makes heavy use of the PropertyGrid in C#. One of my collegues asked that the program be able to display the numbers in hexadecimal.



I noticed a couple of articles online that showed how to do this using TypeConverter classes attached directly the property you want converted e.g.


using System;
using System.ComponentModel;

namespace PropertyGridHexNumeric
{
class TestClass
{
[TypeConverter(typeof(IntToHexTypeConverter))]
public int IntValue { get; set; }


public TestClass() { }
}
}
This works well, except you have to apply the TypeConverter (in this case IntToHexTypeConverter -- defined furthur down) to every property that you want the PropertyGrid to display in hex. This works because when the PropertyGrid gets a value for a property from a class it checks if there are any TypeConverters attached to the property and tries to use them to do the conversion, which is almost always a conversion to a string.

To get around this problem I experimented with the TypeDescriptor static members and noticed that there is a "GetProvider" and "AddProvider" pair. These appear to set global providers. By creating a custom TypeDescriptionProvider and using TypeDescriptor.AddProvier(...) you are able to override the TypeConverter used for all values of the specified type.

For this example I'll use an int. Given a class like so:

using System;
using System.ComponentModel;

namespace PropertyGridHexNumeric
{
class TestClass
{
public int IntValue { get; set; }

public TestClass() { }
}
}

We want to display "IntValue" in hex in the PropertyGrid but without using a TypeConverter attached directly to the IntValue property. I'll spare you the pain of trying to figure this out, create a new Form with one Checkbox and one PropertyGrid, add a handler for the Checkbox.CheckedChanged event and use the following code:
using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace PropertyGridHexNumeric
{
public partial class Form1 : Form
{
private TypeDescriptionProvider m_OriginalProvider = null;

public Form1()
{
InitializeComponent();

propertyGrid1.SelectedObject = new TestClass();
}

private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked)
{
m_OriginalProvider = TypeDescriptor.GetProvider(typeof(int));
IntToHexTypeDescriptionProvider hexProvider = new IntToHexTypeDescriptionProvider(m_OriginalProvider);
TypeDescriptor.AddProvider(hexProvider, typeof(int));
}
else
{
TypeDescriptor.AddProvider(m_OriginalProvider, typeof(int));
}

propertyGrid1.Refresh();
}
}
   public class IntToHexTypeDescriptionProvider : TypeDescriptionProvider
   {
private IntToHexCustomTypeDescriptor m_Descriptor = new IntToHexCustomTypeDescriptor();

public IntToHexTypeDescriptionProvider(TypeDescriptionProvider parent) :
base(parent) { }

public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
if (objectType == typeof(int))
{
return m_Descriptor;
}
else
{
return base.GetTypeDescriptor(objectType, instance);
}
}
}

public class IntToHexCustomTypeDescriptor : CustomTypeDescriptor
{
private IntToHexTypeConverter m_Converter = new IntToHexTypeConverter();
public override TypeConverter GetConverter()
{
return m_Converter;
}
}

public class IntToHexTypeConverter : 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 override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string) && value.GetType() == typeof(int))
{
return string.Format("0x{0:X8}", value);
}
else
{
return base.ConvertTo(context, culture, value, destinationType);
}
}

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value.GetType() == typeof(string))
{
string input = (string)value;

if (input.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
input = input.Substring(2);
}

return int.Parse(input, NumberStyles.HexNumber, culture);
}
else
{
return base.ConvertFrom(context, culture, value);
}
}
}
}


This should do the trick, now you can use the checkbox to toggle the hex display of ints in the PropertyGrid, this will work for ANY object you set the PropertyGrid to display! Of course you can apply this method to any Type and I'm sure you will want to add uint, short, ushort etc to the TypeDescriptor so you can see all numeric values in hex.

If you only want to make the hex values readonly you can choose not to override the CanConvertFrom method and the PropertyGrid will prevent you from entering anything.