WPF ComboBox 使用 ResourceBinding 动态绑定资源键并支持语言切换

WPF ComboBox 使用 ResourceBinding 动态绑定资源键并支持语言切换

独立观察员  2021 年 8 月 23 日

 

我们平常在 WPF 中进行资源绑定操作,一般就是用 StaticResource 或者 DynamicResource 后面跟上资源的 key 这种形式,能满足大部分需求。但是有的时候,我们需要绑定的是代表了资源的 key 的变量,也就是动态绑定资源的 key(注意和 DynamicResource 区分开),比如本文将要演示的支持国际化的场景。这种动态绑定资源 key 的功能,在 WPF 中没有被原生支持,所以还是得在网上找找解决方法。

 

最终在 stackoverflow 网站上看到一篇靠谱的讨论帖(Binding to resource key, WPF),里面几个人分别用 标记扩展、附加属性、转换器 的方式给出了解决方法,本文使用的是 Gor Rustamyan 给出的 标记扩展 的方案,核心就是一个 ResourceBinding 类(代码整理了下,下文给出)。

 

先来看看本次的使用场景吧,简单来说就是一个下拉框控件绑定了键值对列表,显示的是其中的键,但是要求是支持国际化(多语言),如下图:

 

 

由于要支持多语言,所以键值对的键不是直接显示的值,而是显示值的资源键:

/// <summary>
/// 时间列表
/// </summary>
public ObservableCollection<KeyValuePair<string, int>> TimeList { get; set; } = new ObservableCollection<KeyValuePair<string, int>>()
{new KeyValuePair<string, int>("LockTime-OneMinute", 1),new KeyValuePair<string, int>("LockTime-FiveMinute", 5),new KeyValuePair<string, int>("LockTime-TenMinute", 10),new KeyValuePair<string, int>("LockTime-FifteenMinute", 15),new KeyValuePair<string, int>("LockTime-ThirtyMinute", 30),new KeyValuePair<string, int>("LockTime-OneHour", 60),new KeyValuePair<string, int>("LockTime-TwoHour", 120),new KeyValuePair<string, int>("LockTime-ThreeHour", 180),new KeyValuePair<string, int>("LockTime-Never", 0),
};

 

字符串资源放在资源字典中:

 

界面 Xaml 代码为:

xmlns:markupExtensions="clr-namespace:Mersoft.Mvvm.MarkupExtensions"<GroupBox Header="演示 ComboBox 绑定资源键(国际化支持)" Height="100"><StackPanel Orientation="Horizontal"><ComboBox MinWidth="200" MaxWidth="400" Height="35" Margin="10" FontSize="18" VerticalContentAlignment="Center"ItemsSource="{Binding TimeList}" SelectedItem="{Binding SelectedTime}"><ComboBox.ItemTemplate><DataTemplate><TextBlock Text="{markupExtensions:ResourceBinding Key}"></TextBlock></DataTemplate></ComboBox.ItemTemplate></ComboBox><Button Width="100" Command="{Binding SwitchCnCmd}"> 切换中文 </Button><Button Width="100" Command="{Binding SwitchEnCmd}"> 切换英文 </Button><TextBlock Text="{markupExtensions:ResourceBinding SelectedTime.Key}" VerticalAlignment="Center"></TextBlock></StackPanel>
</GroupBox>

 

可以看到,给 ComboBox 的 ItemTemplate 设置了一个 DataTemplate,里面通过 TextBlock 来绑定键值对中的 Key。关键在于,此处不是使用普通的 Binding,而是使用了自定义的标记扩展 ResourceBinding,其代码如下:

using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;namespace Mersoft.Mvvm.MarkupExtensions
{/// <summary>/// 用于处理 绑定代表资源键 (key) 的变量 业务的标记扩展类/// markup extension to allow binding to resourceKey in general case./// https://stackoverflow.com/questions/20564862/binding-to-resource-key-wpf/// </summary>/// <example>/// <code>/// (Image Source="{local:ResourceBinding ImageResourceKey}"/>/// </code>/// </example>public class ResourceBinding : MarkupExtension{#region Helper propertiespublic static object GetResourceBindingKeyHelper(DependencyObject obj){return (object)obj.GetValue(ResourceBindingKeyHelperProperty);}public static void SetResourceBindingKeyHelper(DependencyObject obj, object value){obj.SetValue(ResourceBindingKeyHelperProperty, value);}// Using a DependencyProperty as the backing store for ResourceBindingKeyHelper.  This enables animation, styling, binding, etc...public static readonly DependencyProperty ResourceBindingKeyHelperProperty =DependencyProperty.RegisterAttached("ResourceBindingKeyHelper", typeof(object), typeof(ResourceBinding), new PropertyMetadata(null, ResourceKeyChanged));static void ResourceKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var target = d as FrameworkElement;var newVal = e.NewValue as Tuple<object, DependencyProperty>if (target == null || newVal == null)return;var dp = newVal.Item2;if (newVal.Item1 == null){target.SetValue(dp, dp.GetMetadata(target).DefaultValue);return;}target.SetResourceReference(dp, newVal.Item1);}#endregionpublic ResourceBinding(){}public ResourceBinding(string path){Path = new PropertyPath(path);}public override object ProvideValue(IServiceProvider serviceProvider){var provideValueTargetService = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));if (provideValueTargetService == null)return null;if (provideValueTargetService.TargetObject != null &&provideValueTargetService.TargetObject.GetType().FullName == "System.Windows.SharedDp")return this;var targetObject = provideValueTargetService.TargetObject as FrameworkElement;var targetProperty = provideValueTargetService.TargetProperty as DependencyProperty;if (targetObject == null || targetProperty == null)return null;#region bindingBinding binding = new Binding{Path = Path,XPath = XPath,Mode = Mode,UpdateSourceTrigger = UpdateSourceTrigger,Converter = Converter,ConverterParameter = ConverterParameter,ConverterCulture = ConverterCulture,FallbackValue = FallbackValue};if (RelativeSource != null)binding.RelativeSource = RelativeSource;if (ElementName != null)binding.ElementName = ElementName;if (Source != null)binding.Source = Source;#endregionvar multiBinding = new MultiBinding{Converter = HelperConverter.Current,ConverterParameter = targetProperty};multiBinding.Bindings.Add(binding);multiBinding.NotifyOnSourceUpdated = true;targetObject.SetBinding(ResourceBindingKeyHelperProperty, multiBinding);return null;}#region Binding Members/// <summary>/// The source path (for CLR bindings)./// </summary>public object Source { get; set; }/// <summary>/// The source path (for CLR bindings)./// </summary>public PropertyPath Path { get; set; }/// <summary>/// The XPath path (for XML bindings)./// </summary>[DefaultValue(null)]public string XPath { get; set; }/// <summary>/// Binding mode/// </summary>[DefaultValue(BindingMode.Default)]public BindingMode Mode { get; set; }/// <summary>/// Update type/// </summary>[DefaultValue(UpdateSourceTrigger.Default)]public UpdateSourceTrigger UpdateSourceTrigger { get; set; }/// <summary>/// The Converter to apply/// </summary>[DefaultValue(null)]public IValueConverter Converter { get; set; }/// <summary>/// The parameter to pass to converter./// </summary>/// <value></value>[DefaultValue(null)]public object ConverterParameter { get; set; }/// <summary>/// Culture in which to evaluate the converter/// </summary>[DefaultValue(null)][TypeConverter(typeof(System.Windows.CultureInfoIetfLanguageTagConverter))]public CultureInfo ConverterCulture { get; set; }/// <summary>/// Description of the object to use as the source, relative to the target element./// </summary>[DefaultValue(null)]public RelativeSource RelativeSource { get; set; }/// <summary>/// Name of the element to use as the source/// </summary>[DefaultValue(null)]public string ElementName { get; set; }#endregion#region BindingBase Members/// <summary>/// Value to use when source cannot provide a value/// </summary>/// <remarks>/// Initialized to DependencyProperty.UnsetValue; if FallbackValue is not set, BindingExpression/// will return target property's default when Binding cannot get a real value./// </remarks>public object FallbackValue { get; set; }#endregion#region Nested typesprivate class HelperConverter : IMultiValueConverter{public static readonly HelperConverter Current = new HelperConverter();public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture){return Tuple.Create(values[0], (DependencyProperty)parameter);}public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture){throw new NotImplementedException();}}#endregion}
}

 

主要就是继承 MarkupExtension 并重写 ProvideValue 方法,具体的本人也没怎么研究,就先不说了,大家感兴趣可以自己查一查。这里直接拿来使用,可以达到动态绑定资源 key 的目的。

 

如果使用的是普通的 Binding,则只能显示原始值:

 

最后来看看中英文切换,当然,如果有其它语言,也是一样可以切换的。

首先是移除现有语言资源的方法:

/// <summary>
/// 语言名称列表
/// </summary>
private readonly List<string> _LangKeys = new List<string>() { "en-us", "zh-cn" };/// <summary>
/// 移除语言资源
/// </summary>
/// <param name="removeKeyList"> 需要移除的资源中包含的 key 的列表,默认为空,为空移除所有的 </param>
private void RemoveLangThemes(List<string> removeKeyList = null)
{if (removeKeyList == null){removeKeyList = _LangKeys;}var rd = Application.Current.Resources;List<ResourceDictionary> removeList = new List<ResourceDictionary>();foreach (var dictionary in rd.MergedDictionaries){// 判断是否是对应的语言资源文件;bool isExists = removeKeyList.Exists(x => dictionary.Contains("LangName") && dictionary["LangName"]+"" == x);if (isExists){removeList.Add(dictionary);}}foreach (var removeResource in removeList){rd.MergedDictionaries.Remove(removeResource);}
}

 

主要是对 Application.Current.Resources.MergedDictionaries 进行操作,移除有 LangName 键,且值为对应语言代号的资源字典。

 

然后是应用对应语言资源的方法及调用:

/// <summary>
/// 应用语言
/// </summary>
/// <param name="packUriTemplate"> 资源路径模板,形如:"/WPFPractice;component/Resources/Language/{0}.xaml"</param>
/// <param name="langName"> 语言名称,形如:"zh-cn"</param>
private void ApplyLanguage(string packUriTemplate, string langName = "zh-cn")
{var rd = Application.Current.Resources;//RemoveLangThemes();var packUri = string.Format(packUriTemplate, langName);RemoveLangThemes(new List<string>() { langName });// 将资源加载在最后,优先使用;rd.MergedDictionaries.Add((ResourceDictionary)Application.LoadComponent(new Uri(packUri, UriKind.Relative)));
}/// <summary>
/// 语言资源路径模板字符串
/// </summary>
private string _LangResourceUriTemplate = "/WPFPractice;component/Resources/Language/{0}.xaml";/// <summary>
/// 命令方法赋值(在构造方法中调用)
/// </summary>
private void SetCommandMethod()
{SwitchCnCmd ??= new RelayCommand(o => true, async o =>{ApplyLanguage(_LangResourceUriTemplate, "zh-cn");});SwitchEnCmd ??= new RelayCommand(o => true, async o =>{ApplyLanguage(_LangResourceUriTemplate, "en-us");});
}

 

逻辑就是,先移除要切换到的语言资源的已存在的实例,然后将新的实例放在最后,以达到比其它语言资源(如果有的话)更高优先级的目的。

 

源码地址:https://gitee.com/dlgcy/Practice/tree/Blog20210823

发行版地址:https://gitee.com/dlgcy/Practice/releases/Blog20210823

 

WPF

【翻译】WPF 中附加行为的介绍 Introduction to Attached Behaviors in WPF

WPF 使用 Expression Design 画图导出及使用 Path 画图

WPF MVVM 弹框之等待框

解决 WPF 绑定集合后数据变动界面却不更新的问题(使用 ObservableCollection)

WPF 消息框 TextBox 绑定新数据时让光标和滚动条跳到最下面

真・WPF 按钮拖动和调整大小

WPF MVVM 模式下的弹窗

WPF 让一组 Button 实现 RadioButton 的当前样式效果

WPF 原生绑定和命令功能使用指南

WPF 用户控件的自定义依赖属性在 MVVM 模式下的使用备忘

在WPF的MVVM模式中使用OCX组件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/299124.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

你一直憋着的那个屁,放出来时真的有人知道吗?我先告诉你憋着不放的屁会到哪去……...

全世界只有3.14 % 的人关注了爆炸吧知识制作团队制作人 超模君编剧 恐恐恐插画 杨羊羊友情出演 超模君 想增加更多的奇怪知识&#xff1f;带你入学↓↓↓关注“爆炸吧知识”日增怪知识&#xff01;

Android中View绘制流程

2019独角兽企业重金招聘Python工程师标准>>> 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的&#xff0c;该函数做的执行过程可简单概况为 根据之前设置的状态&#xff0c;判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的…

从零开始学习jQuery (四) 使用jQuery操作元素的属性与样式

本系列文章导航 从零开始学习jQuery (一) 开天辟地入门篇 从零开始学习jQuery (二) 万能的选择器 从零开始学习jQuery (三) 管理jQuery包装集 从零开始学习jQuery (四) 使用jQuery操作元素的属性与样式 从零开始学习jQuery (五) 事件与事件对象 从零开始学习jQuery (六) jQuery…

java克鲁斯卡尔算法_Java语言基于无向有权图实现克鲁斯卡尔算法代码示例

所谓有权图&#xff0c;就是图中的每一条边上都会有相应的一个或一组值。通常情况下&#xff0c;这个值只是一个数字如&#xff1a;在交通运输网中&#xff0c;边上的权值可能表示的是路程&#xff0c;也可能表示的是运输费用(显然二者都是数字)。不过&#xff0c;边上的权值也…

[原]一步一步自己制作弹出框

说到javascript弹出框的制作,将其实现步骤分开&#xff0c;其实很容易。下面&#xff0c;将拆分页面弹出框的制作步骤。首先&#xff0c;准备编辑工具(废话)&#xff0c;其实&#xff0c;您可以使用notepad..嘿嘿。弹出框的组成结构&#xff1a; 1.一个页面遮罩层。 2.一个di…

那些你从未见过的神奇物理化学实验,今天全给你整理了!

全世界只有3.14 % 的人关注了爆炸吧知识1.电荷吸引水流2.比空气重的六氟化硫六氟化硫&#xff08;SF6&#xff09;&#xff0c;法国两位化学家Moissan和Lebeau于1900年合成的人造惰性气体&#xff0c;具有良好的电气绝缘性能及优异的灭弧性能&#xff0c;是一种优于空气和油之间…

ASP.NET Core使用Middleware有条件地允许访问路由

问题有时&#xff0c;我们可能在Web API中包含一些具有调试功能的请求。比如我们上次的文章中“晕了&#xff01;这个配置值从哪来的&#xff1f;”使用的获取配置值的功能&#xff1a;endpoints.MapGet("/test2/{key:alpha}", async context > {var key context…

如何用最低的成本教育出最棒的孩子?看完这些公众号你就知道了

全世界只有3.14 % 的人关注了爆炸吧知识真正决定人与人之间的差距的&#xff0c;其实是我们对事物的见识与内心的格局&#xff0c;见识的深浅决定人生的深浅&#xff0c;格局的大小决定了人生之路是宽是窄。今天给大家推荐几个有深度、有想法的公众号&#xff0c;希望能够给你带…

Virtualbox 无缝整合linux和windows双系统

一直以来&#xff0c;为了使用公司规定的内部通信工具RTX, 而不得不在ubuntu上安装一个virtualbox以运行RTX, 但是由于是运行在虚拟机里面&#xff0c;所以经常不能及时的回复同时的消息&#xff0c;而造成沟通的延迟。 所以就想着&#xff0c;能不能及时的在ubuntu上面看到…

聊一聊对外API接口的存活检查可以怎么做

背景 公司内部的API接口一般会分为两大类&#xff0c;一类是直接暴露在公网可以访问的&#xff0c;一类是只能在局域网内访问的。暴露在公网的一般就是业务网关以及一些和第三方公司有着某些合作&#xff0c;从而进行数据交互的接口。检查API接口是否存活&#xff0c;第一反应应…

各种机械原理动态图,看完脑洞大开,绝对涨姿势!

全世界只有3.14 % 的人关注了爆炸吧知识1.钥匙开锁2.撬锁3.汽车换挡4.手枪上膛发射5.炮弹发射原理6.手雷爆炸7.洗衣机8.缝纫机9.心脏10. 近视眼手术&#xff1a;原理其实很简单&#xff0c;就是把角膜削成一个“隐形眼镜”。11.零钱分类12.吹号转载来源&#xff1a;物理好教师部…

CSharp设计模式读书笔记(10):装饰模式(学习难度:★★★☆☆,使用频率:★★★☆☆)...

装饰模式(Decorator Pattern)&#xff1a; 动态地给一个对象增加一些额外的职责&#xff0c;就增加对象功能来说&#xff0c;装饰模式比生成子类实现更为灵活。 模式角色与结构&#xff1a; 示例代码&#xff1a; using System; using System.Collections.Generic; using Syste…

ShardingCore 如何呈现“完美”分表

这篇文章是我针对efcore的分表的简单介绍,如果您有以下需求那么可以自己选择是否使用本框架,本框架将一直持续更新下去&#xff0c;并且免费开源为.net生态做贡献,如果您觉得不错那么请帮忙点个star谢谢&#xff0c;框架地址[sharding-core](https://github.com/xuejmnet/shard…

使用javascript oop开发滑动(slide) 菜单控件

这里使用原生的javascript&#xff0c;用面向对象的方式创建一个容易维护使用方便的滑动菜单&#xff0c;调用方式如下&#xff1a;var$sliding document.getElementById("silding");vars1 newSliding();s1.commands $sliding.getElementsByTagName("dt");…

他毕业于北师大,编写了我国首套数学教材,陈景润华罗庚都崇拜他

全世界只有3.14 % 的人关注了爆炸吧知识要说起我国著名的数学家&#xff0c;很多人会想到陈景润、华罗庚等人&#xff0c;这些大数学家为我国数学研究做出了卓越贡献&#xff0c;也深深影响了很多人。那么&#xff0c;像陈景润、华罗庚这些大数学家&#xff0c;他们学习数学有受…

用多媒体库 Bass.dll 播放 mp3 [8] - 实时显示左右声道的峰值

为什么80%的码农都做不了架构师&#xff1f;>>> 本例效果图: 代码文件: unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls, ExtCtrls, ComCtrls;typeTForm1 class(TForm)OpenDialog1: TOpe…

最近看了两本低代码的书

月初写了一篇《你真的了解低代码平台吗&#xff1f;》&#xff0c;介绍了下我对低代码产品的一些认识&#xff0c;随后有朋友送了我两本华章出版的关于低代码的书&#xff1a;明道云的《零代码实战》和微软的《实战低代码》&#xff0c;目前市面上也就这两本关于低代码的书。因…

如何逼疯一名数学系学生?

全世界只有3.14 % 的人关注了爆炸吧知识今天在知乎竟看到这样一个问题但看在关注人数如此之多超模君就放下成见稍微透露一下我们的“底线”到底要怎样做才能激怒数学系学生数学沾边法顾名思义只要和数学沾边就可以开怼在菜市场算菜钱时在提问后的0.5秒迅速补上致命一刀经过彩票…

WinAPI: 钩子回调函数之 MouseProc

为什么80%的码农都做不了架构师&#xff1f;>>> MouseProc(nCode: Integer; {}wParam: WPARAM; {}lParam: LPARAM {} ): LRESULT; {}//待续...转载于:https://my.oschina.net/hermer/blog/320962

内推!字节、阿里、网易火热招聘中,内推优筛简历,快人一步拿offer,真香!(送内推码)...

全世界只有3.14 % 的人关注了爆炸吧知识“金三银四”可谓招聘的黄金期。一方面&#xff0c;校园春招正如火如荼的进行中&#xff0c;另一方面&#xff0c;各大企业也纷纷启动了暑假实习招聘。对于2020届的同学来说&#xff0c;务必要利用好“应届生”这个身份&#xff0c;把握住…