How do you create a DynamicResourceBinding that supports Converters, StringFormat?
原文 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?的更多相关文章
- .NET 框架(转自wiki)
.NET Framework (pronounced dot net) is a software framework developed by Microsoft that runs primari ...
- Revit 2015 API 的全部变化和新功能
这里从SDK的文章中摘录出全部的API变化.主要是希望用户用搜索引擎时能找到相关信息: Major changes and renovations to the Revit API APIchange ...
- Jerry的CDS view自学系列
My CDS view self study tutorial - part 1 how to test odata service generated by CDS view https://blo ...
- kubernetes 实战3_命令_Configure Pods and Containers
Configure a Pod to Use a PersistentVolume for Storage how to configure a Pod to use a PersistentVolu ...
- e666. 创建缓冲图像
A buffered image is a type of image whose pixels can be modified. For example, you can draw on a buf ...
- [英中双语] Pragmatic Software Development Tips 务实的软件开发提示
Pragmatic Software Development Tips务实的软件开发提示 Care About Your Craft Why spend your life developing so ...
- Service官方教程(11)Bound Service示例之2-AIDL 定义跨进程接口并通信
Android Interface Definition Language (AIDL) 1.In this document Defining an AIDL Interface Create th ...
- React搭建项目(全家桶)
安装React脚手架: npm install -g create-react-app 创建项目: create-react-app app app:为该项目名称 或者跳过以上两步直接使用: npx ...
- [译]Vulkan教程(03)开发环境
[译]Vulkan教程(03)开发环境 这是我翻译(https://vulkan-tutorial.com)上的Vulkan教程的第3篇. In this chapter we'll set up y ...
随机推荐
- 图形界面Aardio
用aardio给python写个图形界面 前阵子在用python写一些小程序,写完后就开始思考怎么给python程序配一个图形界面,毕竟控制台实在太丑陋了. 于是百度了下python的图形界面库,眼花 ...
- 【u015】兽径管理
[问题描述] 约翰农场的牛群希望能够在 N 个(1<=N<=200)草地之间任意移动.草地的编号由 1到N.草地之间有树林隔开.牛群希望能够选择草地间的路径,使牛群能够从任一 片草地移动到 ...
- NOIP模拟 回文序列 - DP
题意: 如果一个字符串等于s和t的长度之和(\(l \le 50\)),并且可以拆成两个字符串子序列,分别与s和t相同,那么它就是s和t的一个并字符串(从字符串中选出若干个可以不连续的字符按照原序列写 ...
- js实现去文本换行符小工具
js实现去文本换行符小工具 一.总结 一句话总结: 1.vertical属性使用的时候注意看清定义,也注意父元素的基准线问题.vertical-align:top; 2.获取textareaEleme ...
- iOS开发- Xcode插件- 规范凝视生成器VVDocumenter 自己的见解
xcode升级 VVDocumenter 插件失效怎么办?? 首先给个完整的安装參考:http://www.th7.cn/Program/IOS/201405/212030.shtml 參考这个能 ...
- ZooKeeper 分布式锁实现原理
原理 进程需要访问共享数据时, 就在"/locks"节点下创建一个sequence类型的子节点, 称为thisPath. 当thisPath在所有子节点中最小时, 说明该进程获得了 ...
- Android菜鸟的成长笔记(18)——绑定本地Service并与之通信
在上一篇中介绍了Service与Activity的区别及Service两种启动方式中的第一种启动方式startService(). 我们会发现用startService().stopService() ...
- Win7的diskpart硬盘分区
Windows7 给硬盘分区有两个特点: 1.默认所有是主分区. 2.会有一个 100MB 大小的隐藏分区,为"系统预留". 假设喜欢折腾计算机,这两个特点会造成非常多麻烦.能不能 ...
- Android 4.0新增Space及GridLayout初谈
Android 4.0的SDK已经发布,在众多的新增特性中,其中对开发者来说比较重要的特性之一,是新增的两种界面布局方式:Space和Gridlayout,它们跟以往Android版本的sdk有什么不 ...
- 详尽分析世纪之战:360VS腾讯是两个阶层的抗争
很不错的一篇文字 分析的也很透彻 [转自中国移动http://labs.chinamobile.com/] 来源:搜狐IT 作者:吃熊掌的鱼 2010-11-01 10:11:51 [ 13967阅 ...