概述
设置Form、UserControl的Localizable设置成ture后对应的文件下会生成一个同名的.resx资源文件,对应的.Designer.cs文件内会生成如下代码:
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
resources.ApplyResources(this, "$this");
resources.ApplyResources(this.button4, "button4");
resources.GetString("comboBoxEdit1.Properties.Items")
resources是对应取.resx的对象,ApplyResources和GetString方法取资源内容,假如我们能够拦截resources对象并且重写ApplyResources和GetString是不是就能做导ui多语言的最优解?答案是肯定的。
ComponentDesigner
我们可以通过扩展组件的设计模式行为来定义一个控件,基 ComponentDesigner 设计器类提供了一个简单的设计器,该设计器可以在设计模式下扩展关联组件的行为。
public class LocalizerDesigner : ComponentDesigner
{
public override void Initialize(IComponent c)
{
base.Initialize(c);
var dh = (IDesignerHost)GetService(typeof(IDesignerHost));
if (dh == null)
return;
var innerListProperty = dh.Container.Components.GetType().GetProperty("InnerList", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.FlattenHierarchy);
var innerList = innerListProperty.GetValue(dh.Container.Components, null) as System.Collections.ArrayList;
if (innerList == null)
return;
if (innerList.IndexOf(c) <= 1)
return;
innerList.Remove(c);
innerList.Insert(0, c);
}
}
CodeDomSerializer
将对象图序列化为一系列 CodeDOM 语句。 此类提供序列化程序的抽象基类。
可以实现自定义 CodeDomSerializer ,以控制在设计时为某一类型的组件生成组件初始化代码。
"WinformMultilingual.Localizer"替换成自己的命名空间,此代码对应Designer.cs里拖控件时自动生成替换代码WinformMultilingual.Localizer.GetResourceManager(typeof(Form1), out resources);
public class LocalizerSerializer : CodeDomSerializer
{
public override object Deserialize(IDesignerSerializationManager manager, object codeDomObject)
{
CodeDomSerializer baseSerializer = (CodeDomSerializer)
manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer));
return baseSerializer.Deserialize(manager, codeDomObject);
}
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeDomSerializer baseSerializer =
(CodeDomSerializer)manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer));
object codeObject = baseSerializer.Serialize(manager, value);
if (codeObject is CodeStatementCollection)
{
CodeStatementCollection statements = (CodeStatementCollection)codeObject;
CodeTypeDeclaration classTypeDeclaration =
(CodeTypeDeclaration)manager.GetService(typeof(CodeTypeDeclaration));
CodeExpression typeofExpression = new CodeTypeOfExpression(classTypeDeclaration.Name);
CodeDirectionExpression outResourceExpression = new CodeDirectionExpression(
FieldDirection.Out, new CodeVariableReferenceExpression("resources"));
CodeExpression rightCodeExpression =
new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("WinformMultilingual.Localizer"), "GetResourceManager",
new CodeExpression[] { typeofExpression, outResourceExpression });
statements.Insert(0, new CodeExpressionStatement(rightCodeExpression));
}
return codeObject;
}
}
Localizer
设计一个Localizer控件。
[Designer(typeof(LocalizerDesigner))]
[DesignerSerializer(typeof(LocalizerSerializer), typeof(CodeDomSerializer))]
public class Localizer : Component
{
public static void GetResourceManager(Type type, out ComponentResourceManager resourceManager)
{
resourceManager = new MyResourceManager(type);
}
}
MyResourceManager
在MyResourceManager类中就可以拦截获取资源的代码,从xml或者json等文件中获取,从而实现兼容【较为自动化和高效】与【高度动态化和扩展性】能同时兼备。
public class MyResourceManager : ComponentResourceManager
{
private Hashtable _resourceSets;
private CultureInfo _neutralResourcesCulture;
private Type _locationInfo;
/// <devdoc>
/// The culture of the main assembly's neutral resources. If someone is asking for this culture's resources,
/// we don't need to walk up the parent chain.
/// </devdoc>
private CultureInfo NeutralResourcesCulture
{
get
{
if (_neutralResourcesCulture == null && MainAssembly != null)
{
_neutralResourcesCulture = GetNeutralResourcesLanguage(MainAssembly);
}
return _neutralResourcesCulture;
}
}
public MyResourceManager(Type type) : base(type)
{
_locationInfo = type;
}
/// <devdoc>
/// This method examines all the resources for the provided culture.
/// When it finds a resource with a key in the format of
/// "[objectName].[property name]" it will apply that resource's value
/// to the corresponding property on the object. If there is no matching
/// property the resource will be ignored.
/// </devdoc>
public override void ApplyResources(object value, string objectName, CultureInfo culture)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (objectName == null)
{
throw new ArgumentNullException("objectName");
}
if (culture == null)
{
culture = CultureInfo.CurrentUICulture;
}
// The general case here will be to always use the same culture, so optimize for
// that. The resourceSets hashtable uses culture as a key. It's value is
// a sorted dictionary that contains ALL the culture values (so it traverses up
// the parent culture chain) for that culture. This means that if ApplyResources
// is called with different cultures there could be some redundancy in the
// table, but it allows the normal case of calling with a single culture to
// be much faster.
//
// The reason we use a SortedDictionary here is to ensure the resources are applied
// in an order consistent with codedom deserialization.
SortedList<string, object> resources;
if (_resourceSets == null)
{
ResourceSet dummy;
_resourceSets = new Hashtable();
resources = FillResources(culture, out dummy);
_resourceSets[culture] = resources;
}
else
{
resources = (SortedList<string, object>)_resourceSets[culture];
if (resources == null || (resources.Comparer.Equals(StringComparer.OrdinalIgnoreCase) != IgnoreCase))
{
ResourceSet dummy;
resources = FillResources(culture, out dummy);
_resourceSets[culture] = resources;
}
}
BindingFlags flags = BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance;
if (IgnoreCase)
{
flags |= BindingFlags.IgnoreCase;
}
bool componentReflect = false;
if (value is IComponent)
{
ISite site = ((IComponent)value).Site;
if (site != null && site.DesignMode)
{
componentReflect = true;
}
}
foreach (KeyValuePair<string, object> kvp in resources)
{
// See if this key matches our object.
//
string key = kvp.Key;
if (key == null)
{
continue;
}
if (IgnoreCase)
{
if (string.Compare(key, 0, objectName, 0, objectName.Length, StringComparison.OrdinalIgnoreCase) != 0)
{
continue;
}
}
else
{
if (string.CompareOrdinal(key, 0, objectName, 0, objectName.Length) != 0)
{
continue;
}
}
// Character after objectName.Length should be a ".", or else we should continue.
//
int idx = objectName.Length;
if (key.Length <= idx || key[idx] != '.')
{
continue;
}
// Bypass type descriptor if we are not in design mode. TypeDescriptor does an attribute
// scan which is quite expensive.
//
string propName = key.Substring(idx + 1);
if (componentReflect)
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(value).Find(propName, IgnoreCase);
if (prop != null && !prop.IsReadOnly && (kvp.Value == null || prop.PropertyType.IsInstanceOfType(kvp.Value)))
{
prop.SetValue(value, kvp.Value);
}
}
else
{
PropertyInfo prop = null;
try
{
prop = value.GetType().GetProperty(propName, flags);
}
catch (AmbiguousMatchException)
{
// Looks like we ran into a conflict between a declared property and an inherited one.
// In such cases, we choose the most declared one.
Type t = value.GetType();
do
{
prop = t.GetProperty(propName, flags | BindingFlags.DeclaredOnly);
t = t.BaseType;
} while (prop == null && t != null && t != typeof(object));
}
if (prop != null && prop.CanWrite && (kvp.Value == null || prop.PropertyType.IsInstanceOfType(kvp.Value)))
{
prop.SetValue(value, kvp.Value, null);
}
}
}
}
/// <devdoc>
/// Recursive routine that creates a resource hashtable
/// populated with resources for culture and all parent
/// cultures.
/// </devdoc>
private SortedList<string, object> FillResources(CultureInfo culture, out ResourceSet resourceSet)
{
SortedList<string, object> sd;
ResourceSet parentResourceSet = null;
// Traverse parents first, so we always replace more
// specific culture values with less specific.
//
if (!culture.Equals(CultureInfo.InvariantCulture) && !culture.Equals(NeutralResourcesCulture))
{
sd = FillResources(culture.Parent, out parentResourceSet);
}
else
{
// We're at the bottom, so create the sorted dictionary
//
if (IgnoreCase)
{
sd = new SortedList<string, object>(StringComparer.OrdinalIgnoreCase);
}
else
{
sd = new SortedList<string, object>(StringComparer.Ordinal);
}
}
// Now walk culture's resource set. Another thing we
// do here is ask ResourceManager to traverse up the
// parent chain. We do NOT want to do this because
// we are trawling up the parent chain ourselves, but by
// passing in true for the second parameter the resource
// manager will cache the culture it did find, so when we
// do recurse all missing resources will be filled in
// so we are very fast. That's why we remember what our
// parent resource set's instance was -- if they are the
// same, we're looking at a cache we've already applied.
//
resourceSet = GetResourceSet(culture, true, true);
if (resourceSet != null && !object.ReferenceEquals(resourceSet, parentResourceSet))
{
foreach (DictionaryEntry de in resourceSet)
{
sd[(string)de.Key] = de.Value;
}
}
//Todo 获取拉取的配置
var dic = LanguageProvider.Provider.GetSortedList(_locationInfo, culture);
foreach (var kv in dic)
{
sd[kv.Key] = kv.Value;
}
return sd;
}
// Looks up a resource value for a particular name. Looks in the
// specified CultureInfo, and if not found, all parent CultureInfos.
// Returns null if the resource wasn't found.
//
public override string GetString(string name, CultureInfo culture)
{
//Todo 获取拉取的配置
var value = LanguageProvider.Provider.GetString(_locationInfo,name, culture);
if (value != null)
return value.ToString();
return base.GetString(name, culture);
}
}
怎么使用
使用时,设置Form、UserControl的Localizable设置成true,并拖当前控件到窗体,.Designer.cs中会自动生成拦截代码,替换资源成我们自己的对象,如下:
WinformMultilingual.Localizer.GetResourceManager(typeof(Form1), out resources);
并设置CurrentCulture即可完成语言切换。这应该是最优解。
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(CultureId);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(CultureId);
以上~~