MarkupExtension.cs

Exemple de MarkupExtension WPF avec rafraîchissement

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

[MarkupExtensionReturnType(typeof(string))]
public class TraductionExtension : MarkupExtension
{
    private static readonly HashSet<EventHandler> UiCultureChangedHandlers = new();
    private WeakReference _targetObjectRef;
    private object _targetProperty;

    public TraductionExtension(string path)
    {
        Path = path;
    }

    [ConstructorArgument("path")]
    public string Path { get; }

    public sealed override object ProvideValue(IServiceProvider serviceProvider)
    {
        var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        // Si le contexte est statique (exemple : DataTemplate), nous retournons `this` afin que la liaison soit évaluée dynamiquement.
        if (target != null && target.TargetObject.GetType().FullName == "System.Windows.SharedDp")
            return this;

        return ProvideValueInternal(serviceProvider);
    }

    private object ProvideValueInternal(IServiceProvider serviceProvider)
    {
        if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget
            {
                TargetObject: { }, TargetProperty: { }
            } target)
        {
            _targetObjectRef = new WeakReference(target.TargetObject);
            _targetProperty = target.TargetProperty;
            UiCultureChanged += TraductionExtension_UICultureChanged;
        }

        var traduction = GetTraduction();

        // Si la traduction est trouvé avec Path
        if (traduction != Path) return traduction;
        // Sinon créer un binding
        SetBinding();

        return Path;
    }

    private string GetTraduction()
    {
        var traduction = "";
        // TODO : Récupère la traduction
        // Si la traduction n'est pas trouvée (renvoie Path)
        return !string.IsNullOrEmpty(traduction) ? traduction : Path;
    }

    private void TraductionExtension_UICultureChanged(object sender, EventArgs e)
    {
        Refresh();
    }

    public static event EventHandler UiCultureChanged
    {
        add => UiCultureChangedHandlers.Add(value);
        remove => UiCultureChangedHandlers.Remove(value);
    }

    public static void OnUICultureChanged()
    {
        foreach (var handler in UiCultureChangedHandlers) handler(typeof(TraductionExtension), EventArgs.Empty);
    }

    private void Refresh()
    {
        if (!_targetObjectRef.IsAlive) // Si la référence n'est plus active
        {
            // Suppression du handler
            UiCultureChanged -= TraductionExtension_UICultureChanged;
            return;
        }

        var value = GetTraduction();

        if (_targetProperty is DependencyProperty dp)
        {
            var obj = _targetObjectRef.Target as DependencyObject;
            if (value.Equals(Path)) // Si la traduction n'est pas trouvé avec Path
                SetBinding(); // Créer un binding
            else
                obj.SetValue(dp, value); // Sinon applique la traduction à la propriété
        }
        else
        {
            var obj = _targetObjectRef.Target;
            var prop = _targetProperty as PropertyInfo;
            prop.SetValue(obj, value, null);
        }
    }

    private void SetBinding()
    {
        var obj = _targetObjectRef.Target as DependencyObject;
        var prop = _targetProperty as DependencyProperty;
        var frameworkElement = _targetObjectRef.Target as FrameworkElement;
        Binding binding = new Binding();
        binding.Source = frameworkElement.DataContext;
        // TODO : Modifier Binding attr
        BindingOperations.SetBinding(obj, prop, binding);
    }
}