Lokalizacja DisplayNameAttribute

Szukam sposobu na zlokalizowanie nazw właściwości wyświetlanych w PropertyGrid. Nazwa właściwości może być "nadpisana" przy użyciu atrybutu DisplayNameAttribute. Niestety atrybuty nie mogą mieć wyrażeń niestałych. Więc nie mogę używać mocno wpisanych zasobów, takich jak:

class Foo
{
   [DisplayAttribute(Resources.MyPropertyNameLocalized)]  // do not compile
   string MyProperty {get; set;}
}

Rozejrzałem się i znalazłem pewną sugestię, aby dziedziczyć z DisplayNameAttribute, aby móc korzystać z zasobów. Ja bym skończył z kodem w stylu:

class Foo
{
   [MyLocalizedDisplayAttribute("MyPropertyNameLocalized")] // not strongly typed
   string MyProperty {get; set;}
}

Jednak tracę mocno wpisane korzyści z zasobów, co zdecydowanie nie jest dobrą rzeczą. Wtedy natknąłem się na DisplayNameResourceAttribute , który może być tym, czego szukam. Ale to powinno być w Microsofcie.VisualStudio.Modeling.Zaprojektuj przestrzeń nazw i nie mogę znaleźć referencji, którą mam dodać do tej przestrzeni nazw.

Ktoś wie, czy jest łatwiejszy sposób na uzyskanie lokalizacji DisplayName w dobry sposób ? a może jest sposób na wykorzystanie tego, co Microsoft wydaje się używać w Visual Studio ?

Author: serhio, 2008-12-10

10 answers

Istnieje atrybut Display z systemu.ComponentModel.DataAnnotations w. Net 4. Działa na MVC 3 PropertyGrid.

[Display(ResourceType = typeof(MyResources), Name = "UserName")]
public string UserName { get; set; }

To wyszukuje zasób o nazwie UserName w twoim MyResources.plik resx.

 107
Author: RandomEngy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-27 23:08:25

Robimy to dla wielu atrybutów w celu obsługi wielu języków. Przyjęliśmy podobne podejście do Microsoft, gdzie nadpisują swoje atrybuty podstawowe i przekazują nazwę zasobu, a nie rzeczywisty ciąg znaków. Nazwa zasobu jest następnie używana do przeprowadzenia wyszukiwania w zasobach DLL, aby rzeczywisty łańcuch mógł powrócić.

Na przykład:

class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
    private readonly string resourceName;
    public LocalizedDisplayNameAttribute(string resourceName)
        : base()
    {
      this.resourceName = resourceName;
    }

    public override string DisplayName
    {
        get
        {
            return Resources.ResourceManager.GetString(this.resourceName);
        }
    }
}

Możesz pójść o krok dalej, gdy faktycznie używasz atrybutu i określić nazwy zasobów jako stałe w zajęcia statyczne. W ten sposób otrzymujesz deklaracje.

[LocalizedDisplayName(ResourceStrings.MyPropertyName)]
public string MyProperty
{
  get
  {
    ...
  }
}

Aktualizacja
ResourceStrings wygląda to tak (zauważ, że każdy łańcuch odnosi się do nazwy zasobu, który określa rzeczywisty łańcuch):

public static class ResourceStrings
{
    public const string ForegroundColorDisplayName="ForegroundColorDisplayName";
    public const string FontSizeDisplayName="FontSizeDisplayName";
}
 78
Author: Jeff Yates,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-11 09:42:07

Oto rozwiązanie, które znalazłem w osobnym zbiorze (zwanym w moim przypadku "wspólnym"):

   [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
   public class DisplayNameLocalizedAttribute : DisplayNameAttribute
   {
      public DisplayNameLocalizedAttribute(Type resourceManagerProvider, string resourceKey)
         : base(Utils.LookupResource(resourceManagerProvider, resourceKey))
      {
      }
   }

Z kodem do wyszukania zasobu:

  internal static string LookupResource(Type resourceManagerProvider, string resourceKey)
  {
     foreach (PropertyInfo staticProperty in  resourceManagerProvider.GetProperties(BindingFlags.Static | BindingFlags.NonPublic))
     {
        if (staticProperty.PropertyType == typeof(System.Resources.ResourceManager))
        {
           System.Resources.ResourceManager resourceManager = (System.Resources.ResourceManager)staticProperty.GetValue(null, null);
           return resourceManager.GetString(resourceKey);
        }
     }

     return resourceKey; // Fallback with the key name
  }

Typowe użycie to:

class Foo
{
      [Common.DisplayNameLocalized(typeof(Resources.Resource), "CreationDateDisplayName"),
      Common.DescriptionLocalized(typeof(Resources.Resource), "CreationDateDescription")]
      public DateTime CreationDate
      {
         get;
         set;
      }
}

Co jest dość brzydkie, ponieważ używam dosłownych ciągów dla klucza zasobów. Użycie stałej oznaczałoby modyfikowanie zasobów.Projektant.cs, co chyba nie jest dobrym pomysłem.

Wniosek: nie jestem z tego zadowolony, ale jeszcze mniej cieszę się z Microsoftu, który nie może zapewnić wszystko, co przydatne do tak wspólnego zadania.

 41
Author: PowerKiKi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-11 13:37:57

Możesz użyć T4 do wygenerowania stałych. Napisałem jeden:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Xml.XPath" #>
using System;
using System.ComponentModel;


namespace Bear.Client
{
 /// <summary>
 /// Localized display name attribute
 /// </summary>
 public class LocalizedDisplayNameAttribute : DisplayNameAttribute
 {
  readonly string _resourceName;

  /// <summary>
  /// Initializes a new instance of the <see cref="LocalizedDisplayNameAttribute"/> class.
  /// </summary>
  /// <param name="resourceName">Name of the resource.</param>
  public LocalizedDisplayNameAttribute(string resourceName)
   : base()
  {
   _resourceName = resourceName;
  }

  /// <summary>
  /// Gets the display name for a property, event, or public void method that takes no arguments stored in this attribute.
  /// </summary>
  /// <value></value>
  /// <returns>
  /// The display name.
  /// </returns>
  public override String DisplayName
  {
   get
   {
    return Resources.ResourceManager.GetString(this._resourceName);
   }
  }
 }

 partial class Constants
 {
  public partial class Resources
  {
  <# 
   var reader = XmlReader.Create(Host.ResolvePath("resources.resx"));
   var document = new XPathDocument(reader);
   var navigator = document.CreateNavigator();
   var dataNav = navigator.Select("/root/data");
   foreach (XPathNavigator item in dataNav)
   {
    var name = item.GetAttribute("name", String.Empty);
  #>
   public const String <#= name#> = "<#= name#>";
  <# } #>
  }
 }
}
 13
Author: zielu1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-22 16:32:24

Użycie atrybutu Display (z systemu.ComponentModel.DataAnnotations) oraz wyrażenie nameof () w C # 6 otrzymasz zlokalizowane i mocno wpisane rozwiązanie.

[Display(ResourceType = typeof(MyResources), Name = nameof(MyResources.UserName))]
public string UserName { get; set; }
 13
Author: dionoid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-24 19:36:46

To stare pytanie, ale myślę, że jest to bardzo powszechny problem, a oto moje rozwiązanie w MVC 3.

Po pierwsze, szablon T4 jest potrzebny do generowania stałych, aby uniknąć nieprzyjemnych łańcuchów. Mamy etykiety plików zasobów.resx ' zawiera wszystkie ciągi etykiet. Dlatego szablon T4 używa pliku zasobów bezpośrednio,

<#@ template debug="True" hostspecific="True" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="C:\Project\trunk\Resources\bin\Development\Resources.dll" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Globalization" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Resources" #>
<#
  var resourceStrings = new List<string>();
  var manager = Resources.Labels.ResourceManager;

  IDictionaryEnumerator enumerator = manager.GetResourceSet(CultureInfo.CurrentCulture,  true, true)
                                             .GetEnumerator();
  while (enumerator.MoveNext())
  {
        resourceStrings.Add(enumerator.Key.ToString());
  }
#>     

// This file is generated automatically. Do NOT modify any content inside.

namespace Lib.Const{
        public static class LabelNames{
<#
            foreach (String label in resourceStrings){
#>                    
              public const string <#=label#> =     "<#=label#>";                    
<#
           }    
#>
    }
}

Następnie zostanie utworzona metoda rozszerzenia, która zlokalizuje 'DisplayName',

using System.ComponentModel.DataAnnotations;
using Resources;

namespace Web.Extensions.ValidationAttributes
{
    public static class ValidationAttributeHelper
    {
        public static ValidationContext LocalizeDisplayName(this ValidationContext    context)
        {
            context.DisplayName = Labels.ResourceManager.GetString(context.DisplayName) ?? context.DisplayName;
            return context;
        }
    }
}

Atrybut "DisplayName" zastępuje się atrybutem "DisplayLabel" w order to read from ' Labels.resx",

namespace Web.Extensions.ValidationAttributes
{

    public class DisplayLabelAttribute :System.ComponentModel.DisplayNameAttribute
    {
        private readonly string _propertyLabel;

        public DisplayLabelAttribute(string propertyLabel)
        {
            _propertyLabel = propertyLabel;
        }

        public override string DisplayName
        {
            get
            {
                return _propertyLabel;
            }
        }
    }
}

Po tych wszystkich pracach przygotowawczych, czas dotknąć tych domyślnych atrybutów walidacji. Używam atrybutu "Required" jako przykładu,

using System.ComponentModel.DataAnnotations;
using Resources;

namespace Web.Extensions.ValidationAttributes
{
    public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
    {
        public RequiredAttribute()
        {
          ErrorMessageResourceType = typeof (Errors);
          ErrorMessageResourceName = "Required";
        }

        protected override ValidationResult IsValid(object value, ValidationContext  validationContext)
        {
            return base.IsValid(value, validationContext.LocalizeDisplayName());
        }

    }
}

Teraz możemy zastosować te atrybuty w naszym modelu,

using Web.Extensions.ValidationAttributes;

namespace Web.Areas.Foo.Models
{
    public class Person
    {
        [DisplayLabel(Lib.Const.LabelNames.HowOldAreYou)]
        public int Age { get; set; }

        [Required]
        public string Name { get; set; }
    }
}

Domyślnie, nazwa właściwości jest używana jako klucz do wyszukiwania ' Label.resx', ale jeśli ustawisz go przez 'DisplayLabel', użyje go zamiast tego.

 9
Author: YYFish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-04 15:45:19

Możesz podklasować DisplayNameAttribute, aby zapewnić i18n, nadpisując jedną z metod. W ten sposób. edit: być może będziesz musiał zadowolić się używaniem stałej dla klucza.

using System;
using System.ComponentModel;
using System.Windows.Forms;

class Foo {
    [MyDisplayName("bar")] // perhaps use a constant: SomeType.SomeResName
    public string Bar {get; set; }
}

public class MyDisplayNameAttribute : DisplayNameAttribute {
    public MyDisplayNameAttribute(string key) : base(Lookup(key)) {}

    static string Lookup(string key) {
        try {
            // get from your resx or whatever
            return "le bar";
        } catch {
            return key; // fallback
        }
    }
}

class Program {
    [STAThread]
    static void Main() {
        Application.Run(new Form { Controls = {
            new PropertyGrid { SelectedObject =
                new Foo { Bar = "abc" } } } });
    }
}
 5
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-10 15:50:56

Używam tej metody w moim przypadku

[LocalizedDisplayName("Age", NameResourceType = typeof(RegistrationResources))]
 public bool Age { get; set; }

Z kodem

public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
    private PropertyInfo _nameProperty;
    private Type _resourceType;


    public LocalizedDisplayNameAttribute(string displayNameKey)
        : base(displayNameKey)
    {

    }

    public Type NameResourceType
    {
        get
        {
            return _resourceType;
        }
        set
        {
            _resourceType = value;
            _nameProperty = _resourceType.GetProperty(base.DisplayName, BindingFlags.Static | BindingFlags.Public);
        }
    }

    public override string DisplayName
    {
        get
        {
            if (_nameProperty == null)
            {
                return base.DisplayName;
            }

            return (string)_nameProperty.GetValue(_nameProperty.DeclaringType, null);
        }
    }

}
 2
Author: HaikMnatsakanyan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-12 15:04:04

Cóż, Zgromadzenie jest Microsoft.VisualStudio.Modeling.Sdk.dll. który pochodzi z Visual Studio SDK (z pakietem integracji Visual Studio).

Ale byłby używany w prawie taki sam sposób jak twój atrybut; nie ma sposobu na użycie zasobów silnie typów w atrybutach po prostu dlatego, że nie są one stałe.

 1
Author: configurator,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-10 15:36:03

Przepraszam za VB.NET kod, mój C# jest trochę zardzewiały... Ale wpadniesz na pomysł, prawda?

Najpierw Utwórz nową klasę: LocalizedPropertyDescriptor, która dziedziczy PropertyDescriptor. Nadpisanie właściwości DisplayName w ten sposób:

Public Overrides ReadOnly Property DisplayName() As String
         Get
            Dim BaseValue As String = MyBase.DisplayName
            Dim Translated As String = Some.ResourceManager.GetString(BaseValue)
            If String.IsNullOrEmpty(Translated) Then
               Return MyBase.DisplayName
            Else
               Return Translated
           End If
    End Get
End Property

Some.ResourceManager jest menedżerem zasobów pliku zasobu, który zawiera Twoje tłumaczenia.

Następnie zaimplementuj ICustomTypeDescriptor w klasie o zlokalizowanych właściwościach i nadpisaj metodę GetProperties:

Public Function GetProperties() As PropertyDescriptorCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetProperties
    Dim baseProps As PropertyDescriptorCollection = TypeDescriptor.GetProperties(Me, True)
    Dim LocalizedProps As PropertyDescriptorCollection = New PropertyDescriptorCollection(Nothing)

    Dim oProp As PropertyDescriptor
    For Each oProp In baseProps
        LocalizedProps.Add(New LocalizedPropertyDescriptor(oProp))
    Next
    Return LocalizedProps
End Function

Możesz teraz użyć atrybutu 'DisplayName` do przechowywania odniesienie do wartości w pliku zasobów...

<DisplayName("prop_description")> _
Public Property Description() As String

prop_description jest kluczem w pliku zasobów.

 0
Author: Vincent Van Den Berghe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-10 15:47:06