带有绑定参数的 MarkupExtension
Posted
技术标签:
【中文标题】带有绑定参数的 MarkupExtension【英文标题】:MarkupExtension with binding parameters 【发布时间】:2012-05-06 21:54:58 【问题描述】:我正在开发一个自定义MarkupExtension
,我需要一个来自 XAML 的非字符串参数来构造新对象。是否可以在DataContext
范围内的字段上使用非字符串参数绑定?
换句话说,我该怎么做?
<ListBox ItemsSource="Binding Source=local:MyMarkupExtension x:Type Button,IncludeMethods=Binding Source=CustomerObject.IsProblematic" />
IncludeMethods=CustomerObject.IsProblematic
给我这个错误:
无法在“TypeDescriptorExtension”类型的“IncludeMethods”属性上设置绑定。 “绑定”只能在 DependencyObject 的 DependencyProperty 上设置。
谁能帮帮我?
谢谢
【问题讨论】:
这能回答你的问题吗? MarkupExtension that uses a DataBinding value 【参考方案1】:我找到了解决此问题的方法。 主要思想是为每个需要绑定的参数定义附加属性。
public class MarkupExtensionWithBindableParam : MarkupExtension
public BindingBase Param1 get; set; // its necessary to set parameter type as BindingBase to avoid exception that binding can't be used with non DependencyProperty
public override object ProvideValue(IServiceProvider serviceProvider)
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject targetObject;
DependencyProperty targetProperty;
if (target != null && target.TargetObject is DependencyObject && target.TargetProperty is DependencyProperty)
targetObject = (DependencyObject)target.TargetObject;
targetProperty = (DependencyProperty)target.TargetProperty;
else
return this; // magic
// Bind the Param1 to attached property Param1BindingSinkProperty
BindingOperations.SetBinding(targetObject, MarkupExtensionWithBindableParam.Param1BindingSinkProperty, Param1);
// Now you can use Param1
// Param1 direct access example:
object param1Value = targetObject.GetValue(Param1BindingSinkProperty);
// Param1 use in binding example:
var param1InnerBinding = new Binding() Source = targetObject, Path = new PropertyPath("(0).SomeInnerProperty", Param1BindingSinkProperty) ); // binding to Param1.SomeInnerProperty
return param1InnerBinding.ProvideValue(serviceProvider); // return binding to Param1.SomeInnerProperty
private static DependencyProperty Param1BindingSinkProperty = DependencyProperty.RegisterAttached("Param1BindingSink", typeof(object)// set the desired type of Param1 for at least runtime type safety check
, typeof(MarkupExtensionWithBindableParam ), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));
用法很简单:
<TextBlock Text="local:MarkupExtensionWithBindableParam Param1=Binding Path='SomePathToParam1'"/>
【讨论】:
Usage 示例有问题。标记不必用引号引起来吗? @nicolay.anykienko 我没有测试解决方案,但请检查我的编辑是否适合您,xaml 语法 它需要一些额外的东西才能在(数据)模板中工作:***.com/questions/44106154/… 在代码中,这意味着在 ProvideValue 方法的开头添加它:if(serviceProvider.GetService(typeof(IProvideValueTarget) ) is IProvideValueTarget targetProvider && targetProvider.TargetObject != null && !(targetProvider.TargetObject is DependencyObject)) return this;【参考方案2】:正如其他人所说,请首先考虑使用ValueConverter。这是操作绑定的正确方法。
但是,如果您仍想使用 MarkupExtension 绑定到视图模型或数据上下文,那么您可以在标记扩展类中手动创建绑定。这类似于@nicolay.anykienko 采用的方法,但我们不需要创建附加属性。
例如,我创建了一个货币符号标记扩展。默认行为是使用CultureInfo.CurrentCulture
,但一些视图模型有自己的 CultureInfo 属性,这些属性与当前文化不同。因此,对于这些视图模型,XAML 需要绑定到此属性。请注意,这可以使用 Converter 轻松完成,但为了举例,这里是标记扩展:
public class CurrencySymbolExtension : MarkupExtension
public override object ProvideValue(IServiceProvider serviceProvider)
var targetProvider = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var targetElement = targetProvider.TargetObject as FrameworkElement;
var targetProperty = targetProvider.TargetProperty as DependencyProperty;
if (!String.IsNullOrEmpty(CultureBindingPath) &&
targetElement != null &&
targetProperty != null)
// make sure that if the binding context changes then the binding gets updated.
targetElement.DataContextChanged +=
(sender, args) => ApplyBinding(targetElement, targetProperty, args.NewValue);
// apply a binding to the target
var binding = ApplyBinding(targetElement, targetProperty, targetElement.DataContext);
// return the initial value of the property
return binding.ProvideValue(serviceProvider);
else
// if no culture binding is provided then use the current culture
return CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol;
private Binding ApplyBinding(DependencyObject target, DependencyProperty property, object source)
BindingOperations.ClearBinding(target, property);
var binding = new Binding(CultureBindingPath + ".NumberFormat.CurrencySymbol")
Mode = BindingMode.OneWay,
Source = source,
FallbackValue = CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol,
;
BindingOperations.SetBinding(target, property, binding);
return binding;
public string CultureBindingPath get; set;
然后按如下方式使用:
<!-- Standard Usage -->
<TextBlock Text="local:CurrencySymbol"/>
<!-- With DataContext Binding -->
<TextBlock Text="local:CurrencySymbol CultureBindingPath=ViewModelCulture"/>
其中ViewModelCulture
是视图模型上用作绑定源的属性。
【讨论】:
【参考方案3】:此链接提供有关信息
Custom Markup Extension with bindable properties
编辑 有人让我注意到这仅适用于 Silverlight,因为在 WPF MarkupExtension 中没有实现 IMarkupExtension 接口。 (谢谢 EvAlex)
【讨论】:
它只适用于 Silverlight,因为在 WPF MarkupExtension 中没有实现 IMarkupExtension 接口 也适用于 Xamarin 表单。耶,终于找到了 Xamarin Forms 领先于 WPF 的功能:P【参考方案4】:“绑定”只能在 DependencyObject 的 DependencyProperty 上设置 - 这是真的。问题是MarkupExtension
类不是从DependencyObject
派生的,这就是为什么不能在它的属性上设置绑定。
[编辑]
解决方法是使用ValueConverters。另一种解决方法是更改 C# 语言以允许多重继承。顺便说一句,在 Silverlight 中MarkupExtension
实现了IMarkupExtension
接口,所以我尝试在我的自定义扩展中实现它并从DependecyObject
派生它,在那里添加DependencyProperty
并设置绑定。它不会崩溃,但实际上是在 调用 ProvideValue() 之后设置了绑定。所以即使在 Silverlight 中也没有解决方案(或者很难 - 请参阅Klaus78's answer 中提供的链接)。在 WPF MarkupExtension 中没有实现任何接口,所以你不能绑定到它的属性。
【讨论】:
更改 C# 语言以允许多重继承并不是我所说的“解决方法”;) 更改 C# 语言将是 IMO 最优雅的解决方案。您可以在此处找到 .Net Framework 的源代码:github.com/dotnet(有多个存储库)。我认为您必须编辑可以在此处找到的 CLR github.com/dotnet/coreclr @Snicker 冷静,讽刺,让我开心! :D 是的,让我们都 fork .NET 框架.....以上是关于带有绑定参数的 MarkupExtension的主要内容,如果未能解决你的问题,请参考以下文章
带有绑定参数的大型查询会导致Doctrine中的参数编号错误
使用带有 Spring Data 和绑定参数的 Postgres JSONB 查询失败并出现 InvalidDataAccessApiUsageException