Je cherche un moyen de localiser les noms de propriétés affichés dans un PropertyGrid. Le nom de la propriété peut être "remplacé" à l'aide de l'attribut DisplayNameAttribute. Malheureusement, les attributs ne peuvent pas avoir d'expressions non constantes. Je ne peux donc pas utiliser de ressources fortement typées telles que:
class Foo
{
[DisplayAttribute(Resources.MyPropertyNameLocalized)] // do not compile
string MyProperty {get; set;}
}
J'ai jeté un coup d'œil et trouvé une suggestion d'hériter de DisplayNameAttribute pour pouvoir utiliser une ressource. Je finirais avec un code comme:
class Foo
{
[MyLocalizedDisplayAttribute("MyPropertyNameLocalized")] // not strongly typed
string MyProperty {get; set;}
}
Cependant, je perds des ressources très typées, ce qui n’est certainement pas une bonne chose. Puis je suis tombé sur DisplayNameResourceAttribute qui peut être ce que je cherche. Mais il est supposé appartenir à l'espace de noms Microsoft.VisualStudio.Modeling.Design et je ne trouve pas la référence que je suis censé ajouter pour cet espace de noms.
Quelqu'un sait-il s'il existe un moyen plus simple de réaliser la localisation de DisplayName de la bonne manière? ou s'il y a moyen d'utiliser ce que Microsoft semble utiliser pour Visual Studio?
Voici la solution que j'ai trouvée dans une assemblée séparée (appelée "commune" dans mon cas):
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
public class DisplayNameLocalizedAttribute : DisplayNameAttribute
{
public DisplayNameLocalizedAttribute(Type resourceManagerProvider, string resourceKey)
: base(Utils.LookupResource(resourceManagerProvider, resourceKey))
{
}
}
avec le code pour rechercher la ressource:
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
}
L'utilisation typique serait:
class Foo
{
[Common.DisplayNameLocalized(typeof(Resources.Resource), "CreationDateDisplayName"),
Common.DescriptionLocalized(typeof(Resources.Resource), "CreationDateDescription")]
public DateTime CreationDate
{
get;
set;
}
}
Ce qui est plutôt moche alors que j'utilise des chaînes littérales pour la clé de ressource. Utiliser une constante signifierait modifier Resources.Designer.cs, ce qui n’est probablement pas une bonne idée.
Conclusion: je ne suis pas content de cela, mais je le suis encore moins de Microsoft qui ne peut rien fournir d’utile pour une tâche aussi commune.
Il y a le attribut d'affichage de System.ComponentModel.DataAnnotations dans .NET 4. Il fonctionne sur le MVC 3 PropertyGrid
.
[Display(ResourceType = typeof(MyResources), Name = "UserName")]
public string UserName { get; set; }
Ceci recherche une ressource nommée UserName
dans votre fichier MyResources
.resx.
Nous le faisons pour un certain nombre d'attributs afin de prendre en charge plusieurs langues. Nous avons adopté une approche similaire à celle de Microsoft, où ils remplacent leurs attributs de base et transmettent un nom de ressource plutôt que la chaîne réelle. Le nom de la ressource est ensuite utilisé pour effectuer une recherche dans les ressources DLL de la chaîne à renvoyer.
Par exemple:
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);
}
}
}
Vous pouvez aller plus loin lorsque vous utilisez réellement l'attribut et spécifier vos noms de ressources en tant que constantes dans une classe statique. De cette façon, vous obtenez des déclarations du type.
[LocalizedDisplayName(ResourceStrings.MyPropertyName)]
public string MyProperty
{
get
{
...
}
}
Mise à jourResourceStrings
ressemblerait à quelque chose comme (note, chaque chaîne ferait référence au nom d'une ressource qui spécifie la chaîne réelle):
public static class ResourceStrings
{
public const string ForegroundColorDisplayName="ForegroundColorDisplayName";
public const string FontSizeDisplayName="FontSizeDisplayName";
}
Vous pouvez utiliser T4 pour générer des constantes. J'en ai écrit un:
<#@ 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#>";
<# } #>
}
}
}
C'est une vieille question, mais je pense que c'est un problème très courant, et voici ma solution dans MVC 3.
Tout d'abord, un modèle T4 est nécessaire pour générer des constantes afin d'éviter les chaînes nuisibles. Nous avons un fichier de ressources ‘Labels.resx’ qui contient toutes les chaînes de libellé. Par conséquent, le modèle T4 utilise directement le fichier de ressources.
<#@ 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#>";
<#
}
#>
}
}
Ensuite, une méthode d’extension est créée pour localiser le "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;
}
}
}
L’attribut "DisplayName" est remplacé par l’attribut "DisplayLabel" pour permettre la lecture automatique de "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;
}
}
}
}
Après tout ce travail de préparation, il est temps de toucher à ces attributs de validation par défaut. J'utilise l'attribut "Obligatoire" comme exemple,
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());
}
}
}
Maintenant, nous pouvons appliquer ces attributs dans notre modèle,
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; }
}
}
Par défaut, le nom de la propriété est utilisé comme clé pour rechercher "Label.resx", mais si vous le définissez via "DisplayLabel", il l'utilisera à la place.
Vous pouvez sous-classer DisplayNameAttribute pour fournir i18n en redéfinissant l'une des méthodes. Ainsi. edit: Vous devrez peut-être vous contenter d'une constante pour la clé.
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" } } } });
}
}
J'utilise cette façon résoudre dans mon cas
[LocalizedDisplayName("Age", NameResourceType = typeof(RegistrationResources))]
public bool Age { get; set; }
Avec le code
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);
}
}
}
Eh bien, l’Assemblée est Microsoft.VisualStudio.Modeling.Sdk.dll
. fourni avec le SDK de Visual Studio (avec le package d’intégration Visual Studio).
Mais il serait utilisé à peu près de la même manière que votre attribut; il n'y a aucun moyen d'utiliser fortement les ressources de types dans les attributs simplement parce qu'elles ne sont pas constantes.
Je m'excuse pour le code VB.NET, mon C # est un peu rouillé ... Mais vous aurez l'idée, non?
Tout d’abord, créez une nouvelle classe: LocalizedPropertyDescriptor
, qui hérite de PropertyDescriptor
. Remplacez la propriété DisplayName
comme ceci:
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
est le gestionnaire de ressources du fichier de ressources contenant vos traductions.
Ensuite, implémentez ICustomTypeDescriptor
dans la classe avec les propriétés localisées et substituez la méthode 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
Vous pouvez maintenant utiliser l'attribut 'DisplayName` pour stocker une référence à une valeur dans un fichier de ressources ...
<DisplayName("prop_description")> _
Public Property Description() As String
prop_description
est la clé du fichier de ressources.