Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MarkupExtension with binding parameters

Tags:

binding

wpf

I am working on a custom MarkupExtension in which I need a non string parameters from XAML to construct the new object. Is it possible to use a non-string parameter binding on a field in DataContext scope?

In other words, how can I do something like this?

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

where IncludeMethods=CustomerObject.IsProblematic give me this error:

Binding cannot be set on the 'IncludeMethods' property of type 'TypeDescriptorExtension'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

Can anyone help me?

thanks

like image 634
user1351709 Avatar asked Apr 26 '12 07:04

user1351709


People also ask

What is Binding markup?

The {Binding} markup extension is used to data bind properties on controls to values coming from a data source such as code. The {Binding} markup extension is converted at XAML load time into an instance of the Binding class.

What is markup extension in WPF?

The most common markup extensions used in WPF programming are those that support resource references ( StaticResource and DynamicResource ), and those that support data binding ( Binding ). StaticResource provides a value for a property by substituting the value of an already defined resource.


3 Answers

I found a workaround for this problem.
The main idea is to define attached property for each parameter that requires binding.

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));
}

Usage is straightforward:

<TextBlock Text="{local:MarkupExtensionWithBindableParam Param1={Binding Path='SomePathToParam1'}}"/>
like image 112
nicolas2008 Avatar answered Sep 25 '22 07:09

nicolas2008


So as other have said, please first consider using a ValueConverter. This is the proper approach for manipulating bindings.

If however, you still want to use a MarkupExtension with bindings to the view-model or data context then you can create the binding manually in the markup extension class. This is similar to the approach taken by @nicolay.anykienko but we don't need to create an attached property.

As an example, I have created a currency symbol markup extension. The default behaviour is to use the CultureInfo.CurrentCulture but a few view-models have their own CultureInfo property that are different from the current culture. So for these view-models the XAML needs to bind to this property. Note that this could easily be done with a Converter instead, but for the sake of an example here is the markup extension:

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; }
}

This then gets used as follows:

<!-- Standard Usage -->
<TextBlock Text="{local:CurrencySymbol}"/>

<!-- With DataContext Binding -->
<TextBlock Text="{local:CurrencySymbol CultureBindingPath=ViewModelCulture}"/>

Where ViewModelCulture is the property on the view-model being used as the source of the binding.

like image 27
Ben Avatar answered Sep 26 '22 07:09

Ben


A 'Binding' can only be set on a DependencyProperty of a DependencyObject - it is true. The problem is that MarkupExtension class does not derive from DependencyObject, that's why it is not possible to set binding on it's properties.

[EDIT]

Workaround is using ValueConverters. Another workaround is to change C# language to allow multiple inheritance. By the way, in Silverlight MarkupExtension implements IMarkupExtension interface, so I tried to implement it in my custom extension and derive it from DependecyObject, added DependencyProperty there and set binding to it. It doesn't crash, but the binding is actually set after ProvideValue() is called. So even in Silverlight there's no solution (or it is difficult - see link provided in Klaus78's answer). In WPF MarkupExtension doesn't implement any interface, so you cannot bind to it's properties.

like image 36
EvAlex Avatar answered Sep 23 '22 07:09

EvAlex