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

  1. 2
  2. down vote
  3. accepted
  4. 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.
  5.  
  6. 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.
  7.  
  8. The key to making this work is a unique feature of the Freezable class:
  9.  
  10. 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.
  11.  
  12. 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.
  13.  
  14. That said, here's the solution.
  15.  
  16. DynamicResourceBinding
  17. public class DynamicResourceBinding : DynamicResourceExtension
  18. {
  19. #region Internal Classes
  20.  
  21. private class DynamicResourceBindingSource : Freezable
  22. {
  23. public static readonly DependencyProperty ResourceReferenceExpressionProperty = DependencyProperty.Register(
  24. nameof(ResourceReferenceExpression),
  25. typeof(object),
  26. typeof(DynamicResourceBindingSource),
  27. new FrameworkPropertyMetadata());
  28.  
  29. public object ResourceReferenceExpression
  30. {
  31. get { return GetValue(ResourceReferenceExpressionProperty); }
  32. set { SetValue(ResourceReferenceExpressionProperty, value); }
  33. }
  34.  
  35. protected override Freezable CreateInstanceCore()
  36. {
  37. return new DynamicResourceBindingSource();
  38. }
  39. }
  40.  
  41. #endregion Internal Classes
  42.  
  43. public DynamicResourceBinding(){}
  44.  
  45. public DynamicResourceBinding(string resourceKey)
  46. : base(resourceKey){}
  47.  
  48. public IValueConverter Converter { get; set; }
  49. public object ConverterParameter { get; set; }
  50. public CultureInfo ConverterCulture { get; set; }
  51. public string StringFormat { get; set; }
  52.  
  53. public override object ProvideValue(IServiceProvider serviceProvider)
  54. {
  55. // Get the expression representing the DynamicResource
  56. var resourceReferenceExpression = base.ProvideValue(serviceProvider);
  57.  
  58. // If there's no converter, nor StringFormat, just return it (Matches standard DynamicResource behavior}
  59. if(Converter == null && StringFormat == null)
  60. return resourceReferenceExpression;
  61.  
  62. // Create the Freezable-based object and set its ResourceReferenceExpression property directly to the
  63. // result of base.ProvideValue (held in resourceReferenceExpression). Then add it to the target FrameworkElement's
  64. // Resources collection (using itself as its key for uniqueness) so it participates in the resource lookup chain.
  65. var dynamicResourceBindingSource = new DynamicResourceBindingSource(){ ResourceReferenceExpression = resourceReferenceExpression };
  66.  
  67. // Get the target FrameworkElement so we have access to its Resources collection
  68. // Note: targetFrameworkElement may be null in the case of setters. Still trying to figure out how to handle them.
  69. // For now, they just fall back to looking up at the app level
  70. var targetInfo = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
  71. var targetFrameworkElement = targetInfo.TargetObject as FrameworkElement;
  72. targetFrameworkElement?.Resources.Add(dynamicResourceBindingSource, dynamicResourceBindingSource);
  73.  
  74. // Now since we have a source object which has a DependencyProperty that's set to the value of the
  75. // DynamicResource we're interested in, we simply use that as the source for a new binding,
  76. // passing in all of the other binding-related properties.
  77. var binding = new Binding()
  78. {
  79. Path = new PropertyPath(DynamicResourceBindingSource.ResourceReferenceExpressionProperty),
  80. Source = dynamicResourceBindingSource,
  81. Converter = Converter,
  82. ConverterParameter = ConverterParameter,
  83. ConverterCulture = ConverterCulture,
  84. StringFormat = StringFormat,
  85. Mode = BindingMode.OneWay
  86. };
  87.  
  88. // Now we simply return the result of the new binding's ProvideValue
  89. // method (or the binding itself if the target is not a FrameworkElement)
  90. return (targetFrameworkElement != null)
  91. ? binding.ProvideValue(serviceProvider)
  92. : binding;
  93. }
  94. }
  95. And just like with a regular binding, here's how you use it (assuming you've defined a 'double' resource with the key 'MyResourceKey')...
  96.  
  97. <TextBlock Text="{drb:DynamicResourceBinding ResourceKey=MyResourceKey, Converter={cv:MultiplyConverter Factor=4}, StringFormat='Four times the resource is {0}'}" />
  98. or even shorter, you can omit 'ResourceKey=' thanks to constructor overloading to match how 'Path' works on a regular binding...
  99.  
  100. <TextBlock Text="{drb:DynamicResourceBinding MyResourceKey, Converter={cv:MultiplyConverter Factor=4}, StringFormat='Four times the resource is {0}'}" />
  101. Awesomesausage! (Well, ok, AwesomeViennasausage thanks to the small 'setter' caveat I uncovered after writing this. I updated the code with the comments.)
  102.  
  103. 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.
  104.  
  105. 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.
  106.  
  107. 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.
  108.  
  109. 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.
  110.  
  111. So there you have it! Binding to a DynamicResource with full Converter and StringFormat support.
  112.  
  113. For completeness, here's something similar but for StaticResources...
  114.  
  115. StaticResourceBinding
  116. public class StaticResourceBinding : StaticResourceExtension
  117. {
  118. public StaticResourceBinding(){}
  119.  
  120. public StaticResourceBinding(string resourceKey)
  121. : base(resourceKey){}
  122.  
  123. public IValueConverter Converter { get; set; }
  124. public object ConverterParameter { get; set; }
  125. public CultureInfo ConverterCulture { get; set; }
  126. public string StringFormat { get; set; }
  127.  
  128. public override object ProvideValue(IServiceProvider serviceProvider)
  129. {
  130. var staticResourceValue = base.ProvideValue(serviceProvider);
  131.  
  132. if(Converter == null)
  133. return (StringFormat != null)
  134. ? string.Format(StringFormat, staticResourceValue)
  135. : staticResourceValue;
  136.  
  137. var targetInfo = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
  138. var targetFrameworkElement = (FrameworkElement)targetInfo.TargetObject;
  139. var targetDependencyProperty = (DependencyProperty)targetInfo.TargetProperty;
  140.  
  141. var convertedValue = Converter.Convert(staticResourceValue, targetDependencyProperty.PropertyType, ConverterParameter, ConverterCulture);
  142.  
  143. return (StringFormat != null)
  144. ? string.Format(StringFormat, convertedValue)
  145. : convertedValue;
  146. }
  147. }
  148. 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.
  149.  
  150. Enjoy!

How do you create a DynamicResourceBinding that supports Converters, StringFormat?的更多相关文章

  1. .NET 框架(转自wiki)

    .NET Framework (pronounced dot net) is a software framework developed by Microsoft that runs primari ...

  2. Revit 2015 API 的全部变化和新功能

    这里从SDK的文章中摘录出全部的API变化.主要是希望用户用搜索引擎时能找到相关信息: Major changes and renovations to the Revit API APIchange ...

  3. Jerry的CDS view自学系列

    My CDS view self study tutorial - part 1 how to test odata service generated by CDS view https://blo ...

  4. kubernetes 实战3_命令_Configure Pods and Containers

    Configure a Pod to Use a PersistentVolume for Storage how to configure a Pod to use a PersistentVolu ...

  5. e666. 创建缓冲图像

    A buffered image is a type of image whose pixels can be modified. For example, you can draw on a buf ...

  6. [英中双语] Pragmatic Software Development Tips 务实的软件开发提示

    Pragmatic Software Development Tips务实的软件开发提示 Care About Your Craft Why spend your life developing so ...

  7. Service官方教程(11)Bound Service示例之2-AIDL 定义跨进程接口并通信

    Android Interface Definition Language (AIDL) 1.In this document Defining an AIDL Interface Create th ...

  8. React搭建项目(全家桶)

    安装React脚手架: npm install -g create-react-app 创建项目: create-react-app app app:为该项目名称 或者跳过以上两步直接使用: npx ...

  9. [译]Vulkan教程(03)开发环境

    [译]Vulkan教程(03)开发环境 这是我翻译(https://vulkan-tutorial.com)上的Vulkan教程的第3篇. In this chapter we'll set up y ...

随机推荐

  1. Java 8新特性探究(十一)Base64详解

    开发十年,就只剩下这套架构体系了! >>>   BASE64 编码是一种常用的字符编码,在很多地方都会用到.但base64不是安全领域下的加密解密算法.能起到安全作用的效果很差,而且 ...

  2. JS前端图形化插件之利器Gojs组件(php中文网)

    JS前端图形化插件之利器Gojs组件(php中文网) 一.总结 一句话总结:php中文网我可以好好走一波 二.JS前端图形化插件之利器Gojs组件 参考: JS前端图形化插件之利器Gojs组件-js教 ...

  3. Oracle数据库零散知识05 -- 表创建,修改

    1.表的创建 Create table student02(sno number); 2.表的删除 Drop table student02; 3.表的重命名 Rename student02 to ...

  4. Html5实现手机九宫格password解锁功能

    HTML5真的是非常强大,前端时间看到一个canvas实现九宫格的password解锁. 今天抽空模仿了一个,特定分享一下. 效果截图例如以下: 效果看起来还不错吧! 源代码例如以下: <!DO ...

  5. 学习鸟哥的Linux私房菜笔记(10)——bash2

    七.命令行表达式 命令行输出--" " 将一串字符当成一个字符串来对待,如果字符串中包含特殊含义的字符,则转义. 双引号不能将 \ $ ` ! 符号的特殊功能禁止 命令行输出--' ...

  6. 【noip模拟】Fancy Signal Translate (暴力 + 哈希)

    题目描述 FST是一名可怜的 OIer,他很强,但是经常 fst,所以 rating 一直低迷. 但是重点在于,他真的很强!他发明了一种奇特的加密方式,这种加密方式只有OIer才能破解. 这种加密方式 ...

  7. 【t095】拯救小tim

    Time Limit: 1 second Memory Limit: 128 MB [问题描述] 小tim在游乐场,有一天终于逃了出来!但是不小心又被游乐场的工作人员发现了... 所以你的任务是安全地 ...

  8. freemarker中间split字符串切割

    freemarker中间split字符串切割 1.简易说明 split切割:用来依据另外一个字符串的出现将原字符串切割成字符串序列 2.举例说明 <#--freemarker中的split字符串 ...

  9. C#7模范和实践

    C# 7 中的模范和实践   原文地址:https://www.infoq.com/articles/Patterns-Practices-CSharp-7 关键点 遵循 .NET Framework ...

  10. 【50.88%】【Codeforces round 382B】Urbanization

    time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...