以编程方式将转换器添加到 ResourceDictionary

Adding a Converter to a ResourceDictionary Programmatically

为了避免在每个控件的 XAML 的资源部分中编写语法 <converters:MyConverter x:Key="myConverter"/> 我想将转换器用作静态资源,我想尝试添加每个转换器在该控件的范围内以编程方式访问其资源字典。

我所做的是创建我自己的自定义 ControlBase class 派生自 UserControl 然后我将继承我的任何自定义控件而不是 UserControl. ControlBase 负责将其自己的程序集中的任何资源(转换器、图像、模板选择器等)和 MediaLibrary 加载到其自身的 Resources 属性 中。它还具有一个名为 LoadCallerResources(ResourceTypes resourceTypes) 的受保护方法,该方法将派生控件中指定类型的任何资源加载到 Resources 字典中。

所有这些功能的核心方法如下。请注意,我在此代码中使用了几个我自己的扩展方法和补充方法,例如 ResourceTypes 标志枚举上的 "AsEnumerable","ThrowArgumentNullExceptionIfNull"、"ToCamelCase" 和 "GetResourceTypePlurals" 用于获取提供的枚举中每种资源类型的复数名称(对应于该类型资源预期所在的目录)。

/// <summary>
/// Loads all .g.resources files found within the provided assembly that meet the requirements for the specified resource type into the <see cref="FrameworkElement.Resources"/> collection.
/// </summary>
/// <param name="resourceTypes">The type of data in the .g.resources files to load.</param>
/// <param name="resourceAssembly">The assembly to load the resources from.</param>
/// <exception cref="ArgumentNullException">Thrown if resourceAssembly is null.</exception>
private void LoadAssemblyResources(ResourceTypes resourceTypes, Assembly resourceAssembly)
{
    Stream manifestResourceStream = resourceAssembly
        .ThrowArgumentNullExceptionIfNull(nameof(resourceAssembly))
        .GetManifestResourceStream(resourceAssembly.GetName().Name + ".g.resources");

    if (manifestResourceStream != null)
        using (ResourceReader resourceReader = new ResourceReader(manifestResourceStream))
            foreach (DictionaryEntry relevantDictionaryEntry in resourceReader
                .Cast<DictionaryEntry>()
                .Where(dictionaryEntry =>
                {
                    string resourceFilePath = dictionaryEntry.Key.ToString();
                    string resourceDirectory = Path.GetDirectoryName(dictionaryEntry.Key.ToString());
                    string resourceFileName = Path.GetFileName(resourceFilePath);

                    return string.IsNullOrEmpty(resourceDirectory) && string.Equals(Path.GetExtension(resourceFileName), ".baml", StringComparison.OrdinalIgnoreCase) && resourceTypes.AsEnumerable().Any(resourceType => resourceFileName.StartsWith(Enum.GetName(typeof(ResourceTypes), resourceType), StringComparison.OrdinalIgnoreCase))
                        || GetResourceTypesPlurals(resourceTypes).Any(resourceTypePlural => resourceDirectory.Contains(resourceTypePlural, CompareInfo.GetCompareInfo(CultureInfo.InvariantCulture.Name), false));
                }))
                    Resources.Add(Path.GetFileNameWithoutExtension(relevantDictionaryEntry.Key.ToString()).ToCamelCase(), relevantDictionaryEntry.Value);
}

我在使用这段代码时遇到了一些问题:

  1. 资源全部小写,而它们的源文件以 Pascal Case 命名(我想将其转换为 Camel Case)
  2. StaticResource 绑定到转换器,即使拼写为全小写字母,也会给编译器错误 'The resource "myconvertername" could not be resolved.'
  3. 为了在 .g.resources 中找到转换器(.cs 文件),其构建操作必须从 "Compile" 更改为 "Resource" - 这不是意味着无论如何,当代码用作静态资源时,它会有任何意义吗?

我的最终目标是能够引用 XAML 中的静态资源,而不必在控件的字典中显式声明它们。我该怎么做?

To save having to write the syntax in a Resources section of XAML

我赞成这种情况的方法是让我的转换器实现 MarkupExtension。这样做可以让您无需将其声明为 Xaml.

中的资源
  public class InvertedBooleanConverter : MarkupExtension, IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      return null; //put logic here
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      return null; //put logic here
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
      return this;
    }
  }

然后您可以在 xaml 中使用它:

IsEnabled="{Binding someValue, Converter={yourNamespace:InvertedBooleanConverter}}"