原文 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 Classes private 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 Classes public 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 DynamicResource
var 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 level
var 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. .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)

    一.题目描述:通过键盘输入一串小写字母(a~z)组成的字符串.请编写一个字符串过滤程序,若字符串中出现多个相同的字符,将非首次出现的字符过滤掉.比如字符串“abacacde”过滤结果为“abcde”. ...

  2. MethodInterceptor拦截器

    http://blog.csdn.net/heirenheiren/article/details/39030767

  3. Android NDK对象的引用-全局引用,局部引用,弱引用

    百度了一下,google了一下,关于NDK引用的介绍无10篇中就有9.9篇是相同的,对于这种问题,我只能呜呼哀哉了!! 局部引用(函数内部对象类型变量):在C或C++中,局部变量表示只运行范围局限在该 ...

  4. 数据存储常用5种方式plist、Preference、NSCoding、SQLite3、Core Data

    数据存储 iOS应用数据存储的常用方式 XML属性列表(plist)归档 Preference(偏好设置) NSKeyedArchiver归档(NSCoding) SQLite3 Core Data ...

  5. web.xml 中的listener、 filter、servlet 加载顺序及其详解(转)

    在项目中总会遇到一些关于加载的优先级问题,近期也同样遇到过类似的,所以自己查找资料总结了下,下面有些是转载其他人的,毕竟人家写的不错,自己也就不重复造轮子了,只是略加点了自己的修饰. 首先可以肯定的是 ...

  6. SharePoint Permission Analyzer 权限分析仪

    SharePoint Permission Analyzer 权限分析仪         这是一个很好的应用,为了分析SharePoint权限.它会扫描整个网站集,建一个站点的权限架构.       ...

  7. erlang app 文件

    http://hje.iteye.com/blog/1211734 应用的概念¶ 当我们写了实现特定功能的代码之后,我们可能想将代码转成一个 应用 (application),这是可以作为一个单元启动 ...

  8. C# Tuple VS ValueTuple

    C# Tuple VS ValueTuple(元组类 VS 值元组) C# 7.0已经出来一段时间了,大家都知道新特性里面有个对元组的优化:ValueTuple.这里利用详尽的例子详解Tuple VS ...

  9. NOIP模拟 table - 矩阵链表

    题目大意: 给一个n*m的矩阵,每次交换两个大小相同的不重叠的子矩阵,输出最后的矩阵 题目分析: 这题向我们展示了出神入化的链表是如何炼成的.思想都懂,实现是真的需要技术,%%% 用一副链表来表示该矩 ...

  10. [UWP]使用Acrylic(亚克力)

    原文:[UWP]使用Acrylic(亚克力) 1. 前言 在 如何使用Fluent Design System 这篇文章里已经简单介绍过Reveal的用法,这篇再详细介绍其它内容. 自Windows ...