带有绑定参数的MarkupExtension

10

我正在开发一个自定义的MarkupExtension,我需要从XAML中获取非字符串参数来构建新对象。在DataContext作用域中,是否可以对字段使用非字符串参数绑定?

换句话说,我该如何实现类似以下代码的功能?

<ListBox ItemsSource="{Binding Source={local:MyMarkupExtension {x:Type Button},IncludeMethods={Binding Source=CustomerObject.IsProblematic}}}" />

使用IncludeMethods=CustomerObject.IsProblematic时,我遇到了以下错误:

无法在类型为'TypeDescriptorExtension'的属性'IncludeMethods'上设置绑定。'Binding'只能在DependencyObject的DependencyProperty上设置。

有人能帮帮我吗?

谢谢


这个回答解决了你的问题吗?使用DataBinding值的MarkupExtension - StayOnTarget
4个回答

13
“Binding”只能在DependencyObject的DependencyProperty上设置-这是真的。问题在于MarkupExtension类没有继承自DependencyObject,因此无法在其属性上设置绑定。”
“[编辑]”
“解决方法是使用ValueConverters。另一个解决方法是将C#语言更改为允许多重继承。顺便说一句,在Silverlight中,MarkupExtension实现了IMarkupExtension接口,因此我尝试在我的自定义扩展中实现它,并从DependecyObject派生它,在那里添加DependencyProperty并将其绑定。它不会崩溃,但是绑定实际上是在ProvideValue()被调用之后设置的。因此,即使在Silverlight中也没有解决方案(或者很困难-请参见Klaus78's answer中提供的链接)。在WPF中,MarkupExtension不实现任何接口,因此您无法绑定到其属性。”

27
将C#语言更改以允许多重继承并不完全是我所谓的“变通方法” ;) - Thomas Levesque
10
在我看来,改变C#语言将是最优雅的解决方案。你可以在这里找到.Net Framework的源代码:https://github.com/dotnet(有多个存储库)。我认为,你需要编辑CLR,它可以在这里找到https://github.com/dotnet/coreclr。 - Snicker
2
@Snicker 镇定、讽刺并且让我开心! :D 好的,让我们一起 fork .NET 框架..... - bytecode77

2
正如其他人所说,请首先考虑使用ValueConverter来操作绑定。这是处理绑定的正确方法。
但是,如果您仍然想要使用MarkupExtension来绑定视图模型或数据上下文,那么您可以在MarkupExtension类中手动创建绑定。这类似于@nicolay.anykienko所采取的方法,但我们不需要创建附加属性。
例如,我创建了一个货币符号Markup Extension。默认行为是使用,但是一些视图模型具有其自己的CultureInfo属性,与当前文化不同。因此,对于这些视图模型,XAML需要绑定到此属性。请注意,这可以很容易地通过转换器完成,但出于示例的目的,这里是MarkupExtension:
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是视图模型上用作绑定源的属性。


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 示例存在问题。标记不是必须要用引号括起来吗? - ΩmegaMan
@nicolay.anykienko 我没有测试这个解决方案,但请检查一下我的编辑是否适用于你,XAML语法。 - Cfun
它需要一些额外的东西才能在(数据)模板中工作:https://stackoverflow.com/questions/44106154/bindingexpressionbase-is-null-in-custom-markupextension在代码中,这意味着在ProvideValue方法的开头添加以下内容:if(serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget targetProvider && targetProvider.TargetObject != null &&!(targetProvider.TargetObject is DependencyObject))返回此内容; - Coder14

0

1
它只适用于Silverlight,因为在WPF中MarkupExtension没有实现IMarkupExtension接口。 - EvAlex
1
同样适用于Xamarin Forms。耶,终于找到了一个Xamarin Forms领先于WPF的功能:P - ToolmakerSteve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接