我正在开发一个Custom MarkupExtension,我需要XAML中的非字符串参数来构造新对象。是否可以在datacontext范围内的字段上使用非字符串参数绑定?
换句话说,我该怎么办呢?
<ListBox ItemsSource="{Binding Source={local:MyMarkupExtension {x:Type Button},IncludeMethods={Binding Source=CustomerObject.IsProblematic}}}" />
其中IncludeMethods=CustomerObject.IsProblematic
给出了这个错误:无法在“TypeDescriptorExtension”类型的“IncludeMethods”属性上设置绑定。 '绑定'只能在DependencyObject的DependencyProperty上设置。
任何人都可以帮助我吗?
感谢
答案 0 :(得分:12)
'Binding'只能在DependencyObject的DependencyProperty上设置 - 它是真的。问题是MarkupExtension
类不是从DependencyObject
派生的,这就是为什么不能对它的属性设置绑定的原因。
<强> [编辑] 强>
解决方法正在使用ValueConverters。另一种解决方法是更改C#语言以允许多重继承。顺便说一下,在Silverlight中MarkupExtension
实现IMarkupExtension
接口,所以我尝试在我的自定义扩展中实现它并从DependecyObject
派生它,在那里添加DependencyProperty
并设置绑定到它。它不会崩溃,但绑定实际上是在调用ProvideValue()之后设置的。因此,即使在Silverlight中也没有解决方案(或者很难 - 请参阅Klaus78's answer中提供的链接)。在WPF中,MarkupExtension不实现任何接口,因此您无法绑定到它的属性。
答案 1 :(得分:0)
此链接提供了有关
的信息Custom Markup Extension with bindable properties
EDIT 有人让我注意到这只适用于Silverlight,因为在WPF中MarkupExtension没有实现IMarkupExtension接口。 (谢谢EvAlex)
答案 2 :(得分:0)
因此,正如其他人所说,请首先考虑使用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 :(得分:-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"}}/>