Data bind enum properties to grid and display description - c#

This is a similar question to How to bind a custom Enum description to a DataGrid, but in my case I have multiple properties.
public enum ExpectationResult
{
[Description("-")]
NoExpectation,
[Description("Passed")]
Pass,
[Description("FAILED")]
Fail
}
public class TestResult
{
public string TestDescription { get; set; }
public ExpectationResult RequiredExpectationResult { get; set; }
public ExpectationResult NonRequiredExpectationResult { get; set; }
}
I'm binding a BindingList<TestResult> to a WinForms DataGridView (actually a DevExpress.XtraGrid.GridControl, but a generic solution would be more widely applicable). I want the descriptions to appear rather than the enum names. How can I accomplish this? (There are no constraints on the class/enum/attributes; I can change them at will.)

A TypeConverter will usually do the job; here's some code that works for DataGridView - just add in your code to read the descriptions (via reflection etc - I've just used a string prefix for now to show the custom code working).
Note you would probably want to override ConvertFrom too. The converter can be specified at the type or the property level (in case you only want it to apply for some properties), and can also be applied at runtime if the enum isn't under your control.
using System.ComponentModel;
using System.Windows.Forms;
[TypeConverter(typeof(ExpectationResultConverter))]
public enum ExpectationResult
{
[Description("-")]
NoExpectation,
[Description("Passed")]
Pass,
[Description("FAILED")]
Fail
}
class ExpectationResultConverter : EnumConverter
{
public ExpectationResultConverter()
: base(
typeof(ExpectationResult))
{ }
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value,
System.Type destinationType)
{
if (destinationType == typeof(string))
{
return "abc " + value.ToString(); // your code here
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
public class TestResult
{
public string TestDescription { get; set; }
public ExpectationResult RequiredExpectationResult { get; set; }
public ExpectationResult NonRequiredExpectationResult { get; set; }
static void Main()
{
BindingList<TestResult> list = new BindingList<TestResult>();
DataGridView grid = new DataGridView();
grid.DataSource = list;
Form form = new Form();
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
Application.Run(form);
}
}

I'm not sure how much this helps, but I use an extension method on Enum that looks like this:
/// <summary>
/// Returns the value of the description attribute attached to an enum value.
/// </summary>
/// <param name="en"></param>
/// <returns>The text from the System.ComponentModel.DescriptionAttribute associated with the enumeration value.</returns>
/// <remarks>
/// To use this, create an enum and mark its members with a [Description("My Descr")] attribute.
/// Then when you call this extension method, you will receive "My Descr".
/// </remarks>
/// <example><code>
/// enum MyEnum {
/// [Description("Some Descriptive Text")]
/// EnumVal1,
///
/// [Description("Some More Descriptive Text")]
/// EnumVal2
/// }
///
/// static void Main(string[] args) {
/// Console.PrintLine( MyEnum.EnumVal1.GetDescription() );
/// }
/// </code>
///
/// This will result in the output "Some Descriptive Text".
/// </example>
public static string GetDescription(this Enum en)
{
var type = en.GetType();
var memInfo = type.GetMember(en.ToString());
if (memInfo != null && memInfo.Length > 0)
{
var attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs != null && attrs.Length > 0)
return ((DescriptionAttribute)attrs[0]).Description;
}
return en.ToString();
}
You could use a custom property getter on your object to return the name:
public class TestResult
{
public string TestDescription { get; set; }
public ExpectationResult RequiredExpectationResult { get; set; }
public ExpectationResult NonRequiredExpectationResult { get; set; }
/* *** added these new property getters *** */
public string RequiredExpectationResultDescr { get { return this.RequiredExpectationResult.GetDescription(); } }
public string NonRequiredExpectationResultDescr { get { return this.NonRequiredExpectationResult.GetDescription(); } }
}
Then bind your grid to the "RequiredExpectationResultDescr" and "NonRequiredExpectationResultDescr" properties.
That might be a little over-complicated, but its the 1st thing I came up with :)

Based on the two other answers, I have put together a class that can generically convert between an arbitrary enum and a string using a Description attribute on each enum value.
This uses System.ComponentModel for the definition of DescriptionAttribute, and only supports conversion between T and String.
public class EnumDescriptionConverter<T> : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(T) || sourceType == typeof(string));
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(T) || destinationType == typeof(string));
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
Type typeFrom = context.Instance.GetType();
if (typeFrom == typeof(string))
{
return (object)GetValue((string)context.Instance);
}
else if (typeFrom is T)
{
return (object)GetDescription((T)context.Instance);
}
else
{
throw new ArgumentException("Type converting from not supported: " + typeFrom.FullName);
}
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
Type typeFrom = value.GetType();
if (typeFrom == typeof(string) && destinationType == typeof(T))
{
return (object)GetValue((string)value);
}
else if (typeFrom == typeof(T) && destinationType == typeof(string))
{
return (object)GetDescription((T)value);
}
else
{
throw new ArgumentException("Type converting from not supported: " + typeFrom.FullName);
}
}
public string GetDescription(T en)
{
var type = en.GetType();
var memInfo = type.GetMember(en.ToString());
if (memInfo != null && memInfo.Length > 0)
{
var attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs != null && attrs.Length > 0)
return ((DescriptionAttribute)attrs[0]).Description;
}
return en.ToString();
}
public T GetValue(string description)
{
foreach (T val in Enum.GetValues(typeof(T)))
{
string currDescription = GetDescription(val);
if (currDescription == description)
{
return val;
}
}
throw new ArgumentOutOfRangeException("description", "Argument description must match a Description attribute on an enum value of " + typeof(T).FullName);
}
}

Related

Looking for a more dynamic designer TypeConverter serialization in a VS10 .NET form

I have a list of a class which I serialize to the designer generated code with the following code:
internal class TargetSettingsConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(InstanceDescriptor) || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor) && value is ControlListManager.TargetSettings)
{
ConstructorInfo constructor = typeof(ControlListManager.TargetSettings).GetConstructor(new[] { typeof(object), typeof(string), typeof(DisplayModes), typeof(bool), typeof(int), typeof(int), typeof(int) });
var target = value as ControlListManager.TargetSettings;
var descriptor = new InstanceDescriptor(constructor, new[] { target.Target, target.Text, target.DisplayMode, target.Fade, target.HideTimeout, target.PaddingX, target.PaddingY }, true);
return descriptor;
}
if (culture == null) { culture = CultureInfo.CurrentCulture; }
return base.ConvertTo(context, culture, value, destinationType);
}
}
This works well so far, but what bothers me is that I have to specify the types and values individually.
My first idea was to use GetConstructors() instead. But then to problem of the values still remains. I'm really confused about this problem since I actually write the function without knowing the amount of parameters - or only if it is "hardcoded".
Does anyone have any idea how this can be done better?
Edit: The data class
[TypeConverter(typeof(TargetSettingsConverter))]
public class TargetSettings : IEquatable<TargetSettings>
{
public object Target = new { };
public string Text;
public DisplayModes DisplayMode = DisplayModes.FollowXY;
public bool Fade = true;
public int HideTimeout;
public int PaddingX;
public int PaddingY;
public bool Equals(TargetSettings other)
{
return other != null && (Target.Equals(other.Target));
}
public override bool Equals(object obj)
{
if (obj == null) { return false; }
TargetSettings objAsPart = obj as TargetSettings;
if (objAsPart == null) { return false; }
return Equals(objAsPart);
}
public override int GetHashCode()
{
return Target.GetHashCode();
}
public TargetSettings(object target, string text = "", DisplayModes displayMode = DisplayModes.FollowXY, bool fade = true, int hideTimeout = 0, int paddingX = 0, int paddingY = 0)
{
Target = target;
Text = text;
DisplayMode = displayMode;
Fade = fade;
HideTimeout = hideTimeout;
PaddingX = paddingX;
PaddingY = paddingY;
}
}
Based on your question, you want to make your code more dynamic for the types and values.
Since the types and values you want to convert are all field types. I recommend that you use the reflection to do it.
You can get all the field types as the following:
Type []tarr = typeof(TargetSettings).GetFields().Select(i => i.FieldType).ToArray();
ConstructorInfo constructor = typeof(TargetSettings).GetConstructor(tarr);
You can get all the field values as the following:
object []oarr= typeof(TargetSettings).GetFields().Select(i => i.GetValue(target)).ToArray();
var descriptor = new InstanceDescriptor(constructor, oarr, true);
Full code:
internal class TargetSettingsConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(InstanceDescriptor) || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(InstanceDescriptor) && value is TargetSettings)
{
Type []tarr = typeof(TargetSettings).GetFields().Select(i => i.FieldType).ToArray();
ConstructorInfo constructor = typeof(TargetSettings).GetConstructor(tarr);
var target = value as TargetSettings;
object []oarr= typeof(TargetSettings).GetFields().Select(i => i.GetValue(target)).ToArray();
var descriptor = new InstanceDescriptor(constructor, oarr, true);
return descriptor;
}
if (culture == null) { culture = CultureInfo.CurrentCulture; }
return base.ConvertTo(context, culture, value, destinationType);
}
}
[TypeConverter(typeof(TargetSettingsConverter))]
public class TargetSettings : IEquatable<TargetSettings>
{
public object Target = new { };
public string Text;
public DisplayModes DisplayMode = DisplayModes.FollowXY;
public bool Fade = true;
public int HideTimeout;
public int PaddingX;
public int PaddingY;
public bool Equals(TargetSettings other)
{
return other != null && (Target.Equals(other.Target));
}
public override bool Equals(object obj)
{
if (obj == null) { return false; }
TargetSettings objAsPart = obj as TargetSettings;
if (objAsPart == null) { return false; }
return Equals(objAsPart);
}
public override int GetHashCode()
{
return Target.GetHashCode();
}
public TargetSettings(object target, string text = "", DisplayModes displayMode = DisplayModes.FollowXY, bool fade = true, int hideTimeout = 0, int paddingX = 0, int paddingY = 0)
{
Target = target;
Text = text;
DisplayMode = displayMode;
Fade = fade;
HideTimeout = hideTimeout;
PaddingX = paddingX;
PaddingY = paddingY;
}
}
public enum DisplayModes
{
FollowXY
}

Property Grid > how to refresh the main property

[TypeConverter(typeof(BrokerageConverter))]
[DescriptionAttribute("Brokerage Details")]
[PropertyGridInitialExpanded(true)]
[RefreshProperties(RefreshProperties.Repaint)]
public class Brokerage
{
private Decimal _Amt = Decimal.Zero; private string _currency = "";
public Brokerage() { }
public Brokerage(Decimal broAmount, string broCurrency) { Amount = broAmount; Currency = broCurrency; }
[ReadOnly(false)]
public Decimal Amount
{
get { return _Amt; }
set { _Amt = value; }
}
[ReadOnly(true)]
public string Currency
{
get { return _currency; }
set { _currency = value; }
}
//public override string ToString() { return _Amt.ToString() + " - " + _currency; }
}
public class BrokerageConverter : ExpandableObjectConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, System.Type destinationType)
{
if (destinationType == typeof(Brokerage))
return true;
return base.CanConvertTo(context, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type t)
{
if (t == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, t);
}
// Overrides the ConvertFrom method of TypeConverter.
public override object ConvertFrom(ITypeDescriptorContext context,CultureInfo culture, object value)
{
if (value is string)
{
string[] v = ((string)value).Split(new char[] { '-' });
return new Brokerage(Decimal.Parse(v[0]), v[1]);
}
return base.ConvertFrom(context, culture, value);
}
// Overrides the ConvertTo method of TypeConverter.
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(System.String) && value is Brokerage)
{
Brokerage b = (Brokerage)value;
return b.Amount.ToString() + " - " + b.Currency.ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
Now when I change the Amount, the Buyer Bro is not updating automatically. How to achieve it? Let me know, if i have to provide some additional info
Adding the attribute "[NotifyParentProperty(true)]" will do the trick:
[ReadOnly(false)]
[NotifyParentProperty(true)]
public Decimal Amount
{
get { return _Amt; }
set { _Amt = value; }
}
Make sure to add
using System.ComponentModel;
Now when you change Amount and loose the focus of this distinct property,
the parent property will automatically be updated.
Cheers!
I had the same issue in the past.
I did put [RefreshProperties(RefreshProperties.Repaint)] or RefreshProperties.All, then I implemented INotifyPropertyChanged on my target objects, but I never managed to get the automatic mechanism it to work correctly.
Apparently, I am not alone.
I ended using the .Refresh() method on the PropertyGrid. Now it works all the time.
var b = new Brokerage(10, "EUR");
this.propertyGrid1.SelectedObject = b;
...
b.Amount = 20;
this.propertyGrid1.Refresh();
Did you try adding,
[RefreshProperties(RefreshProperties.Repaint)]
to the 2 properties inside your Brokerage class?

Edit the display name of enumeration members in a PropertyGrid

I have a property grid that I am using for users to be able to configure objects for any plugin that is written to be used in my application. I would like to be able to tell developers writing plugins to use the ComponentModel Attributes for their members like so:
[CategoryAttribute("On Screen Display Settings"),
DescriptionAttribute("Whether or not to show the session timer."),
DisplayName("Show Session Timer")]
public bool ShowTimer
{
get;
set;
}
This works great. Now I would like for the members of an enumeration to be able to be edited as well. i.e.
public enum Resolution_ : byte
{
DCIF,
CIF,
QCIF,
[DisplayName("4CIF")]
CIF4,
[DisplayName("2CIF")]
CIF2
}
So that they are displayed in the PropertyGrid's list like so:
DCIF
CIF
QCIF
CIF4
CIF2
Along with any Descriptions and Display names they may have with them.
It seems that I can only do this with properties, events, and methods. Does anyone know how I can do this for an enumeration?
You will have to make an EnumConverter class and decorate your property with a TypeConverter attribute in order to do this.
See this Using PropertyGrid in .NET, it's a fun example:
Imagine that you want more than two items in list. The boolean type is not enough; you need to set Description attributes with a name for every element in enum.
enum DrinkDoses {
[Description("Half of litre")]
litre,
[Description("One litre")]
oneLitre,
[Description("Two litres")]
twoLitre,
[Description("Three litres")]
threeLitres,
[Description("Four litres")]
fourLitres,
[Description("Death dose, five litres")]
fiveLitres
}
In another class you need to utilize the type EnumConverter.
class DrinkDosesConverter : EnumConverter {
private Type enumType;
public DrinkDosesConverter(Type type) : base(type) {
enumType = type;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destType) {
return destType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture,
object value, Type destType) {
FieldInfo fi = enumType.GetField(Enum.GetName(enumType, value));
DescriptionAttribute dna = (DescriptionAttribute)Attribute.GetCustomAttribute(fi,
typeof(DescriptionAttribute));
if (dna != null)
return dna.Description;
else
return value.ToString();
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type srcType) {
return srcType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture,
object value) {
foreach (FieldInfo fi in enumType.GetFields()) {
DescriptionAttribute dna = (DescriptionAttribute)Attribute.GetCustomAttribute(fi,
typeof(DescriptionAttribute));
if ((dna != null) && ((string)value == dna.Description))
return Enum.Parse(enumType, fi.Name);
}
return Enum.Parse(enumType, (string)value);
}
}
Third, you need set the attribute TypeConverter for displaying the property.
class DrinkerDoses {
DrinkDoses doses;
[DisplayName("Doses")]
[Description("Drinker doses")]
[Category("Alcoholics drinking")]
[TypeConverter(typeof(DrinkDosesConverter))]
public DrinkDoses Doses {
get { return doses; }
set { doses = value; }
}
int dataInt;
public int DataInt {
get { return dataInt; }
set { dataInt = value; }
}
}
You can attach a custom TypeConverter implementation to the property whose type is your enumeration and override the GetStandardValuesSupported and GetStandardValues to return a custom list of items to show in the drop-down list in the PropertyGrid. You can then override ConvertFrom/ConvertTo methods to handle converting values to/from your enumeration type.
You may also want to override GetStandardValuesExclusive and have it return "true" so the user can't type anything into the property value.
So, something like this:
public class MyTypeConverter : TypeConverter
{
//Override GetStandardValuesExclusive,
//GetStandardValues and GetStandardValuesSupported
}
public class SomeClass
{
[TypeConverter(typeof(MyTypeConverter))]
public Resolution SomePropertry
{
...
}
}
In your implementation of GetStandardValues/ConvertFrom/ConvertTo you could then use Reflection to pull out the DisplayNameAttribute (or DescriptionAttribute, which may be more suited to this task) attributes of the various enum members to show that text instead of hard-coding the list of items to show.
The answer I gave here Has a working example of this. Here is the specific code from that example that you want:
/// <summary>
/// This attribute is used to represent a string value
/// for a value in an enum.
/// </summary>
public class StringValueAttribute : Attribute {
#region Properties
/// <summary>
/// Holds the stringvalue for a value in an enum.
/// </summary>
public string StringValue { get; protected set; }
#endregion
#region Constructor
/// <summary>
/// Constructor used to init a StringValue Attribute
/// </summary>
/// <param name="value"></param>
public StringValueAttribute(string value) {
this.StringValue = value;
}
#endregion
}
public static class MyExtension
{
public static string GetStringValue(this Enum value)
{
// Get the type
Type type = value.GetType();
// Get fieldinfo for this type
FieldInfo fieldInfo = type.GetField(value.ToString());
// Get the stringvalue attributes
StringValueAttribute[] attribs = fieldInfo.GetCustomAttributes(
typeof(StringValueAttribute), false) as StringValueAttribute[];
// Return the first if there was a match.
return attribs.Length > 0 ? attribs[0].StringValue : null;
}
public static String[] GetEnumNames(Type t)
{
Array enumValueArray= Enum.GetValues(t);
string[] enumStrings = new String[enumValueArray.Length];
for(int i = 0; i< enumValueArray.Length; ++i)
{
enumStrings[i] = GetStringValue((test)enumValueArray.GetValue(i));
}
return enumStrings;
}
}
enum test
{
[StringValue("test ONE")]
test1,
[StringValue("test TWO")]
test2
}
This also seems to work:
[AttributeUsage(AttributeTargets.Field)]
public class EnumDisplayNameAttribute : System.ComponentModel.DisplayNameAttribute
{
public EnumDisplayNameAttribute(string data) : base(data) { }
}
public enum Resolution_ : byte
{
DCIF,
CIF,
QCIF,
[EnumDisplayName("4CIF")]
CIF4,
[EnumDisplayName("2CIF")]
CIF2
}
Components looking for a DisplayName attribute via Reflection will find one, and as far as I can tell this works. Is there a reason why this might be a bad idea?

How to get Custom Attribute values for enums?

I have an enum where each member has a custom attribute applied to it. How can I retrieve the value stored in each attribute?
Right now I do this:
var attributes = typeof ( EffectType ).GetCustomAttributes ( false );
foreach ( object attribute in attributes )
{
GPUShaderAttribute attr = ( GPUShaderAttribute ) attribute;
if ( attr != null )
return attr.GPUShader;
}
return 0;
Another issue is, if it's not found, what should I return? 0 is implcity convertible to any enum, right? That's why I returned that.
Forgot to mention, the above code returns 0 for every enum member.
Try using a generic method
Attribute:
class DayAttribute : Attribute
{
public string Name { get; private set; }
public DayAttribute(string name)
{
this.Name = name;
}
}
Enum:
enum Days
{
[Day("Saturday")]
Sat,
[Day("Sunday")]
Sun,
[Day("Monday")]
Mon,
[Day("Tuesday")]
Tue,
[Day("Wednesday")]
Wed,
[Day("Thursday")]
Thu,
[Day("Friday")]
Fri
}
Generic method:
public static TAttribute GetAttribute<TAttribute>(this Enum value)
where TAttribute : Attribute
{
var enumType = value.GetType();
var name = Enum.GetName(enumType, value);
return enumType.GetField(name).GetCustomAttributes(false).OfType<TAttribute>().SingleOrDefault();
}
Invoke:
static void Main(string[] args)
{
var day = Days.Mon;
Console.WriteLine(day.GetAttribute<DayAttribute>().Name);
Console.ReadLine();
}
Result:
Monday
It is a bit messy to do what you are trying to do as you have to use reflection:
public GPUShaderAttribute GetGPUShader(EffectType effectType)
{
MemberInfo memberInfo = typeof(EffectType).GetMember(effectType.ToString())
.FirstOrDefault();
if (memberInfo != null)
{
GPUShaderAttribute attribute = (GPUShaderAttribute)
memberInfo.GetCustomAttributes(typeof(GPUShaderAttribute), false)
.FirstOrDefault();
return attribute;
}
return null;
}
This will return an instance of the GPUShaderAttribute that is relevant to the one marked up on the enum value of EffectType. You have to call it on a specific value of the EffectType enum:
GPUShaderAttribute attribute = GetGPUShader(EffectType.MyEffect);
Once you have the instance of the attribute, you can get the specific values out of it that are marked-up on the individual enum values.
There is another method to do this with generics:
public static T GetAttribute<T>(Enum enumValue) where T: Attribute
{
T attribute;
MemberInfo memberInfo = enumValue.GetType().GetMember(enumValue.ToString())
.FirstOrDefault();
if (memberInfo != null)
{
attribute = (T) memberInfo.GetCustomAttributes(typeof (T), false).FirstOrDefault();
return attribute;
}
return null;
}
Assuming GPUShaderAttribute:
[AttributeUsage(AttributeTargets.Field,AllowMultiple =false)]
public class GPUShaderAttribute: Attribute
{
public GPUShaderAttribute(string value)
{
Value = value;
}
public string Value { get; internal set; }
}
Then we could write a few generic methods to return a dictionary of the enum values and the GPUShaderAttribute object.
/// <summary>
/// returns the attribute for a given enum
/// </summary>
public static TAttribute GetAttribute<TAttribute>(IConvertible #enum)
{
TAttribute attributeValue = default(TAttribute);
if (#enum != null)
{
FieldInfo fi = #enum.GetType().GetField(#enum.ToString());
attributeValue = fi == null ? attributeValue : (TAttribute)fi.GetCustomAttributes(typeof(TAttribute), false).DefaultIfEmpty(null).FirstOrDefault();
}
return attributeValue;
}
Then return the whole set with this method.
/// <summary>
/// Returns a dictionary of all the Enum fields with the attribute.
/// </summary>
public static Dictionary<Enum, RAttribute> GetEnumObjReference<TEnum, RAttribute>()
{
Dictionary<Enum, RAttribute> _dict = new Dictionary<Enum, RAttribute>();
Type enumType = typeof(TEnum);
Type enumUnderlyingType = Enum.GetUnderlyingType(enumType);
Array enumValues = Enum.GetValues(enumType);
foreach (Enum enumValue in enumValues)
{
_dict.Add(enumValue, GetAttribute<RAttribute>(enumValue));
}
return _dict;
}
If you just wanted a string value I would recommend a slightly different route.
/// <summary>
/// Returns the string value of the custom attribute property requested.
/// </summary>
public static string GetAttributeValue<TAttribute>(IConvertible #enum, string propertyName = "Value")
{
TAttribute attribute = GetAttribute<TAttribute>(#enum);
return attribute == null ? null : attribute.GetType().GetProperty(propertyName).GetValue(attribute).ToString();
}
/// <summary>
/// Returns a dictionary of all the Enum fields with the string of the property from the custom attribute nulls default to the enumName
/// </summary>
public static Dictionary<Enum, string> GetEnumStringReference<TEnum, RAttribute>(string propertyName = "Value")
{
Dictionary<Enum, string> _dict = new Dictionary<Enum, string>();
Type enumType = typeof(TEnum);
Type enumUnderlyingType = Enum.GetUnderlyingType(enumType);
Array enumValues = Enum.GetValues(enumType);
foreach (Enum enumValue in enumValues)
{
string enumName = Enum.GetName(typeof(TEnum), enumValue);
string decoratorValue = Common.GetAttributeValue<RAttribute>(enumValue, propertyName) ?? enumName;
_dict.Add(enumValue, decoratorValue);
}
return _dict;
}
I came up with a different method to locate the FieldInfo element for the targeted enumerated value. Locating the enumerated value by converting it to a string felt wrong, so I opted for checking the field list with LINQ:
Type enumType = value.GetType();
FieldInfo[] fields = enumType.GetFields();
FieldInfo fi = fields.Where(tField =>
tField.IsLiteral &&
tField.GetValue(null).Equals(value)
).First();
So all glommed together I have:
public static TAttribute GetAttribute<TAttribute>(this Enum value)
where TAttribute : Attribute
{
Type enumType = value.GetType();
FieldInfo[] fields = enumType.GetFields();
FieldInfo fi = fields.Where(tField =>
tField.IsLiteral &&
tField.GetValue(null).Equals(value)
).First();
// If we didn't get, return null
if (fi == null) return null;
// We found the element (which we always should in an enum)
// return the attribute if it exists.
return (TAttribute)(fi.GetCustomAttribute(typeof(TAttribute)));
}
public string GetEnumAttributeValue(Enum enumValue, Type attributeType, string attributePropertyName)
{
/* New generic version (GetEnumDescriptionAttribute results can be achieved using this new GetEnumAttribute with a call like (enumValue, typeof(DescriptionAttribute), "Description")
* Extracts a given attribute value from an enum:
*
* Ex:
* public enum X
* {
[MyAttribute(myProp = "aaaa")]
* x1,
* x2,
* [Description("desc")]
* x3
* }
*
* Usage:
* GetEnumAttribute(X.x1, typeof(MyAttribute), "myProp") returns "aaaa"
* GetEnumAttribute(X.x2, typeof(MyAttribute), "myProp") returns string.Empty
* GetEnumAttribute(X.x3, typeof(DescriptionAttribute), "Description") returns "desc"
*/
var attributeObj = enumValue.GetType()?.GetMember(enumValue.ToString())?.FirstOrDefault()?.GetCustomAttributes(attributeType, false)?.FirstOrDefault();
if (attributeObj == null)
return string.Empty;
else
{
try
{
var attributeCastedObj = Convert.ChangeType(attributeObj, attributeType);
var attributePropertyValue = attributeType.GetProperty(attributePropertyName)?.GetValue(attributeCastedObj);
return attributePropertyValue?.ToString() ?? string.Empty;
}
catch (Exception ex)
{
return string.Empty;
}
}
}

Custom attribute on property - Getting type and value of attributed property

I have the following custom attribute, which can be applied on properties:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class IdentifierAttribute : Attribute
{
}
For example:
public class MyClass
{
[Identifier()]
public string Name { get; set; }
public int SomeNumber { get; set; }
public string SomeOtherProperty { get; set; }
}
There will also be other classes, to which the Identifier attribute could be added to properties of different type:
public class MyOtherClass
{
public string Name { get; set; }
[Identifier()]
public int SomeNumber { get; set; }
public string SomeOtherProperty { get; set; }
}
I then need to be able to get this information in my consuming class.
For example:
public class TestClass<T>
{
public void GetIDForPassedInObject(T obj)
{
var type = obj.GetType();
//type.GetCustomAttributes(true)???
}
}
What's the best way of going about this?
I need to get the type of the [Identifier()] field (int, string, etc...) and the actual value, obviously based on the type.
Something like the following,, this will use only the first property it comes accross that has the attribute, of course you could place it on more than one..
public object GetIDForPassedInObject(T obj)
{
var prop = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(p => p.GetCustomAttributes(typeof(IdentifierAttribute), false).Count() ==1);
object ret = prop !=null ? prop.GetValue(obj, null) : null;
return ret;
}
public class TestClass<T>
{
public void GetIDForPassedInObject(T obj)
{
PropertyInfo[] properties =
obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo IdProperty = (from PropertyInfo property in properties
where property.GetCustomAttributes(typeof(Identifier), true).Length > 0
select property).First();
if(null == IdProperty)
throw new ArgumentException("obj does not have Identifier.");
Object propValue = IdProperty.GetValue(entity, null)
}
}
A bit late but here is something I did for enums (could be any object also) and getting the description attribute value using an extension (this could be a generic for any attribute):
public enum TransactionTypeEnum
{
[Description("Text here!")]
DROP = 1,
[Description("More text here!")]
PICKUP = 2,
...
}
Getting the value:
var code = TransactionTypeEnum.DROP.ToCode();
Extension supporting all my enums:
public static string ToCode(this TransactionTypeEnum val)
{
return GetCode(val);
}
public static string ToCode(this DockStatusEnum val)
{
return GetCode(val);
}
public static string ToCode(this TrailerStatusEnum val)
{
return GetCode(val);
}
public static string ToCode(this DockTrailerStatusEnum val)
{
return GetCode(val);
}
public static string ToCode(this EncodingType val)
{
return GetCode(val);
}
private static string GetCode(object val)
{
var attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : string.Empty;
}
Here is a more real-word example. We use an extension method and check if a property contains a FieldMetaDataAttribute (a custom attribute in my source code base)
with valid Major and MinorVersion. What is of general interest is the part where we use the parent class type and GetProperties and retrieve the ProperyInfo and then use GetCustomAttribute to retrieve a attribute FieldMetaDataAttribute in this special case. Use this code for inspiration how to do more generic way of retrieving a custom attribute. This can of course be polished to make a general method to retrieve a given attribute of any property of a class instance.
/// <summary>
/// Executes the action if not the field is deprecated
/// </summary>
/// <typeparam name="TProperty"></typeparam>
/// <typeparam name="TForm"></typeparam>
/// <param name="form"></param>
/// <param name="memberExpression"></param>
/// <param name="actionToPerform"></param>
/// <returns>True if the action was performed</returns>
public static bool ExecuteActionIfNotDeprecated<TForm, TProperty>(this TForm form, Expression<Func<TForm, TProperty>> memberExpression, Action actionToPerform)
{
var memberExpressionConverted = memberExpression.Body as MemberExpression;
if (memberExpressionConverted == null)
return false;
string memberName = memberExpressionConverted.Member.Name;
PropertyInfo matchingProperty = typeof(TForm).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(p => p.Name == memberName);
if (matchingProperty == null)
return false; //should not occur
var fieldMeta = matchingProperty.GetCustomAttribute(typeof(FieldMetadataAttribute), true) as FieldMetadataAttribute;
if (fieldMeta == null)
{
actionToPerform();
return true;
}
var formConverted = form as FormDataContract;
if (formConverted == null)
return false;
if (fieldMeta.DeprecatedFromMajorVersion > 0 && formConverted.MajorVersion > fieldMeta.DeprecatedFromMajorVersion)
{
//major version of formConverted is deprecated for this field - do not execute action
return false;
}
if (fieldMeta.DeprecatedFromMinorVersion > 0 && fieldMeta.DeprecatedFromMajorVersion > 0
&& formConverted.MinorVersion >= fieldMeta.DeprecatedFromMinorVersion
&& formConverted.MajorVersion >= fieldMeta.DeprecatedFromMajorVersion)
return false; //the field is expired - do not invoke action
actionToPerform();
return true;
}

Categories

Resources