Comment définir et changer la culture de WPF

j'ai une application .NET 4.0 WPF où l'utilisateur peut changer la langue (culture) Je laisse simplement l'utilisateur sélectionner une langue, créer un CultureInfo correspondant et mettre:

Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;

dans le code C ça marche très bien. Toutefois, dans les contrôles du WPF, la culture est encore en-US. Cela signifie par exemple que les dates seront affichées dans le format US au lieu de ce qui est correct pour la culture actuelle.

Apparemment, ce n'est pas un bug. Selon MSDN et plusieurs billets de blog et articles sur StackOverflow, le langage WPF ne suit pas automatiquement la culture actuelle. C'est en-nous jusqu'à ce que vous fassiez ceci:

FrameworkElement.LanguageProperty.OverrideMetadata(
    typeof(FrameworkElement),
    new FrameworkPropertyMetadata(
        XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag)));

Voir, par exemple, StringFormat problèmes de Localisation dans wpf .

je ne comprends pas complètement ce qui se passe ici. Il semble que la propriété de la langue sur tous les cadreskelements est mis à la culture actuelle. De toute façon, il fonctionne. - Je faire cela quand l'application démarre et maintenant tous les contrôles fonctionnent comme prévu, et par exemple les dates sont formatées selon la culture actuelle.

mais maintenant le problème: selon MSDN FrameworkElement.LanguageProperty.OverrideMetadata ne peut être appelé qu'une seule fois. Et en effet, si je l'appelle à nouveau (quand l'utilisateur change la langue) lèvera une exception. Donc je n'ai pas vraiment résolu mon problème.

la question: Comment puis-je mettre à jour de manière fiable la culture dans WPF plus d'une fois et à tout moment dans mon cycle de vie des applications?

(j'ai trouvé ceci en cherchant: http://www.nbdtech.com/Blog/archive/2009/03/18/getting-a-wpf-application-to-pick-up-the-correct-regional.aspx et il semble qu'il ait quelque chose qui travaille là-bas. Cependant, je ne peux pas imaginer comment faire cela dans ma demande. Il semble que je devrais mettre à jour le langage dans toutes les fenêtres ouvertes et les contrôles et rafraîchir toutes les liaisons existantes, etc.)

51
demandé sur Community 2010-10-28 12:48:23

7 réponses

je vais sonner ici.

j'ai réussi à le faire en utilisant la méthode OverrideMetadata() que L'OP mentionnait:

var lang = System.Windows.Markup.XmlLanguage.GetLanguage(MyCultureInfo.IetfLanguageTag);
FrameworkElement.LanguageProperty.OverrideMetadata(
  typeof(FrameworkElement), 
  new FrameworkPropertyMetadata(lang)
);

mais, j'ai quand même trouvé des cas dans mon WPF où la culture du système était appliquée pour les dates et les valeurs numériques. Il s'est avéré qu'il s'agissait de valeurs dans <Run> éléments. Cela s'est produit parce que la classe System.Windows.Documents.Run n'hérite pas de System.Windows.FrameworkElement , et donc la suppression des métadonnées sur FrameworkElement n'a évidemment pas eu d'effet.

System.Windows.Documents.Run hérite de son bien Language de System.Windows.FrameworkContentElement à la place.

Et donc, la solution évidente était de remplacer les métadonnées sur FrameworkContentElement de la même manière. Hélas, do do do a fait une exception ( PropertyMetadata est déjà enregistré pour le système de type.Windows.Cadrorkcontentelement ), et donc j'ai dû le faire sur le prochain descendant ancêtre de Run à la place, System.Windows.Documents.TextElement :

FrameworkContentElement.LanguageProperty.OverrideMetadata(
  typeof(System.Windows.Documents.TextElement), 
  new FrameworkPropertyMetadata(lang)
);

et ça a réglé tous mes problèmes.

il y a quelques sous-classes supplémentaires de FrameworkContentElement (listées ici ) qui, pour être complètes, devraient également avoir leurs métadonnées effacées.

8
répondu Ross 2014-02-06 13:06:18

Je ne suis pas sûr de savoir comment contourner l'exception" ne peut pas appeler OverrideMetadata plusieurs fois".

comme solution de contournement, lorsque l'utilisateur change les cultures D'UI dans votre application, vous pouvez redémarrer votre application avec cette culture, en passant dans la nouvelle culture comme argument de ligne de commande. À moins que vos utilisateurs ne changent souvent de culture, cela semble être une solution raisonnable.

5
répondu Judah Himango 2010-11-08 19:55:14

je n'ai jamais trouvé un moyen de faire exactement ce que j'ai demandé dans la question. Dans mon cas, j'ai fini par le résoudre en faisant hériter tous mes usercontrols d'une superclasse qui contenait ceci:

/// <summary>
///   Contains shared logic for all XAML-based Views in the application. 
///   Views that extend this type will have localization built-in.
/// </summary>
public abstract class ViewUserControl : UserControl
{
    /// <summary>
    ///   Initializes a new instance of the ViewUserControl class.
    /// </summary>
    protected ViewUserControl()
    {
        // This is very important! We make sure that all views that inherit 
        // from this type will have localization built-in. 
        // Notice that the following line must run before InitializeComponent() on 
        // the view. Since the supertype's constructor is executed before the type's 
        // own constructor (which call InitializeComponent()) this is as it 
        // should be for classes extending this
        this.Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag);
    }
}

lorsque l'utilisateur change la langue, je crée alors de nouvelles instances de contrôles utilisateurs qui sont en cours d'exécution.

Cela a résolu mon problème. Cependant, je voudrais quand même trouver un moyen de le faire "automatiquement" (c'est-à-dire sans avoir à tenir compte tout objets instanciés).

5
répondu T.J.Kjaer 2013-03-25 09:59:57

juste mes deux cents: Après presque devenir fou en essayant de mettre en œuvre ComponentOne contrôles WPF (DataGrid et C1DatePicker) avec mon assemblée de langue allemande, je suis tombé sur cette page.

cela semble être une mise en scène dans le bon sens: je viens d'entrer le code ci-dessus dans mon application.XAML.la routine cs / Application_startup et maintenant le formatage allemand de la date et de l'heure pour C1DatePicker fonctionne enfin.

doit tester DataGrid juste après ça.

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        FrameworkElement.LanguageProperty.OverrideMetadata(
            typeof(FrameworkElement),
            new FrameworkPropertyMetadata(
            System.Windows.Markup.XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag)));
    }

Merci!

mise à jour: testé C1datagrid pour WPF - works! Cela a résolu tous les problèmes que j'avais avec les paramètres internationaux de Date / heure dans mes Applications. Grand!

4
répondu RJ Moeller 2013-02-12 17:23:55

j'ai eu le même problème.

j'ai trouvé ceci: http://www.codeproject.com/Articles/35159/WPF-Localization-Using-RESX-Files (pourrait ne pas être la source originale).

il traite d'une extension de markup nommée" UICultureExtension " qui est attachée à la propriété de langage de tous les éléments de cadre qui doivent être localisés (dans XAML).

si vous soulevez un changement de langage UI événement, statique en arrière-plan, les responsables de la vulgarisation mettront à jour tous les éléments du cadre enregistré.

3
répondu rbee 2013-07-24 08:42:05

Adaptive OverrideMetadata

une certaine forme de rechargement est inévitable, car changer la propriété Language d'un contrôle ne le fait pas mettre à jour son texte.

cependant, il y a un moyen d'outrepasser les métadonnées qui vous permet de les définir une fois et d'avoir nouveau commandes utiliser automatiquement la culture actuelle:

FrameworkElement.LanguageProperty.OverrideMetadata(
    typeof(FrameworkElement),
    new FrameworkPropertyMetadata(
        System.Windows.Markup.XmlLanguage.Empty,
        default(PropertyChangedCallback),
        _CoerceCurrentXmlLang));

CoerceValueCallback est

private static object _CoerceCurrentXmlLang(DependencyObject d, object baseValue)
{
    var lang = baseValue as System.Windows.Markup.XmlLanguage;
    var culture = System.Globalization.CultureInfo.CurrentUICulture;
    return lang != null && lang.IetfLanguageTag.Equals(culture.Name, StringComparison.InvariantCultureIgnoreCase)
        ? lang
        : System.Windows.Markup.XmlLanguage.GetLanguage(culture.Name);
}

par lui-même ce n'est pas tout à fait suffisant, parce que les contrôles nouvellement créés obtiendront la valeur par défaut System.Windows.Markup.XmlLanguage.Empty sans qu'elle soit forcée. Cependant, si vous mettez xml:lang="" dans votre windows XAML, cela sera forcé, et alors chaque nouveau contrôle verra qu'il hérite d'une valeur de son parent et le forcera. Le résultat est que les nouveaux contrôles ajoutés à cette fenêtre va utiliser le langage courant.

PS comme pour beaucoup de choses dans la FPF, ce serait un peu plus simple s'ils n'avaient pas été si désireux de garder les choses internal . DefaultValueFactory serait beaucoup plus élégante façon de le faire.

rechargement

Le plus extrême, mais donc fiable, le mode de rechargement est de créer une nouvelle fenêtre principale et jeter l'ancien.

presque aussi extrême mais pas tout à fait est de faire en sorte que le réglage de la langue ne soit modifié que dans un volet très simple de la fenêtre principale avec très peu de charge, et que très peu d'être entièrement databound à un viewmodel qui supporte forcer une notification de changement de propriété pour tout.

réponses existantes à cette question ont d'autres suggestions.

3
répondu Peter Taylor 2016-01-28 16:05:54

ce n'est pas entièrement votre réponse, mais j'ai utilisé ceci pour recharger les ressources. Mais tu dois quand même recharger les fenêtres...

 List<Uri> dictionaryList = new List<Uri>();
        foreach (ResourceDictionary dictionary in Application.Current.Resources.MergedDictionaries)
        {
            dictionaryList.Add(dictionary.Source);
        }
        Application.Current.Resources.MergedDictionaries.Clear();
        foreach (Uri uri in dictionaryList)
        {
            ResourceDictionary resourceDictionary1 = new ResourceDictionary();
            resourceDictionary1.Source = uri;
            Application.Current.Resources.MergedDictionaries.Add(resourceDictionary1);
        }
1
répondu Emmanuel 2010-11-09 08:18:50