原文 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. The DOT Language

    CSDN新首页上线啦,邀请你来立即体验! 立即体验 博客 学院 下载 更多 登录注册 The DOT Language 翻译 2014年04月15日 11:27:07 标签: EBNF / 语言 / ...

  2. 【前端统计图】echarts实现单条折线图

    五分钟上手: 图片.png <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  3. 网络编程02---HTTP协议

    1.URL简单介绍 1.client怎样找到server 我们都知道网络中部署着各种各样的server.比方腾讯的server.百度的server.那么问题来了.client怎样找到想要连接的serv ...

  4. mysql创建应用账号

    -- 赋予某个库全部权限use mysql;grant all privileges on test_db.* to test_user@'%' identified by 'Aa123456';gr ...

  5. js进阶-9-3/4 form对象有哪些常用属性

    js进阶-9-3/4 form对象有哪些常用属性 一.总结 一句话总结: 1.一般html标签有哪些常用属性:name id value 2.form对象有哪些常用属性(特有):action meth ...

  6. Oracle数据库零散知识03

    21,存储过程,简化复杂操作,增加数据独立性,提高安全性,提高性能 与函数创建对比: create or replace function fun_01(v_01 in number) return ...

  7. js取json对象的键和值

    //构建一个json对象 var pinpai = { "0":{"美的":49,"三星":35,"海信":25,&qu ...

  8. Java易混点记录

    1.Java 默认将所有成员变量和成员方法与 this 关联在一起,因此使用 this 在某些情况下是多余的. 2.只要类存在,程序就可以访问该类的类变量,语法如下: 类.类变量. 只要实例存在,程序 ...

  9. matlab 中使用 GPU 加速运算

    为了提高大规模数据处理的能力,matlab 的 GPU 并行计算,本质上是在 cuda 的基础上开发的 wrapper,也就是说 matlab 目前只支持 NVIDIA 的显卡. 1. GPU 硬件支 ...

  10. android制,点击EditText时刻,隐藏系统软键盘,显示光标

    由于项目中要用自己定义的随机键盘,所以必须得屏蔽系统软键盘,可是在4.0的測试系统来看,使用editText.setInputType(InputType.TYPE_NULL)方法固然能隐藏键盘,可是 ...