WPF: Developing an inflector of resource dictionary keys

.NET WPF XAML

.NET WPF XAMLAccording to the dictionary (not that of resources smiley clin d'oeil , but rather that of the English language: the freed dictionary, “an inflector is the way a word is changed or modified in form in order to reach a new specific meaning“.

In the case of a WPF resource dictionary, a key inflection mechanism makes it possible to substitute one resource for another. If the inflection rule is constant, the resource can be shared (cached as in standard dictionary usage).

The implementation consists of adding a behavior class to a resource dictionary. The solution is based on the OnGettingValue method of the ResourceDictionary class. This method is called when the dictionary receives a resource request:

public partial class BaseMap : ResourceDictionary
{
    /// <summary>
    /// the inflector of resource dictionary keys
    /// </summary>
    IResourceKeyMapper RscKeyMapper;

    /// <summary>
    /// makes a new instance
    /// </summary>
    public BaseMap()
        : base()
    {
        RscKeyMapper = new SampleKeyMapper();
        InitializeComponent();
    }

    /// <summary>
    /// the dictionary receives a resource request
    /// </summary>
    /// <param name="key">The key of the resource to get</param>
    /// <param name="value">The value of the requested resource</param>
   /// <param name="canCache">true if the resource can be saved and used later, otherwise false</param>
    protected override void OnGettingValue(object key, ref object value, out bool canCache)
    {            
        RscKeyMapper.OnGettingValue(key,ref value,out canCache);
    }
}

The inflection method is defined in a class that responds to the IResourceKeyMapper interface that is decoupled from the dictionary, allowing its reuse for other dictionaries. The latter is defined as follows:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib"
                    x:Class="BaseMap"
>

    <!-- menu bar -->

    <FontFamily x:Key="Menu_FontFamily"/>
    <sys:Double x:Key="Menu_FontSize"/>
    <FontStyle x:Key="Menu_FontStyle"/>
    <FontWeight x:Key="Menu_FontWeight"/>

    <!-- Context Menu -->

    <FontFamily x:Key="ContextMenuItem_FontFamily"/>
    <FontWeight x:Key="ContextMenuItem_FontWeight"/>
    <FontStyle x:Key="ContextMenuItem_FontStyle"/>
    <sys:Double x:Key="ContextMenuItem_FontSize"/>

</ResourceDictionary>

In this dictionary example, we define keys without values ​​to style the fonts of Menu and ContextMenu. The idea of ​​the inflector in such a situation is to allow to provide identical values ​​of fonts for different controls (harmonization) or different values, choice that can be made dynamically by the application. Personally I use this system also to solve color charts for themes.
The inflector is then described in the following class, which must respond to the IResourceKeyMapper :

/// <summary>
/// map the requested keys according to a transformation function
/// implements the OnGettingValue method of ResourceDictionary
/// </summary>
public class SampleKeyMapper : IResourceKeyMapper
{
    /// <summary>
    /// makes a new instance
    /// </summary>
    public SampleKeyMapper()
    {

    }

    /// <summary>
    /// the dictionary receives a resource request
    /// </summary>
    /// <param name="key">The key of the resource to get</param>
    /// <param name="value">The value of the requested resource</param>
    /// <param name="canCache">true if the resource can be saved and used later, otherwise false</param>
    public void OnGettingValue(object key, ref object value, out bool canCache)
    {
        canCache = false;
        try
        {
            var s = GetKeyInflection((string)key);
            var r = Application.Current.TryFindResource(s);
            if (r != null)
            {
                value = r;
                canCache = true;
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex);
        }
    }

    /// <summary>
    /// inflection of key to another key according to a heuristic
    /// </summary>
    /// <param name="key">key of the requested resource</param>
    /// <returns>inflected key</returns>
    protected virtual string GetKeyInflection(string key)
    {
        var newKey = ... ;  /* THE INFLECTION FUNCTION IS DEFNIED HERE */
        System.Diagnostics.Debug.WriteLine($"{key} -> {newKey}");
        return newKey;
    }
}

The interface is defined as follows:

public interface IResourceKeyMapper
{
    /// <summary>
    /// the dictionary receives a resource request
    /// </summary>
    /// <param name="key">The key of the resource to get</param>
    /// <param name="value">The value of the requested resource </param>
  /// <param name="canCache">true if the resource can be saved and used later, otherwise false</param>
    void OnGettingValue(object key, ref object value, out bool canCache);
}

Leave a Reply