WPF自定义标记扩展如何实现ResX资源键智能提示?
我正在开发一款WPF全球化应用,使用.resx文件维护本地化资源,自定义了Loc标记扩展用于XAML中绑定本地化资源,但在XAML编辑该扩展时无法获得资源键的智能提示。尝试通过TypeConverter实现该功能,但未生效,现需明确:
- 该方案是否可行?
- 若可行,当前实现存在哪些问题?
- 若不可行,还有其他实现方法吗?
现有实现代码
自定义Loc标记扩展
[MarkupExtensionReturnType(typeof(object))] public class LocExtension : MarkupExtension { public LocExtension() { } public LocExtension(LocalizationKey key) { Key = key; } public LocalizationKey Key { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) { if (DesignerHelper.IsInDesignMode) { return $"[{Key}]"; } if (string.IsNullOrEmpty(Key.Key)) { return string.Empty; } try { string? value = language.ResourceManager.GetString(Key, CultureInfo.CurrentUICulture); return value ?? $"#{Key.Key}#"; } catch { return $"!ERR:{Key.Key}!"; } } }
ResXKeyTypeConverter实现
public class ResXKeyTypeConverter : TypeConverter { public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) { if (destinationType == typeof(string)) { if (context != null && context.Instance != null) { if (!(context.Instance is LocalizationKey localizationKey)) { throw new ArgumentException("context.Instance is not LocalizationKey", nameof(context)); } return true; } return true; } return base.CanConvertTo(context, destinationType); } public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); } public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { if (value == null) { throw GetConvertFromException(value); } if (value is string source) { return new LocalizationKey(source); } return base.ConvertFrom(context, culture, value); } public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) { if ( value is LocalizationKey localizationKey) { if (destinationType == typeof(string)) { // When invoked by the serialization engine we can convert to string only for some instances if (context != null && context.Instance != null) { throw new NotSupportedException("null reference in resx key typeConverter "); } return localizationKey.ToString(); } } // Pass unhandled cases to base class (which will throw exceptions for null value or destinationType.) return base.ConvertTo(context, culture, value, destinationType); } public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true; public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true; public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context) { try { var keys = typeof(language) .GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) .Where(p => p.PropertyType == typeof(string)) .Select(p => p.Name) .OrderBy(name => name) .ToList(); return new StandardValuesCollection(keys); } catch { string[] empty = ["aaa", "bbb"]; return new StandardValuesCollection(empty); } } }
LocalizationKey类型
[TypeConverter(typeof(ResXKeyTypeConverter))] public class LocalizationKey { public string Key { get; set; } public LocalizationKey() { } public LocalizationKey(string key) { Key = key; } public override string ToString() { return Key ?? string.Empty; } public static implicit operator string(LocalizationKey localizationKey) { return localizationKey?.Key; } public static implicit operator LocalizationKey(string key) { return new LocalizationKey(key); } }
XAML使用示例
<TextBlock Text="{markupExt:Loc Key=}"/> <TextBlock Text="{markupExt:Loc}"/>
问题解答
方案可行性确认
通过TypeConverter实现XAML中本地化资源键的智能提示是可行的,Visual Studio的XAML编辑器会识别带有TypeConverter属性的类型,并通过GetStandardValues方法获取候选值来提供智能提示。
当前实现的问题分析
隐式转换与TypeConverter冲突
为LocalizationKey定义的与string的双向隐式转换,会让XAML编辑器优先使用隐式转换而非TypeConverter处理类型转换,直接跳过了TypeConverter中GetStandardValues的逻辑,导致智能提示无法触发。ConvertTo方法的异常逻辑阻断流程
ConvertTo方法中,当context.Instance不为空时抛出NotSupportedException,而XAML编辑器处理标记扩展属性时大概率会传入包含实例的上下文,这会导致转换失败,中断智能提示的生成流程。反射获取资源键的潜在风险
直接通过typeof(language)获取资源类,若该类的命名空间未正确引用、编译时未生成或类名有误,会导致反射失败,返回默认的测试值而非真实资源键,无法提供有效提示。
修正方案
移除LocalizationKey的隐式转换
删除LocalizationKey中与string的双向隐式转换代码,确保XAML编辑器使用TypeConverter处理类型转换。修复ConvertTo方法的异常逻辑
移除不必要的异常抛出,确保字符串转换正常执行:public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) { if (value is LocalizationKey localizationKey && destinationType == typeof(string)) { return localizationKey.ToString(); } return base.ConvertTo(context, culture, value, destinationType); }确保资源类反射的正确性
确认language是.resx自动生成的强类型资源类,且命名空间已在当前项目中正确引用,若资源类名称或命名空间变更,需同步修改反射代码中的类型引用。优化标记扩展构造函数(可选)
支持{markupExt:Loc ResourceKeyName}的简洁写法,调整构造函数参数类型:public LocExtension(string key) { Key = new LocalizationKey(key); }
替代实现方法
使用Enum模拟资源键
将所有资源键定义为Enum类型,标记扩展使用该Enum作为Key的类型,Visual Studio会自动为Enum提供智能提示。缺点是需要手动维护Enum与.resx键的同步,适合资源键较少的场景。自定义XAML编辑器扩展
通过Visual Studio SDK编写自定义XAML编辑器扩展,直接解析.resx文件并提供智能提示。该方案灵活性最高,但开发成本较大,适合复杂或团队级项目。
内容的提问来源于stack exchange,提问作者xinchenglei

