Creating a custom type converter part 3: Types to string
How to use a TypeConverter to provide editing of immutable structs that are properties of containing objects via a PropertyGrid.
It is common practice to create a class that describes
something, a person, a product - some entity or other. Your
application may provide a sublime UI for editing these objects,
or rely on something more basic such as a
However, if you use this approach, you may find that some of
your properties can't be edited. Yet examples abound of
non-simple editing via the grid, such as colours, enumerations
and image selection to name a few.
By making use of the
you can quite easily provide the ability to create richer
editing support for your objects. This first article in this
series will detail how to use
TypeConverter allowing complex
objects to be edited as though they were simple strings.
As with most of my articles, I'm starting with a real world example and a solid required. I need to store units of measurement, so for this I have a simple class that has a pair of properties describing a given measurement.
A fairly standard class that simply has two properties along
with a default (implicit) constructor. I'm also overriding
ToString, as it's useful both for debugging purposes and for
having something other than
displayed in the
And for the purposes of this demonstration, I have created a sample class which has three length properties.
Just for completeness sake, here's the
Isn't that an ugly enum? For this example, it will suffice, but there is another article which describes an alternative approach.
I've set up a sample project which binds an instance of our
SampleClass to a
PropertyGrid, with the
pre-set to 32px. When you run this project, you are left
with a very unsatisfactory editing experience as you can't edit
So, what can we do about this?
TypeConverterAttribute allows you to associate your class
with a type that can handle conversion of instances of your
type to and from other objects. You can only have one occurrence
of this attribute per type. As with a lot of these types of
attributes, you can provide the conversion type one of two ways:
Here, we pass in a type object, meaning the type has to be directly referenced by your project and distributed as a dependency.
Another alternative is to use a direct string, as shown above. This string is the fully qualified type name, meaning it could be located in a differently assembly, but one that isn't referenced directly or flagged as a dependency.
Which one you use depends on your needs, but bear in mind no compile time checking can be done of the string version, so if you get the name wrong, you won't find out until you are unable to edit the type!
This class is built into the .NET Framework and will provide a minimum of functionality at minimum cost.
If we change the declaration of our
Length class to be the
above and run our sample, we get this:
The first property can now be expanded, and each property of the
Length class can be individually set. However, there are two
immediate problems with this approach:
Again, depending on your requirements, this might be perfectly acceptable. In my case, it isn't, so on with the custom converter!
In order to create a custom converter, you need to have a class
which inherits from
TypeConverter. At a minimum, you would
Here's a sample converter for our simple
So, what is this short class doing?
The first override,
CanConvertFrom, is called when .NET wants
to know if it can convert from a given type. Here, I'm saying
"if you are a string, then yes I can convert" (or at least
try!), otherwise it falls back and requests if the base
converter can do the conversion. In most cases that'll probably
be a "no", but it's probably a good idea to leave it in
Now for the interesting method.
ConvertFrom does the type
conversion. I'm going to ignore the context parameter for now
as I haven't had a need for it. You can use the culture
parameter as a guide if you need to do any conversions such as
numbers or dates. The key parameter, is value as this contains
the raw data to convert.
Lengthobject and use object initialization to set the
Valueproperty to be the first part of the string converted to a float, and
Enum.Parseto set the
Unitproperty using the latter part of the string. And that explains the horribly named enum. I'll still show you a better way though!
And that is all you need. Well almost, we need to change our class header:
Now when we run the sample project, we can directly type in a
value into the different
Length based properties and have them
converted to the correct values, including creating new values.
Note that this example doesn't cover clearing a value - for example if you enter an empty string. You could return a new
Lengthobject in this case and then change the
ToStringmethod to return an empty string. Simply returning
ConvertFromdoesn't actually work, so at the moment I don't know the best method for accomplishing a value reset.
I haven't demonstrated error handling, firstly as this is a bare bones example, and also due to .NET providing it for you, at least in the case of the property grid. It will automatically handle the failure to convert a value. The disadvantage is the rather unhelpful error message. If you throw an exception yourself, the exception text you provide is displayed in the Details section of the dialog, allowing you to specifying a more succinct message.
As well as converting a type into our class, we can also use a
type converter to convert our class into another type by
In this example, the
Length class overrides the
method. I would still recommend doing that in additional to this
next tip, but as with everything, it depends on your purpose. In
this case, we can use the
ConvertTo method to convert our
Length object into a string.
As with all the methods you override, if you can't explicitly
handle the passed values, then ask the base class to attempt to
handle it. The above method shows how I check to ensure the
value is a
Length object, and then if the destinationType
is a string, I simply return
value.ToString(). Whatever is
returned via this method will appear in the
use caution if you decide to return formatted strings - you'll
need to handle them in
There is another, more useful, purpose for this override, but I'll defer that for the next article.
Adding a basic type converter is a very simple thing to do, and
is something that can help enrich editing functionality, or even
debugging (in lieu of an immediate window or scripting support,
Cyotek products have a sample add-in which displays documents in
PropertyGrid for simple editing and querying). Even if you
only go as far as adding the
attribute to a base class, it's more useful than nothing!
You can download the complete example from the link below.
Like what you're reading? Perhaps you like to buy us a coffee?