Creating a custom type converter part 3: Types to string
I have discussed creating type converters a few times on this
blog, and recently came across another use for them. I have a
.NET Standard library that uses a variety of struct
instances
that are sometimes properties of concrete classes. I also have a
.NET Framework 4.7.2 demonstration application to work with the
library that uses the PropertyGrid
control to provide easy
editing facilities.
Note: While in this article I'm talking about using type converters purely in relation for use with the
PropertyGrid
control, this isn't their only purpose (just as well otherwise they wouldn't be a part of .NET Standard I assume). You can learn more about theTypeConverter
class on MSDN.
Why not ExpandableObjectConverter
In a previous article, I noted you could use the ExpandableObjectConverter type to provide a reasonably decent editing experience for basic objects out the box. However, this doesn't work for structs, usually for one of two reasons
- A well designed
struct
is generally immutable - A
struct
is a value type, meaning that thePropertyGrid
is actually editing a copy of the value, not the original
There is also a related issue - the PropertyGrid
will default
to calling ToString
on the source type. If you haven't
overrode ToString
, then this will default to the name of the
type but even if you have it is possible that you followed the
conventions of similar types and return a list of names and
values, great for debugging purposes but less so from the end
user in a consumer application.
In short, there isn't an "easy fix" to allow easy editing of value types. But the solution isn't onerous or convoluted.
Creating a custom Type Converter
For the purposes of this article, I'm going to be working with a
read-only struct named UserTuple
, using the following
definition
[TypeConverter(typeof(UserTupleConverter))]
struct UserTuple
{
UserTuple(double x, double y, double z, double w)
double X { get; }
double Y { get; }
double Z { get; }
double W { get; }
static UserTuple Parse(string value);
bool TryParse(string value, out UserTuple result);
string ToString() => "{X=" + _x + ",Y=" + _y + ",Z=" + _z + ",W=" + _w + "}";
}
This in turn is used by the fictitious UserClass
type.
class UserClass : INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
UserTuple TupleA { get; set; }
UserTuple TupleB { get; set; }
void OnPropertyChanged(string propertyName)
}
I have also created a UserTupleConverter
class that inherits
from TypeConverter
and is bound to the UserTuple
type via
the TypeConverter
attribute.
class UserTupleConverter : TypeConverter
{
}
The full types are in the available demonstration download.
I originally covered the basics of creating a type converter in parts 1 and 2 of this series. However, to make this article standalone I'll re-cover the basics.
At the most basic level, we have a class that inherits from
TypeConverter
and we override either the CanConvertFrom
and
ConvertFrom
method pair, or the CanConvertTo
and ConvertTo
pair, or both.
The ConvertFrom
method is used to create an instance of our
type from another value - often (as in this example), a
string
.
The ConvertTo
method is used to take an instance of our type
and convert it
to another value. Again, this example is going to use a string
but you can do
many things - including design time code generation.
Although I won't cover it here, there are some more advanced features you can do, for example making your type "expandable" so that you can edit individual properties, or providing pre-defined standard values.
Converting a String into a Value
To provide our string
to UserTuple
functionality, we first
override CanConvertFrom
and make sure we return true
if the
sourceType
is string
.
For performing the actual conversion, we override ConvertFrom
,
see if the passed value
is a string and if so call our Parse
function to perform the conversion.
Note that I am calling
Parse
and notTryParse
. This is so if the user enters an invalid string, they'll get a (hopefully!) tailored exception message - otherwise the exception will be worded similar to "UserTupleConverter cannot convert from System.String.", which is clearly incorrect.
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)
{
object result;
if (value is string s && !string.IsNullOrEmpty(s))
{
result = UserTuple.Parse(s);
}
else
{
result = null;
}
return result ?? base.ConvertFrom(context, culture, value);
}
That provides the first part of the functionality we need - I can now type in a separated list of values and have a brand new value created and assigned, thus neatly bypassing both of the problems outlined in the list at the start of the article.
However, as you can see in the animation it is hardly a neat
process due to the inclusion of all the additional data from
ToString
.
Converting a Value into a String
To clean up the raw text displayed in the PropertyGrid
, we can
make use of the CanConvertTo
and CanConvertTo
methods. I
touched on these in a previous post regarding generating design
time code but glossed over the string aspects.
It seems you don't need to override
CanConvertTo
for thestring
type as the baseTypeConvertor
class has a defaultToString
implementation, but I choose to include it below for completeness.
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)
{
object result;
if (value is UserTuple tuple && destinationType == typeof(string))
{
result = string.Format("{0}, {1}, {2}, {3}", tuple.X, tuple.Y, tuple.Z, tuple.W);
}
else
{
result = base.ConvertTo(context, culture, value, destinationType);
}
return result;
}
First I test to see if the value
is a UserTuple
and if it is
I return a manually constructed comma separated list of the four
properties.
I'm not actually sure what best practice is in this situation. Certainly, I wouldn't want the parsing functionality part of this class as it has definite use outside of it, hence having
Parse
andTryParse
methods on the core type. But for this "simpler" string representation I have no idea and have chosen to keep it a private part of the converter class.
And that's it - with this simple converter in place, my UI behaves exactly as I wanted.
Downloading the demonstration
A demonstration application is available from the link below.
Note that as always, this is demonstration code and therefore
may elide extended details or contain less than stellar
functionality (for example the parsing of the UserTuple
value
is neither efficient or locale aware).
Update History
- 2019-07-21 - First published
- 2020-11-22 - Updated formatting
Related articles you may be interested in
Downloads
Filename | Description | Version | Release Date | |
---|---|---|---|---|
CustomTypeConverter3Strings.zip
|
Sample project for the Creating a custom type converter part 3: Types to string blog post. |
21/07/2019 | Download |
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?
Comments
Bill
#
re: custom-type-converter-part-3-types-to-string
I really appreciate your excellent articles ! I notice that on this page the code for 'Parse is not visible; in the actual code, you never use the version of 'TryParse you define.
If you are going to take this further, I'd really enjoy seeing an example with a Dictionary of Enum Values (keys) and Colors (values) that you can edit in the PropertyGrid.
cheers, Bill
Richard Moss
#
Hello,
Thanks for the comment! As is my habit, I wrote this demonstration program whilst writing the article itself. Originally I was doing
TryParse
, but as I was writing that part of the article my brain kicked in and reminded me that silently ignoring user input is a silly idea. So I changed it toParse
instead to get the exception, but plain forgot to change the signature at the start of the article. Fortunately the downloadable sample contains everything and is correct. The class is based on an actual type I wrote for another project. I do actually have one more part to write based on some other experiences (and it just so happens one of these is for a customColor
type) so hopefully you'll enjoy the next part too.Thanks again for the comments, it's a pleasure to get a decent comment instead of the usual buckets of spam this blog attracts.
Regards;
Richard Moss
kamal
#
Very great efforts. Thank you much much. Please keep more updates for TypeConverter. And better serialization of winforms designers generator. Thank you