2008-12-10 7 views
112

Я ищу способ локализовать имена свойств, отображаемые в PropertyGrid. Имя свойства может быть «переопределено» с использованием атрибута DisplayNameAttribute. К сожалению, атрибуты не могут иметь не постоянные выражения. Поэтому я не могу использовать строго типизированные ресурсы, такие как:Локализация DisplayNameAttribute

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

Я посмотрел вокруг и нашел некоторые предложения наследовать DisplayNameAttribute, чтобы иметь возможность использовать ресурс. Я бы в конечном итоге с кодом, как:

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

Однако я потеряю сильно типизированных выгоды ресурсов, которые, безусловно, не очень хорошая вещь. Затем я наткнулся на DisplayNameResourceAttribute, и это может быть то, что я ищу. Но это должно быть в пространстве имен Microsoft.VisualStudio.Modeling.Design, и я не могу найти, какую ссылку я должен добавить для этого пространства имен.

Кто-нибудь знает, есть ли лучший способ локализовать локацию DisplayName? или если есть способ использовать то, что Microsoft, похоже, использует для Visual Studio?

+2

насчет дисплея (ResourceType = TypeOf (ресурсных строк), Name = "MyProperty") см http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayattribute.aspx – Peter 2011-12-14 13:46:59

+0

@Peter внимательно прочитал сообщение, ему нужна полная противоположность, используя ResourceStrings и проверку времени компиляции, а не жестко закодированные строки ... – Marko 2013-12-20 17:13:14

ответ

40

Вот решение, которое я закончился (в моем случае - «Common»):

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

с кодом для поиска ресурса:

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 
    } 

Обычное использование:

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

Что такое довольно много уродливые, как я использую буквенные строки для ключа ресурса. Использование константы означало бы изменить Resource.Designer.cs, что, вероятно, не очень хорошая идея.

Заключение: Я не доволен тем, что, но я еще менее рад Microsoft, который не может предоставить что-либо полезное для такой общей задачи.

1

Ну, сборка Microsoft.VisualStudio.Modeling.Sdk.dll. который поставляется вместе с Visual Studio SDK (с пакетом интеграции Visual Studio).

Но он будет использоваться почти так же, как ваш атрибут; нет возможности использовать сильно типы ресурсов в атрибутах просто потому, что они не являются постоянными.

5

Вы можете подклассифицировать DisplayNameAttribute, чтобы обеспечить i18n, переопределив один из методов. Вот так. Редактировать: Возможно, вам придется рассчитывать на использование константы для ключа.

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" } } } }); 
    } 
} 
+0

Лучшее решение! – Cyrus 2016-01-01 09:06:14

0

Я извиняюсь за код VB.NET, C# мой немного ржавый ... Но вы получите идею, не так ли?

Прежде всего, создайте новый класс: LocalizedPropertyDescriptor, который наследует PropertyDescriptor. Переопределение DisplayName свойства как это:

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 является ResourceManager файла ресурса, который содержит ваши переводы.

Далее реализовать ICustomTypeDescriptor в классе с локализованными свойствами, и переопределить метод 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 

Теперь вы можете использовать «DisplayName` атрибут для хранения ссылки на значения в файле ресурсов. ..

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

prop_description ключ в файле ресурсов.

+0

Первая часть вашего решения - это то, что я сделал ... пока мне не пришлось решить вопрос «что такое Some.ResourceManager?» вопрос. Должен ли я указывать строку * second * literal, такую ​​как «MyAssembly.Resources.Resource»? слишком опасно! Что касается второй части (ICustomTypeDescriptor), я не думаю, что это действительно полезно – PowerKiKi 2008-12-10 16:25:04

+0

Решение Marc Gravell - это путь, если вам не нужно ничего, кроме переведенного DisplayName. Я использую собственный дескриптор для других вещей, и это было мое решение. Однако нет никакого способа сделать это, не поставляя какой-то ключ. – 2008-12-10 16:56:50

75

Мы делаем это для ряда атрибутов, чтобы поддерживать несколько языков. Мы применили аналогичный подход к Microsoft, где они переопределяют свои базовые атрибуты и передают имя ресурса, а не фактическую строку. Имя ресурса затем используется для выполнения поиска в ресурсах DLL для фактической возвращаемой строки.

Например:

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); 
     } 
    } 
} 

Вы можете принять это шаг вперед, когда на самом деле с помощью атрибута, а также указать свои имена ресурсов как константы в статическом классе. Таким образом, вы получаете декларации типа.

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

Update
ResourceStrings будет выглядеть примерно так (обратите внимание, каждая строка будет ссылаться на имя ресурса, который определяет фактическую строку):

public static class ResourceStrings 
{ 
    public const string ForegroundColorDisplayName="ForegroundColorDisplayName"; 
    public const string FontSizeDisplayName="FontSizeDisplayName"; 
} 
+0

Когда я пытаюсь использовать этот подход, появляется сообщение об ошибке: «Аргумент атрибута должен быть константным выражением, выражением типа или выражением для создания массива типа атрибута». Тем не менее, передача значения LocalizedDisplayName в виде строки работает. Хотелось бы, чтобы это было строго напечатано. – 2010-02-25 09:11:38

+1

@Andy: Значения в ResourceStrings должны быть константами, указанными в ответе, а не свойствами или значениями только для чтения. Они должны быть помечены как const и ссылаться на имена ресурсов, иначе вы получите сообщение об ошибке. – 2010-03-01 16:05:56

13

Вы можете использовать T4 для создания констант. Я написал один:

<#@ 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#>"; 
    <# } #> 
    } 
} 
} 
104

Существует Display attribute из System.ComponentModel.DataAnnotations в .NET 4. Он работает на MVC 3 PropertyGrid.

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

Это смотрит ресурс с именем UserName в вашем MyResources .resx файла.

9

Это старый вопрос, но я думаю, что это очень распространенная проблема, и вот мое решение в MVC 3.

Во-первых, шаблон T4 необходим для создания констант, чтобы избежать неприятных строк. У нас есть файл ресурсов «Labels.resx» содержит все строки ярлыков. Поэтому шаблон T4 использует файл ресурсов непосредственно,

<#@ 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#>";      
<# 
      }  
#> 
    } 
} 

Затем метод расширения получить создан, чтобы локализовать «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; 
     } 
    } 
} 

атрибут «DISPLAYNAME» заменяется атрибутом «DisplayLabel» в для того, чтобы читать автоматически,

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; 
      } 
     } 
    } 
} 

После всех этих подготовительных работ, время потрогать атрибуты проверки «» Labels.resx тех по умолчанию.Я использую атрибут «Обязательный» в качестве примера,

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()); 
     } 

    } 
} 

Теперь мы можем применить эти атрибуты в нашей модели,

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; } 
    } 
} 

По умолчанию имя свойства используется в качестве ключа для поиска " Label.resx ', но если вы установите его через «DisplayLabel», он будет использовать это вместо этого.

2

Я использую этот способ решить в моем случае

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

С кодом

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); 
     } 
    } 

} 
8

Использование атрибута Display (от System.ComponentModel.DataAnnotations) и nameof() выражения в C# 6, вам Вы получите локализованное и строго типизированное решение.

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