Get DisplayName of property c#

187 Views Asked by At

I'm currently trying to get the displayname of a property in XAML (WPF) and tried to implement the answer of this question "Access DisplayName in xaml".

To test this, I created a class with a property and the DisplayAttribute (I use localization with .resx files to get the localized string),

    public partial class Employee
    {
        public Employee()
        {

        }

        private string? firstName;

        [Display(Name = nameof(Strings.firstName), ResourceType = typeof(Strings))]
        public string? FirstName
        {
            get => firstName;
            set => SetProperty(ref firstName, value, true);
        }

modified the MarkupExtension from the other question to allow null values,

    internal class DisplayNameExtension : MarkupExtension
    {
        public Type Type { get; set; }

        public string PropertyName { get; set; }

        public DisplayNameExtension() { }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (Type.GetProperty(PropertyName) is PropertyInfo prop)
            {
                if (prop.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() is DisplayAttribute attribute && !string.IsNullOrEmpty(attribute.Name))
                {
                    return attribute.Name;
                }
            }
            return string.Empty;
        }
    }

and tried to call this with:

<TextBlock Text="{local:DisplayName PropertyName=FirstName, Type=model:Employee}"/>

(Employee is part of another project, references are correct)

Now when I stop the app and look into the prop property when calling the method, the correct property is determined but no attributes exist here even though I have set the DisplayAttribute. View inside prop with debugger: View inside prop with debugger

Am I missing something obvious here or is the solution to get the attributes a bit more complex?

EDIT: Thanks to @EldHasp for pointing out that I need to look at CustomAttributes and not Attributes. Now I see the real problem of my implementation: The key of the translation from the .resx file is used as display name and not the translation itself. I thought that it works like the RequiredAttribute but this does not seem to be the case:

[Required(ErrorMessageResourceName = nameof(Strings.employeeFirstNameRequired), ErrorMessageResourceType = typeof(Strings))]

used entries of translation

2

There are 2 best solutions below

0
mm8 On BEST ANSWER

Your implementation of the ProvideValue method returns the key.

It should perform the resource lookup based on that key and return the localized value.

There is a GetString method of the ResourceManager that you can use for this:

public override object ProvideValue(IServiceProvider serviceProvider)
{
    if (Type.GetProperty(PropertyName) is PropertyInfo prop)
    {
        if (prop.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() is DisplayAttribute attribute && !string.IsNullOrEmpty(attribute.Name))
        {
            return strings.ResourceManager.GetString(attribute.Name);
        }
    }
    return string.Empty;
}

Just replace "strings" with the name of your .resx file and the auto-generated class.

1
EldHasp On

I didn't quite understand your question. I got the impression that you're just looking at the wrong property. You need to look at the CustomAttributes collection. enter image description here

enter image description here

P.S. Slightly improved implementation (without checking for errors and exceptions):

    public class DisplayNameExtension : MarkupExtension
    {
        public DisplayNameExtension() { }

        [TypeConverter(typeof(PropertyInfoConverter))]
        public PropertyInfo? Property { get; set; }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
                if (Property!.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() is DisplayAttribute attribute && !string.IsNullOrEmpty(attribute.Name))
                {
                    return attribute.Name;
                }
            return string.Empty;
        }
    }

    public class PropertyInfoConverter : TypeConverter
    {
        public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
        {
            int index;
            if (value is string text && (index = text.LastIndexOf('.')) >0) 
            {
                string propName = text[(index + 1)..];
                string typeName = text[..index];
                IXamlTypeResolver xamlTypeResolver = (IXamlTypeResolver) context!.GetService(typeof(IXamlTypeResolver))!;
                Type type = xamlTypeResolver.Resolve(typeName);
                var prop = type.GetProperty(propName);
                return prop;
            }
            return base.ConvertFrom(context, culture, value);
        }
    }
        <TextBlock Text="{local:DisplayName Property=local:Employee.FirstName}"/>