C# 如何绑定到DynamicSource以便使用转换器或StringFormat等。?(第4次修订)
注意:这是对早期设计的修订,该设计的局限性是不能在某个样式中使用,这相当程度上否定了它的有效性。但是,这个新版本现在可以与样式一起使用,本质上让您可以在任何可以使用绑定或动态资源的地方使用它,并获得预期的结果,从而使它变得非常有用C# 如何绑定到DynamicSource以便使用转换器或StringFormat等。?(第4次修订),c#,wpf,ivalueconverter,dynamicresource,C#,Wpf,Ivalueconverter,Dynamicresource,注意:这是对早期设计的修订,该设计的局限性是不能在某个样式中使用,这相当程度上否定了它的有效性。但是,这个新版本现在可以与样式一起使用,本质上让您可以在任何可以使用绑定或动态资源的地方使用它,并获得预期的结果,从而使它变得非常有用 从技术上讲,这不是一个问题。这篇文章展示了我发现的一种方法,可以轻松地使用转换器,并将DynamicResource作为源代码,但为了遵循s/o的最佳实践,我将其作为问答对发布。所以,看看我下面的答案,看看我是如何做到这一点的。希望有帮助 我一直觉得WPF中缺少了一些
从技术上讲,这不是一个问题。这篇文章展示了我发现的一种方法,可以轻松地使用转换器,并将
DynamicResource
作为源代码,但为了遵循s/o的最佳实践,我将其作为问答对发布。所以,看看我下面的答案,看看我是如何做到这一点的。希望有帮助 我一直觉得WPF中缺少了一些功能:使用动态资源作为绑定源的能力。我从技术上理解了这一点——为了检测更改,绑定的源必须是DependencyObject
或支持INotifyPropertyChanged
的对象上的属性,动态资源实际上是一个Microsoft internalResourceReferenceExpression
,它等同于资源的值(即,它不是一个具有要绑定的属性的对象,更不用说具有更改通知的对象了)——但是,它始终困扰着我,因为在运行时可以更改,它应该能够在需要时通过转换器推动
嗯,我相信我终于纠正了这个限制
进入动态资源绑定
注意:我称之为“绑定”,但从技术上讲,它是一个标记扩展
,我在其上定义了属性,如转换器
,转换器参数
,转换器文化
,等等,但它最终会在内部使用绑定(实际上有几个!),因此,我根据其用法对其进行了命名,不是它的实际类型
但是为什么呢?
那你为什么还要这么做呢?如何根据用户偏好全局缩放字体大小,同时借助MultiplyByConverter
,仍能使用相对字体大小?或者简单地基于double
资源定义应用程序范围的边距,使用doubletothicknesconverter
不仅可以将边距转换为厚度,还可以根据需要在布局中遮住边距?或者在一个资源中定义一个基本的颜色,然后使用一个转换器使其变亮或变暗,或者使用一个颜色着色转换器改变其不透明度
更好的是,将上述实现为MarkupExtension
s,您的XAML也会简化
简言之,这有助于整合主资源中的所有“基本值”,但能够在使用它们的时间和地点对它们进行调整,而不必在资源集合中为它们添加“x”个变体
神奇的酱汁
DynamicResourceBinding
的实现得益于Freezable
数据类型的巧妙技巧。具体地说
如果向FrameworkElement的资源集合添加Freezable,则该Freezable对象上设置为动态资源的任何依赖项属性都将解析相对于该FrameworkElement在可视树中的位置的资源
使用这一点“魔力酱”,诀窍是在代理可自由化的对象的依赖属性上设置DynamicResource
,将该可自由化的添加到目标框架元素的资源集合中,然后在这两者之间设置一个绑定,现在这是允许的,因为源现在是一个DependencyObject
(即Freezable
)
复杂性是在样式中使用时获得目标框架元素
,因为标记扩展
在定义的地方提供其值,而不是最终应用其结果的地方。这意味着当您直接在框架元素
上使用标记扩展时,它的目标是您所期望的框架元素
。但是,如果在样式中使用标记扩展名
,则样式
对象是标记扩展名
的目标,而不是应用它的框架元素
。由于使用了第二个内部绑定,我也设法绕过了这个限制
也就是说,下面是一个内嵌注释的解决方案:
动态资源绑定
“神奇酱汁!”阅读内联评论了解发生了什么
绑定代理
这是上面提到的Freezable
,但对于其他需要跨越可视树边界的绑定代理相关模式也很有帮助。在这里或谷歌上搜索“BindingProxy”,以获取有关其他用法的更多信息。太棒了
public class BindingProxy : Freezable {
public BindingProxy(){}
public BindingProxy(object value)
=> Value = value;
protected override Freezable CreateInstanceCore()
=> new BindingProxy();
#region Value Property
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value),
typeof(object),
typeof(BindingProxy),
new FrameworkPropertyMetadata(default));
public object Value {
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
#endregion Value Property
}
注意:同样,您必须使用Freezable才能工作。将任何其他类型的DependencyObject插入目标FrameworkElement的资源(讽刺的是,甚至是另一个FrameworkElement)将解析相对于应用程序而不是关联FrameworkElement的动态资源,因为资源集合中的非Freezable不参与本地化资源查找。因此,您将丢失可视树中定义的所有资源
绑定触发器
此类用于强制多重绑定
刷新,因为我们无法访问最终的绑定表达式
。(从技术上讲,您可以使用任何支持更改通知的类,但我个人喜欢我的设计明确说明它们的用法。)
内联多转换器
这允许您通过简单地提供用于转换的方法,轻松地在代码隐藏中设置转换器。(对于InlineConverter,我有一个类似的示例)
public class BindingProxy : Freezable {
public BindingProxy(){}
public BindingProxy(object value)
=> Value = value;
protected override Freezable CreateInstanceCore()
=> new BindingProxy();
#region Value Property
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value),
typeof(object),
typeof(BindingProxy),
new FrameworkPropertyMetadata(default));
public object Value {
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
#endregion Value Property
}
public class BindingTrigger : INotifyPropertyChanged {
public BindingTrigger()
=> Binding = new Binding(){
Source = this,
Path = new PropertyPath(nameof(Value))};
public event PropertyChangedEventHandler PropertyChanged;
public Binding Binding { get; }
public void Refresh()
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
public object Value { get; }
}
public class InlineMultiConverter : IMultiValueConverter {
public delegate object ConvertDelegate (object[] values, Type targetType, object parameter, CultureInfo culture);
public delegate object[] ConvertBackDelegate(object value, Type[] targetTypes, object parameter, CultureInfo culture);
public InlineMultiConverter(ConvertDelegate convert, ConvertBackDelegate convertBack = null){
_convert = convert ?? throw new ArgumentNullException(nameof(convert));
_convertBack = convertBack;
}
private ConvertDelegate _convert { get; }
private ConvertBackDelegate _convertBack { get; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
=> _convert(values, targetType, parameter, culture);
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
=> (_convertBack != null)
? _convertBack(value, targetTypes, parameter, culture)
: throw new NotImplementedException();
}