1

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

tomo_2403
  • 15
  • 5
  • The `Attributes` property is a property set by the compiler. It does not represent annotation attributes. It's of type `PropertyAttributes` (an enum). The default is `PropertyAttributes.None` which is actually the value returned for all .NET CLR types. To get the annotation attributes you must inspect the `CustomAttributes` property (inherited from `MemberInfo`) or call the inherited `MemberInfo.GetCustomAttributes` method. –  Aug 13 '23 at 19:25
  • May I ask what your goal is? –  Aug 13 '23 at 19:25
  • I want to have a localized display name to show to the user. @EldHasp has already told me that I need to look at `CustomAttributes` and not `Attributes`. I missed the obvious again. It seems that this has been working all along and the problem is with the translation. See edit of question. – tomo_2403 Aug 14 '23 at 12:24
  • In the case of a WPF Solution, I would still prefer localization via changing the Resource Dictionary in App. – EldHasp Aug 15 '23 at 20:07

2 Answers2

1

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.

mm8
  • 163,881
  • 10
  • 57
  • 88
0

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}"/>
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • Thank you very much, I had already suspected that I was missing something obvious! By the way, the improved extension works and I like it better than before. But this revealed the real problem: the translation. Please see edit in question – tomo_2403 Aug 14 '23 at 12:25