Sphynx
Sphynx

Reputation: 155

Adding a Converter to a ResourceDictionary Programmatically

To save having to write the syntax <converters:MyConverter x:Key="myConverter"/> in a Resources section of XAML in every control I want to use a converter as a static resource, I had the idea to try add every converter in the scope of that control to its resource dictionary programmatically.

What I have done is to create my own custom ControlBase class which derives from UserControl which I would then inherit any of my custom controls from rather than UserControl. ControlBase is responsible for loading any resources (converters, images, template selectors, etc.) in its own assembly and a MediaLibrary into the Resources property of itself. It also has a protected method called LoadCallerResources(ResourceTypes resourceTypes) which loads any resources of the specified types from the derived control into the Resources dictionary.

The method which is core to all this functionality is as follows. Note that I am using several of my own extension methods and supplementary methods in this code such as "AsEnumerable" on the ResourceTypes flags enum, "ThrowArgumentNullExceptionIfNull", "ToCamelCase" and "GetResourceTypePlurals" used to get the plural name of each resource type on the provided enum (which corresponds to the directory the resources of that type are expected to be in).

/// <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);
}

There are a few issues I am having with this code:

  1. The resources come in as all lowercase whilst their source files are named in Pascal Case (which I would like to convert into Camel Case)
  2. StaticResource bindings to the converters, even when spelt in all -lowercase lettering, give the compiler error of 'The resource "myconvertername" could not be resolved.'
  3. In order for a converter (.cs file) to be found in .g.resources, its build action must be changed from "Compile" to "Resource" - doesn't this mean that the code will mean anything when used as a static resource anyway?

My ultimate goal is to be able to reference static resources in XAML without having to explicitly declare them in the dictionary of the control. How would I do this?

Upvotes: 1

Views: 2675

Answers (1)

Bill Tarbell
Bill Tarbell

Reputation: 5234

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

The approach i favor for this situation is to have my converters implement MarkupExtension. Doing so makes it so that you don't need to declare it as a resource in 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;
    }
  }

You can then use it as such in xaml:

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

Upvotes: 3

Related Questions