WPF:全球化 - 多语言处理 - LangBindingExtension

前言

当前随笔是基于 WPF - 全球化 - 多语言处理 上进行的扩展。

LangBindingExtension 的应用场景主要为了方便在数据模板中使用。

LangBindingExtension 的工作原理如下:

  1. 绑定元素的数据上下文的属性
  2. 获取绑定元素的数据上下文的属性值(属性值 = LangKey)
  3. 将元素的目标依赖属性和 LangProvider 中对应的 LangKey 进行绑定
  4. 如果,元素的数据上下文继承自 INotifyPropertyChanged 接口。那么,属性值变化后,重新执行第3步。

分析上述的工作原理得出,需要进行两次绑定,并且这两次绑定是一种级联关系。

binding1:是为了获取数据上下文中指定的属性值,这个属性值指向语言包中的静态属性名。并且,需要监听这个的属性值变化。

binding2:是为了将这个静态属性名与元素的目标依赖属性进行关联。此绑定会根据第一次绑定的属性值变化而变化。

也就是说需要持久化 binding1,需要将 binding1 关联到一个依赖属性上。

由于 MarkupExtension 本身不是继承自 DependencyObject 的,而是继承自 Object。

所以,只能使用附加属性 LangKey 来关联 binding1。

注册附加属性 LangKey 时只需要设置 PropertyChangedCallback 就可以对  LangKey 进行监听,这样我们就可以直接在回调函数中实现 binding2。

但是,又有一个新的问题。

PropertyChangedCallback 是一个静态回调函数,实现 binding2 需要设置元素的目标依赖属性和 LangProvider。

而这两个属性值都是实例属性,无法在静态函数中使用。

为了解决这个问题,我们再创建两个附加属性。

  • TargetProperty:元素的目标依赖属性
  • LangMarkup:元素上的语言标记扩展

这两个附加属性需要在 ProvideValue 函数中进行初始化操作。

以上就是实现 LangBindingExtension 的解决思路。

解决方案

LangMarkupExtension:语言标记扩展(基类)
/// <summary>
/// 语言标记扩展,主要用于应用程序全球化
/// </summary>
public abstract class LangMarkupExtension : MarkupExtension
{
    private static LangProvider defaultProvider;
    private LangProvider provider;

    /// <summary>
    /// 默认语言代理
    /// </summary>
    public static LangProvider DefaultProvider
    {
        get
        {
            if (null == defaultProvider)
            {
                defaultProvider = new LangProvider()
                {
                    LangType = Application.Current.GetType()
                };
            }
            return defaultProvider;
        }
        set
        {
            defaultProvider = value;
        }
    }

    /// <summary>
    /// 指定的语言代理,此属性的优先级大于 <see cref="DefaultProvider"/>
    /// </summary>
    public LangProvider Provider
    {
        get
        {
            if (null == provider)
            {
                return DefaultProvider;
            }
            return provider;
        }
        set => provider = value;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        // 如果,当前对象在 VS 中的设计器中使用时,默认显示类名。
        if (Designer.IsInDesignMode)
        {
            return GetType().Name;
        }

        return this;
    }

    /// <summary>
    /// 绑定语言代理
    /// </summary>
    /// <param name="key">语言包中的资源 Key</param>
    /// <returns></returns>
    protected Binding CreateBinding(string key)
    {
        var binding = new Binding();
        binding.Source = Provider;
        binding.Path = new PropertyPath($"[{key}]");
        return binding;
    }
}

 

LangBindingExtension
public class LangBindingExtension : LangMarkupExtension
{
    private static readonly Type ownerType = typeof(LangBindingExtension);

    #region LangKey
        
    /// <summary>
    /// 获取或设置语言包中的资源关键字
    /// </summary>
    internal static readonly DependencyProperty LangKeyProperty =
        DependencyProperty.RegisterAttached("LangKey",
            typeof(string), ownerType,
            new PropertyMetadata(default(string),LangKeyPropertyChangedCallback));

    private static void LangKeyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var langMarkup = GetLangMarkup(d);
        langMarkup.SetLangBinding(d);
    }

    /// <summary>
    /// 设置 <see cref="LangKeyProperty"/> 的值
    /// </summary>
    internal static void SetLangKey(DependencyObject element, string value)
    {
        element.SetValue(LangKeyProperty, value);
    }

    /// <summary>
    /// 获取 <see cref="LangKeyProperty"/> 的值
    /// </summary>
    internal static string GetLangKey(DependencyObject element)
    {
        return (string) element.GetValue(LangKeyProperty);
    }

    #endregion

    #region TargetProperty

    /// <summary>
    /// 获取或设置语言绑定的目标属性
    /// </summary>
    internal static readonly DependencyProperty TargetPropertyProperty =
        DependencyProperty.RegisterAttached("TargetProperty",
            typeof(DependencyProperty), ownerType,
            new PropertyMetadata(default(DependencyProperty)));

    /// <summary>
    /// 设置 <see cref="TargetPropertyProperty"/> 的值
    /// </summary>
    internal static void SetTargetProperty(DependencyObject element, DependencyProperty value)
    {
        element.SetValue(TargetPropertyProperty, value);
    }

    /// <summary>
    /// 获取 <see cref="TargetPropertyProperty"/> 的值
    /// </summary>
    internal static DependencyProperty GetTargetProperty(DependencyObject element)
    {
        return (DependencyProperty) element.GetValue(TargetPropertyProperty);
    }

    #endregion

    #region LangMarkup

    /// <summary>
    /// 获取或设置当前元素的语言标记
    /// </summary>
    internal static readonly DependencyProperty LangMarkupProperty =
        DependencyProperty.RegisterAttached("LangMarkup",
            typeof(LangBindingExtension), ownerType,
            new PropertyMetadata(default(LangBindingExtension)));

    /// <summary>
    /// 设置 <see cref="LangMarkupProperty"/> 的值
    /// </summary>
    internal static void SetLangMarkup(DependencyObject element, LangBindingExtension value)
    {
        element.SetValue(LangMarkupProperty, value);
    }

    /// <summary>
    /// 获取 <see cref="LangMarkupProperty"/> 的值
    /// </summary>
    internal static LangBindingExtension GetLangMarkup(DependencyObject element)
    {
        return (LangBindingExtension) element.GetValue(LangMarkupProperty);
    }

    #endregion
        
    /// <summary>
    /// 此路径会与语言代理中的 Key 绑定
    /// </summary>
    public PropertyPath Key { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (null != Key &&
            serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget provide)
        {
            switch (provide.TargetObject)
            {
                case FrameworkElement element:
                    // 此处最好使用弱事件注册,后期优化
                    element.DataContextChanged += OnDataContextChanged;
                    var targetProperty = provide.TargetProperty as DependencyProperty;
                    SetTargetProperty(element, targetProperty);
                    SetLangMarkup(element, this);
                    SetLangKeyBinding(element);
                    return BindingOperations.GetBinding(element, targetProperty);
            }
        }

        return base.ProvideValue(serviceProvider);
    }

    private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender is FrameworkElement element)
        {
            SetLangKeyBinding(element);
        }
    }

    private void SetLangKeyBinding(FrameworkElement element)
    {
        var binding = new Binding()
        {
            Mode = BindingMode.OneWay,
            Path = Key,
            Source = element.DataContext
        };
        BindingOperations.SetBinding(element, LangKeyProperty, binding);
    }

    private void SetLangBinding(DependencyObject element)
    {
        var langKey = GetLangKey(element);
        var dependencyProperty = GetTargetProperty(element);
        var binding = CreateBinding(langKey);
        BindingOperations.SetBinding(element, dependencyProperty, binding);
    }
}

Xaml 演示

<!--
	DemoNode是一个类似于 TreeNode 的对象,具有属性如下:
		IconPath:图标路径
		Name:名称,对应语言包中的静态属性名
-->
<HierarchicalDataTemplate DataType="{x:Type data:DemoNode}" ItemsSource="{Binding Path=Children}">
    <DockPanel>
        <Image Source="{Binding Path=IconPath}" Stretch="None" Margin="5,0"/>
        <TextBlock Text="{LangBinding Key=Name}" />
    </DockPanel>
</HierarchicalDataTemplate>
posted @ 2021-09-09 11:44  2324736194  阅读(326)  评论(0编辑  收藏  举报