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 ...
随机推荐
- [Jenkins] Creating Application builds
After installing the jenkins, we start creating new job. 1. Give job names (your project name): 2. G ...
- Android推送进阶课程学习笔记
今天在慕课网学习了Android进阶课程推送的server端处理回执的消息 . 这集课程主要介绍了,当server往client推送消息的时候,client须要发送一个回执回来确认收到了推送消息才算一 ...
- JavaScript调用ATL COM(二)
作者:朱金灿 来源:http://blog.csdn.net/clever101 在上篇文章中介绍了如何在JS中调用ATL COM: JS调用ATL COM中的C++接口的做法 现在我们可以把它嵌入到 ...
- [转]Redis 与Mysql通信
http://blog.csdn.net/hpb21/article/details/7852934 找了点资料看了下.学习心得如下: 1 Mysql更新Redis Mysql更新Redis借鉴mem ...
- 【a601】雇佣计划
Time Limit: 1 second Memory Limit: 32 MB [问题描述] 一位管理项目的经理想要确定每个月需要的工人,他知道每月所需的最少工人数.当他雇佣或解雇一个工人时,会有一 ...
- CocoaPods停在Analyzing dependencies的解决方案
解决办法: 1: 换镜像索引库 国内有人建立了cocoapods的索引库镜像,可以通过如下命令更改镜像: pod repo remove master pod repo add master htt ...
- svn X在Xcode中使用
1 在终端输入命令:清除以前的svn链接地址( /Users/mac/Desktop/SHiosProject/SVNmangerfiles) nie-xiao-bo-mac-pro:~ mac$ s ...
- 【32.89%】【codeforces 574D】Bear and Blocks
time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...
- qemu使用copy-on-write(COW)磁盘
写时复制(copy-on-write,缩写COW)技术不会对原始的镜像文件做更改,变化的部分写在另外的镜像文件中,这种特性在qemu中只有QCOW格式支持,多个 COW 文件可以指向同一映像同时测试多 ...
- Oracle BI Publisher 企业版安装后的配置(BI Publisher Enterprise Edition)
Oracle BI Publisher 企业版安装后的配置(BI Publisher Enterprise Edition) (版权声明,本人原创或者翻译的文章如需转载,如转载用于个人学习,请注明出处 ...