在全球化的今天,应用程序往往需要支持多种语言,以满足不同地区用户的需求。本文将介绍如何创建一个能够支持多种语言的WPF应用程序。将通过ResourceDictionary和MEF(Managed Extensibility Framework)来实现这一目标。
ResourceDictionary是基于XML的,它利用了XML规范中定义的全球化支持。可以为每种语言创建多个资源文件,并将它们添加到应用程序的根级别(App.xaml)中,以在整个应用程序中实现多语言支持。
首先,需要创建资源。在WPF项目中,可以通过右键点击项目并选择"添加新项",然后从列表中选择"UserControl"。接下来,将UserControl转换为ResourceDictionary。
为什么要先添加UserControl然后转换为ResourceDictionary,而不是直接添加ResourceDictionary呢?这是因为接下来将使用MEF(Import/Export)类。
给ResourceDictionary页面一个基于语言的合适名称,例如:EnglishLanguage.xaml,并编写如下字符串资源:
<ResourceDictionary x:Class="WPF_Globalization.Resources.EnglishLanguage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d">
<s:String x:Key="keyEmployeeName">Employee Name:</s:String>
<s:String x:Key="keyAddress">Address:</s:String>
<s:String x:Key="keyCountry">Country:</s:String>
<s:String x:Key="keyState">State:</s:String>
<s:String x:Key="keyCity">City:</s:String>
<s:String x:Key="keyPhone">Phone Number:</s:String>
<s:String x:Key="keyDesignation">Designation:</s:String>
</ResourceDictionary>
在上面的代码中,x:key用于字符串,是一个唯一的名称,用于标识字符串资源。
要在应用程序中使用全局文件资源,需要设置DynamicResource;对于本地文件资源,则需要设置StaticResource。
<TextBlock Grid.Row="0" Grid.Column="0" Text="{DynamicResource keyEmployeeName}" />
在这个应用程序中,创建了一个英语和法语的演示,可以根据需要创建更多的资源文件。同样的方式,为其他语言添加一个ResourceDictionary文件,例如FrenchLanguage.xaml,如下所示:
<ResourceDictionary x:Class="WPF_Globalization.Resources.FrenchLanguage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d">
<s:String x:Key="keyEmployeeName">Nom de l'employé:</s:String>
<s:String x:Key="keyAddress">adresse:</s:String>
<s:String x:Key="keyCountry">pays:</s:String>
<s:String x:Key="keyState">état:</s:String>
<s:String x:Key="keyCity">ville:</s:String>
<s:String x:Key="keyPhone">Numéro de téléphone:</s:String>
<s:String x:Key="keyDesignation">désignation:</s:String>
</ResourceDictionary>
Microsoft .NET Framework提供了一个System.ComponentModel.Composition命名空间,它提供了构成MEF(Managed Extensibility Framework)核心的类。有关更多详细信息,请访问MSDN。
MEF(Managed Extensibility Framework)是.NET的一个组合层,它提高了大型应用程序的灵活性、可维护性和可测试性。它允许应用程序开发人员发现和使用扩展,无需任何配置。通过使用MEF,开发人员可以轻松地封装代码并避免脆弱的硬依赖。
MEF组件(类、方法、属性)指定了它的依赖项(Imports)和功能(Exports),这些依赖项和功能由运行时发现。当创建一个对象时,MEF组合引擎会用其他对象提供的内容满足其导入需求。它提供了导出,一个满足导入的对象。
在MEF中有许多可用的属性,除了它们之外,在本应用程序中使用了以下属性:
打开EnglishLanguage.xaml.cs文件,并编写以下代码以导出此类(资源):
[ExportMetadata("Culture", "en-US")]
[Export(typeof(ResourceDictionary))]
public partial class EnglishLanguage : ResourceDictionary
{
public EnglishLanguage()
{
InitializeComponent();
}
}
ExportMetadata指定类型的元数据(或者说它将在键值对中实现操作)。
同样的方式为FrenchLanguage类附加元数据。
现在,创建一个类,它有一个属性来导入所有导出的类,如下所示:
public class ImportModule
{
[ImportMany(typeof(ResourceDictionary))]
public IEnumerable<Lazy<ResourceDictionary, IDictionary<string, object>>> ResourceDictionaryList {
get;
set;
}
}
上述代码片段从不同的程序集中导入所有具有匹配类型ResourceDictionary的类。
组合容器是MEF的核心。它用于通过使用可组合部分目录来发现部分(对象)。目录可以是托管中的任何给定类型(如DirectoryCatalog、AssemblyCatalog、AggregateCatalog等)。
创建一个Singleton类,并为ImportModule类添加一个属性。
public class BaseModel
{
private static BaseModel _instance;
public static BaseModel Instance
{
get
{
if (_instance == null)
_instance = new BaseModel();
return _instance;
}
}
private ImportModule _importCatalog;
public ImportModule ImportCatalog
{
get
{
_importCatalog = _importCatalog ?? new ImportModule();
return _importCatalog;
}
}
}
在App.xaml.cs文件的OnStartup事件中编写以下代码,使用目录导入所有类。
string path = AppDomain.CurrentDomain.BaseDirectory;
DirectoryCatalog catalog = new DirectoryCatalog(path);
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(BaseModel.Instance.ImportCatalog);
创建一个具有Code和Name属性的语言类,用于绑定。
public class Languages
{
public string Code {
get;
set;
}
public string Name {
get;
set;
}
}
在ViewModel中创建一个属性,它有一个语言列表。
private List<Languages> _languageList;
public List<Languages> LanguageList
{
get
{
return _languageList;
}
set
{
_languageList = value;
RaisePropertyChanged("LanguageList");
}
}
LanguageList = new List<Languages>();
LanguageList.Add(new Languages() { Code = "en-US", Name = "English" });
LanguageList.Add(new Languages() { Code = "fr-FR", Name = "French" });
在Usercontrol中添加一个ComboBox,用于更改语言,并将ViewModel类中的语言绑定到它。
<ComboBox x:Name="LanguageComboBox" Width="150" Margin="5" HorizontalAlignment="Left" DisplayMemberPath="Name" ItemsSource="{Binding LanguageList}" SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}" SelectionChanged="LanguageComboBox_SelectionChanged" />
编写代码以在从ComboBox更改选定语言时应用选定的语言资源。
private void LanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var currentResourceDictionary = (
from d in BaseModel.Instance.ImportCatalog.ResourceDictionaryList
where d.Metadata.ContainsKey("Culture")
&& d.Metadata["Culture"].ToString().Equals(vm.SelectedLanguage.Code)
select d).FirstOrDefault();
if (currentResourceDictionary != null)
{
Application.Current.Resources.MergedDictionaries.Add(currentResourceDictionary.Value);
CultureInfo cultureInfo = new CultureInfo(vm.SelectedLanguage.Code);
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
Application.Current.MainWindow.Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag);
}
}