原文 How do you create a DynamicResourceBinding that supports Converters, StringFormat?

2
down vote
accepted
In the past I've resorted to using several hard-to-follow/hard-to-implement hacks and workarounds to achieve what I feel is missing functionality. That's why to me this is close to (but not quite) the Holy Grail of solutions... being able to use this in XAML exactly like you would a binding, but having the source be a DynamicResource.Note: I say 'Binding' but technically it's a DynamicResourceExtension on which I've defined a Converter, ConverterParameter, ConverterCulture and StringFormat but which does use a Binding internally. As such, I named it based on its usage model, not its actual type.The key to making this work is a unique feature of the Freezable class:If you add a Freezable to the resources of a FrameworkElement, any DependencyProperties on that Freezable which are set to a DynamicResource will resolve those resources relative to that FrameworkElement's position in the Visual Tree.Using that bit of 'magic sauce', the trick is to set a DynamicResource on a Freezable's DependencyProperty, add the Freezable to the resource collection of the target FrameworkElement, then use that Freezable's DependencyProperty as the source for an actual binding.That said, here's the solution.DynamicResourceBinding
public class DynamicResourceBinding : DynamicResourceExtension
{#region Internal Classesprivate class DynamicResourceBindingSource : Freezable{public static readonly DependencyProperty ResourceReferenceExpressionProperty = DependencyProperty.Register(nameof(ResourceReferenceExpression),typeof(object),typeof(DynamicResourceBindingSource),new FrameworkPropertyMetadata());public object ResourceReferenceExpression{get { return GetValue(ResourceReferenceExpressionProperty); }set { SetValue(ResourceReferenceExpressionProperty, value); }}protected override Freezable CreateInstanceCore(){return new DynamicResourceBindingSource();}}#endregion Internal Classespublic DynamicResourceBinding(){}public DynamicResourceBinding(string resourceKey): base(resourceKey){}public IValueConverter Converter          { get; set; }public object          ConverterParameter { get; set; }public CultureInfo     ConverterCulture   { get; set; }public string          StringFormat       { get; set; }public override object ProvideValue(IServiceProvider serviceProvider){// Get the expression representing the DynamicResourcevar resourceReferenceExpression = base.ProvideValue(serviceProvider);// If there's no converter, nor StringFormat, just return it (Matches standard DynamicResource behavior}if(Converter == null && StringFormat == null)return resourceReferenceExpression;// Create the Freezable-based object and set its ResourceReferenceExpression property directly to the // result of base.ProvideValue (held in resourceReferenceExpression). Then add it to the target FrameworkElement's// Resources collection (using itself as its key for uniqueness) so it participates in the resource lookup chain.var dynamicResourceBindingSource = new DynamicResourceBindingSource(){ ResourceReferenceExpression = resourceReferenceExpression };// Get the target FrameworkElement so we have access to its Resources collection// Note: targetFrameworkElement may be null in the case of setters. Still trying to figure out how to handle them.// For now, they just fall back to looking up at the app levelvar targetInfo = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));var targetFrameworkElement = targetInfo.TargetObject as FrameworkElement;targetFrameworkElement?.Resources.Add(dynamicResourceBindingSource, dynamicResourceBindingSource);// Now since we have a source object which has a DependencyProperty that's set to the value of the// DynamicResource we're interested in, we simply use that as the source for a new binding,// passing in all of the other binding-related properties.var binding = new Binding(){Path               = new PropertyPath(DynamicResourceBindingSource.ResourceReferenceExpressionProperty),Source             = dynamicResourceBindingSource,Converter          = Converter,ConverterParameter = ConverterParameter,ConverterCulture   = ConverterCulture,StringFormat       = StringFormat,Mode               = BindingMode.OneWay};// Now we simply return the result of the new binding's ProvideValue// method (or the binding itself if the target is not a FrameworkElement)return (targetFrameworkElement != null)? binding.ProvideValue(serviceProvider): binding;}
}
And just like with a regular binding, here's how you use it (assuming you've defined a 'double' resource with the key 'MyResourceKey')...<TextBlock Text="{drb:DynamicResourceBinding ResourceKey=MyResourceKey, Converter={cv:MultiplyConverter Factor=4}, StringFormat='Four times the resource is {0}'}" />
or even shorter, you can omit 'ResourceKey=' thanks to constructor overloading to match how 'Path' works on a regular binding...<TextBlock Text="{drb:DynamicResourceBinding MyResourceKey, Converter={cv:MultiplyConverter Factor=4}, StringFormat='Four times the resource is {0}'}" />
Awesomesausage! (Well, ok, AwesomeViennasausage thanks to the small 'setter' caveat I uncovered after writing this. I updated the code with the comments.)As I mentioned, the trick to get this to work is using a Freezable. Thanks to its aforementioned 'magic powers' of participating in the Resource Lookup relative to the target, we can use it as the source of the internal binding where we have full use of all of a binding's facilities.Note: You must use a Freezable for this to work. Inserting any other type of DependencyObject into the target FrameworkElement's resources--ironically even including another FrameworkElement--will resolve DynamicResources relative to the Application and not the FrameworkElement where you used the DynamicResourceBinding since they don't participate in localized resource lookup (unless they too are in the Visual Tree of course.) As a result, you lose any resources which may be set within the Visual Tree.The other part of getting that to work is being able to set a DynamicResource on a Freezable from code-behind. Unlike FrameworkElement (which we can't use for the above-mentioned reasons) you can't call SetResourceReference on a Freezable. Actually, I have yet to figure out how to set a DynamicResource on anything but a FrameworkElement.Fortunately, here we don't have to since the value provided from base.ProvideValue() is the result of such a call anyway, which is why we can simply set it directly to the Freezable's DependencyProperty, then just bind to it.So there you have it! Binding to a DynamicResource with full Converter and StringFormat support.For completeness, here's something similar but for StaticResources...StaticResourceBinding
public class StaticResourceBinding : StaticResourceExtension
{public StaticResourceBinding(){}public StaticResourceBinding(string resourceKey): base(resourceKey){}public IValueConverter Converter          { get; set; }public object          ConverterParameter { get; set; }public CultureInfo     ConverterCulture   { get; set; }public string          StringFormat       { get; set; }public override object ProvideValue(IServiceProvider serviceProvider){var staticResourceValue = base.ProvideValue(serviceProvider);if(Converter == null)return (StringFormat != null)? string.Format(StringFormat, staticResourceValue): staticResourceValue;var targetInfo               = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));var targetFrameworkElement   = (FrameworkElement)targetInfo.TargetObject;var targetDependencyProperty = (DependencyProperty)targetInfo.TargetProperty;var convertedValue = Converter.Convert(staticResourceValue, targetDependencyProperty.PropertyType, ConverterParameter, ConverterCulture);return (StringFormat != null)? string.Format(StringFormat, convertedValue): convertedValue;}
}
Anyway, that's it! I really hope this helps other devs as it has really simplified our control templates, especially around common border thicknesses and such.Enjoy!

How do you create a DynamicResourceBinding that supports Converters, StringFormat?相关推荐

  1. 编码中统一更该变量的快捷键_更多项目想法,以提高您的编码技能

    编码中统一更该变量的快捷键 Two weeks ago I published an article containing 15 project ideas that you can build to ...

  2. SAP CDS view权限控制实现原理介绍

    Part1 – how to test odata service generated by CDS view Part2 – what objects are automatically gener ...

  3. SAP CDS view源代码行数统计工具

    Part1 – how to test odata service generated by CDS view Part2 – what objects are automatically gener ...

  4. 如何使用SAP HANA Studio的PlanViz分析CDS view性能问题

    Part1 – how to test odata service generated by CDS view Part2 – what objects are automatically gener ...

  5. SAP CDS view 单元测试框架 Test Double 介绍

    系列目录 Part1 – how to test odata service generated by CDS view Part2 – what objects are automatically ...

  6. SAP CDS view自学教程之十:SAP CDS view扩展性(Extensibility)实现原理

    目录 Part1 – how to test odata service generated by CDS view Part2 – what objects are automatically ge ...

  7. SAP CDS view自学教程之八:SAP Fiori Elements里不同类型的annotation

    Part1 – how to test odata service generated by CDS view Part2 – what objects are automatically gener ...

  8. 技术债务管理_管理技术债务

    技术债务管理 DevOps Essentials DevOps基础 介绍 (Introduction) Technical debt is one of the most insidious and ...

  9. CVE和CWE的区别

    CVE & CWE CVE- Common Vulnerabilities and Exposures 通用漏洞披露 https://cve.mitre.org/ CVE 的英文全称是&quo ...

最新文章

  1. PHP5 VC9、VC6、Thread Safe、Non Thread Safe各个版本区别
  2. Linux内核编译学习1
  3. Maven教程初级篇01: 简介
  4. 十大最主流的PHP框架
  5. 【Leetocde | 24 】152. 乘积最大子序列
  6. 基于Spring+SpringMVC+Mybatis架构的开源博客
  7. 将Fortran程序转化成大写[cwp]
  8. 使用npm安装yarn
  9. 微信小程序开发-软件外包平台案例
  10. Effective+Java+中文版
  11. java输入数字星期,输出英文
  12. python 实现盒滤波boxfilter
  13. 在vue中使用marked解析markdown文件
  14. 物联网智慧物流平台开发
  15. Vue.js :使用LODOP打印表格文件
  16. 元旦| 微软ATP伴你2023一路前行
  17. 最新版本 Stable Diffusion 开源 AI 绘画工具之汉化篇
  18. python高并发编程_python 并发编程
  19. 分布式计算模式:Stream
  20. Ubuntu如何安装搜狗输入法

热门文章

  1. MySQL的DDL、DML、DCL、TCL什么意思?
  2. python最新技术开锁工具_Python 自动化库介绍 PySimpleGUI
  3. 自动化测试:Selenium8种元素定位+unittest框架设计
  4. 贴吧粉丝怎么全部移除_亚马逊FBA怎么发货?怎么把货发到FBA仓库?
  5. python3.7 keras和tensorflow兼容_解决Keras 与 Tensorflow 版本之间的兼容性问题
  6. mfc mysql delete_MFC中简单的数据库文件操作(添加,修改,查找,删除)
  7. 数学建模亚太赛优秀论文_泰迪杯数据分析职业技术大赛总结暨亚太地区大学生数学建模经验分享会...
  8. iphone已停用怎么解锁_iPhone多次输错密码已停用,连接iTunes,怎么办?
  9. 框架对比_2020 年前端框架性能对比和评测
  10. php curl post text,php – POST适用于Postman,但不适用于CURL