Universal TypeConverter for PropertyGrid property - c#

I have a problem. I'm trying to add properties to propertyGrid control, with its correct type converter.
I'm using reflection, so actually i know what exactly type of value came only when i'm initializing my propertyGrid.
Something like that:
public SettingsProperty(SettingsFieldValue value, SettingsFieldInfo info)
{
Value = value;
Info = info;
_defaultValue = Info.DefaultValue;
Name = Info.Label;
Description = Info.Description;
PropertyName = Info.FieldName;
TypeName = Info.FieldType.ToString();
CurrentValue = GetValueAsString(Value);
DefaultValue = GetValueAsString(_defaultValue);
}
So. It can be bool, int, double, array, dictionary or complex object with its nested properties.
Is there any way to create converter for that property at runtime?
Like, if came object, it became expandableTypeConverter. If boolean - boolean converter etc.
Thanks for any help or thoughts.

Related

Microsoft.Office.Interop.Word in .NET 6.0 - Change builtin document property

Using the Microsoft.Office.Interop.Word.Document interface in C# you can get/set the document's built-in properties (Author, Title, Subject, Creation date,...) with this code:
dynamic docprops = document.BuiltInDocumentProperties;
dynamic prop = docprops[WdBuiltInPropertyEnum];
prop.Value = VALUE;
Marshal.ReleaseComObject(prop);
Marshal.ReleaseComObject(docprops);
which works fine if the VALUE is string. When the value is the System.DateTime, the prop.Value (for example WdBuiltInProperty.wdPropertyTimeLastPrinted) is set to a new value, but when I read the same property again like
dynamic docprops = document.BuiltInDocumentProperties;
dynamic prop = docprops[WdBuiltInPropertyEnum];
VALUE = prop.Value;
the VALUE returns unchanged date. Any idea why the property of type date is not updated?
Thanks for any help.

How to get TypeConverter of property

I have this property:
[DisplayName("Conexión")]
[TypeConverter(typeof(Converters.DevicesTypeConverter))]
[Description("Conexión con el dispositivo a usar.")]
[Required]
public string Conexion { get; set; }
I need to get the type converter instance of it. I tried with :
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(this);
PropertyDescriptor property = properties.Find("Conexion", false);
var converter = TypeDescriptor.GetConverter(property);
And even with:
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(this);
PropertyDescriptor property = properties.Find("Conexion", false);
var converter = TypeDescriptor.GetConverter(property.PropertyType);
With that, I can only get converter of the property type, that is, the converter for string type, not the actual property converter, DevicesTypeConverter.
Any help please?
EDIT:
What I am trying to do is the following. I have 2 properties in the class that I need to set by mean of a property grid.
"Conexion" property is a list of values that depends on other property.
This dependency works well this way:
When the other property changes, and then I expand the "Conexion" property, GetStandardValuesSupported method on "Conexion" is called. Inside that method I use "context.Instance" to call a method on the object instance that retrieves the list this way:
public List<object> GetDevicesList()
{
if (this.Driver == null)
return null;
var devices = this.Driver.GetList();
if (devices == null)
return null;
return devices.Select(l => (object)l.Value).ToList();
}
The returned list is stored in a private variable in the converter, so this perfectly show the list that depends on the other property.
So far so good. The problem occurs when the object already has value in its properties. Since the list of "Conexion" is exclusive, the property value of it, when I assign it to the property grid, appears empty.
This is obvious because the dependent list only get populated when GetStandardValuesSupported is called, and that happens only when I try to edit the property.
Now I need what I asked in the question. I need to call GetStandardValuesSupported explicitly in object constructor in order to force the dependent list to be loaded before the "Conexion" property is assigned. With that, I am sure the property will appear initialized since the list will have its value.
I think your solution should work but the GetConverter returns null. The problem is only reduced to call the GetStandardValuesSupported() of my custom type converter so I could also use Activator.CreateInstance but the problem is that the type of the converter is null and not the type of DevicesTypeConverter.
You have to jump through several layers to get an instance of the TypeConverter specified in the TypeConverterAttribute (LINQPad demo):
//Get the type you are interested in.
var type = typeof(MyClass);
//Get information about the property you are interested in on the type.
var prop = type.GetProperty("Conexion");
//Pull off the TypeConverterAttribute.
var attr = prop.GetCustomAttribute<TypeConverterAttribute>();
//The attribute only stores the name of the TypeConverter as a string.
var converterTypeName = attr.ConverterTypeName;
// Get the actual Type of the TypeConverter from the string.
var converterType = Type.GetType(converterTypeName);
//Create an instance of the TypeConverter.
var converter = (TypeConverter) Activator.CreateInstance(converterType);
You can use your existing method of getting the converter now that you have the type:
var converter = TypeDescriptor.GetConverter(converterType);
You were close. Once you have the PropertyDescriptor for the property you're interested in, use the Converter property to get the Converter.
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(this);
PropertyDescriptor propertyDescriptor = properties.Find("Conexion", false);
propertyDescriptor.Converter // <- This is what you want

how to cast this string list to an object

If I have this string list:
string myObjectString = "MyObject, SetWidth, int, 10, 0, 1";
in which:
- MyObject: the object class
- SetWidth: the property of the object
- int: type of the SetWidth is int
- 10: default value
- 0: object order
- 1: property order
Then how can I construct an object like this:
[ObjectOrder(0)]
public class MyObject:
{
private int _SetWidth = 10;
[PropertyOrder(1)]
public int SetWidth
{
set{_SetWidth=value;}
get{return _SetWidth;}
}
}
So, I would like have something like this:
Object myObject = ConstructAnObject(myObjectString);
and the myObject is an instance of MyObject. Could it be possible in C#?
Thanks in advance.
I think you better use the Object Serialization/Deserialization instead of creating a custom method that basically needs to do the same thing
more info at:
http://msdn.microsoft.com/en-us/library/ms233843.aspx
Here is some quick and dirty code to get you started:
string myObjectString = "MyObject, SetWidth, int, 10, 0, 1";
var info = myObjectString.Split(',');
string objectName = info[0].Trim();
string propertyName = info[1].Trim();
string defaultValue = info[3].Trim();
//find the type
Type objectType = Assembly.GetExecutingAssembly().GetTypes().Where(t=>t.Name.EndsWith(objectName)).Single();//might want to redirect to proper assembly
//create an instance
object theObject = Activator.CreateInstance(objectType);
//set the property
PropertyInfo pi = objectType.GetProperty(propertyName);
object valueToBeSet = Convert.ChangeType(defaultValue, pi.PropertyType);
pi.SetValue(theObject, valueToBeSet, null);
return theObject;
This will find the MyObject, create an object of the proper propertytype, and set the matching property.
If you use C# 4.0, you can use the new dynamic feature.
string myObjectString = "MyObject, SetWidth, int, 10, 0, 1";
String[] properties = myObjectString.Split(',');
dynamic myObj;
myObj.MyObject = (objtect)properties[0];
myObj.SetWidth = Int32.Parse(properties[1]);
// cast dynamic to your object. Exception may be thrown.
MyObject result = (MyObject)myObj;
I don't quite understand why do you need ObjectOrder and PropertyOrder... Once you have their names you probably don't need them, at least for "deserialization"...
Or please advice what is their role?
You definitely can simply do it via reflection:
Split the string by comma (using myString.Split)
Use reflection to find an object within your application:
Find the type with name = splittedString[0] (enumerate all the assemblies within the domain and all the types within each assembly);
Instantiate the type found (using Activator.CreateInstance)
Find the property by name (Using objectType.GetProperty)
Set the property value (using propertyInfo.SetValue)
Return the object
Assuming you need to generate new types there are two possible ways to do so:
Using Reflection Emit
Using CodeDom provider
I think the simpler solution is CodeDom provider. All needed is to generate the source as a string in memory, and then compile the code and instantiate a new instance with Activator. This is a nice example I just found.
The reason I think that CodeDom provider is simpler is that it has shorter setup - no need to generate dynamic module and assembly and then work with type builder and members builder. In addition, it doesn't require working with IL to generate the getter and setter bodies.
An advantage that reflection emit has is performance - dynamic module can add more types to itself even after one of the types was used. CodeDom provider requires creating all the types at once, otherwise it creates a new assembly each time.

Know the types of properties in an object c#

I know how to get an object properties using reflection:
var properties = typeof(T).GetProperties();
now how do I know if properties[0] is a string? or maybe it is a int? how can I know?
Each element of properties will be a PropertyInfo, which has a PropertyType property, indicating the type of the property.
So for example, you might use:
if (properties[0].PropertyType == typeof(string))
or if you wanted to check something in an inheritance-permitting way:
if (typeof(Stream).IsAssignableFrom(properties[0].PropertyType))

Using reflection with generic types and implicit conversions

I'm trying to use reflection to set properties on some OpenXML types (e.g. Justification). Assigning a value by enumerating all possibilities is straight-forward:
// attr is an XmlAttribute, so .Name and .Value are Strings
if (attr.Name == "Val")
{
if (element is Justification)
{
((Justification)element).Val = (JustificationValues)Enum.Parse(typeof(JustificationValues), attr.Value);
return;
}
else
{
// test for dozens of other types, such as TabStop
}
}
What makes this difficult to do via reflection is:
1) The type of the Val property is EnumValue<T>, so I don't know how to extract the type to pass as the first argument to Enum.Parse.
2) There is an implicit conversion from the actual enumeration type to the EnumValue<> type, which I don't know how to invoke with reflection.
I would like the code to end up looking something like:
PropertyInfo pInfo = element.GetType().GetProperty(attr.Name);
Object value = ConvertToPropType(pInfo.PropertyType, attr.Value); /* this
would return an instance of EnumValue<JustificationValues> in this case */
pInfo.SetValue(element, value, null);
How do I implement ConvertToPropType? Or is there a better solution?
Thanks
Edit:
I got a solution working using Earwicker's suggestion, but it relies on the convenient fact that the enumeration's type name can be derived from the node's type name ("Justification" -> "JustificationValues"). I'm still curious how to solve this in the general case, though.
Edit2:
GetGenericArguments got me the rest of the way there. Thanks.
If the attribute value is just a string, I assume you already have some way of figuring out that the string identifies a value from a specific enumeration. In your example you have it hard-coded, so I'm not sure if that's what you want or what you want to change.
Assuming you know it's an enumeration, and you know which enumeration, you already know how to get an object containing a boxed value of the right enum type, as in your snippet.
Now if I assume EnumValue<T> has a constructor that takes a T.
Type genericType = typeof(EnumValue<>);
Type concreteType = genericType.MakeGenericType(typeof(JustificationValues));
Now concreteType is the type EnumValue<JustificationValues>.
From that you can get a constructor, hopefully one that takes a JustificationValues parameter, and Invoke it.
Update
Ahh, I see what you're doing now. You use the XML attribute name to pick a C# property. You need to be able to detect whether that property is of a type EnumValue<T>, and find out what T is.
PropertyInfo p = // ... get property info
Type t = p.GetType();
if (t.IsGenericType &&
t.GetGenericTypeDefinition == typeof(EnumValue<>))
{
Type e = t.GetGenericArguments()[0]; // get first (and only) type arg
// e is the enum type...
Give that a try.
.Net 4.0 adds support to do a late bound implict or explict conversion. This is simplified in the open source framework ImpromptuInterface with it's static method called InvokeConvert. In your ideal example it would work like this:
PropertyInfo pInfo = element.GetType().GetProperty(attr.Name);
Object value = Impromptu.InvokeConvert(attr.Value, pInfo.PropertyType);
pInfo.SetValue(element, value, null);
This might only work with basic types but it was good enough for what I'm doing
PropertyInfo pInfo = element.GetType().GetProperty(attr.Name);
Object value = System.Convert.ChangeType(attr.Value, pInfo.PropertyType);
pInfo.SetValue(element, value, null);

Categories