Binding ConverterParameter
Asked Answered
G

3

194

Is there a way I could do this in a Style:

<Style TargetType="FrameworkElement">
    <Setter Property="Visibility">
        <Setter.Value>
            <Binding Path="Tag"
                RelativeSource="{RelativeSource AncestorType=UserControl}"
                Converter="{StaticResource AccessLevelToVisibilityConverter}"
                ConverterParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}" />                        
        </Setter.Value>
    </Setter>
</Style>

I simply need to send the Tag of top-level parent and the Tag of control itself to my converter class.

Gastrointestinal answered 9/3, 2013 at 9:7 Comment(0)
C
362

The ConverterParameter property can not be bound because it is not a dependency property.

Since Binding is not derived from DependencyObject none of its properties can be dependency properties. As a consequence, a Binding can never be the target object of another Binding.

There is however an alternative solution. You could use a MultiBinding with a multi-value converter instead of a normal Binding:

<Style TargetType="FrameworkElement">
    <Setter Property="Visibility">
        <Setter.Value>
            <MultiBinding Converter="{StaticResource AccessLevelToVisibilityConverter}">
                <Binding Path="Tag" RelativeSource="{RelativeSource Mode=FindAncestor,
                                                     AncestorType=UserControl}"/>
                <Binding Path="Tag" RelativeSource="{RelativeSource Mode=Self}"/>
            </MultiBinding>
        </Setter.Value>
    </Setter>
</Style>

The multi-value converter gets an array of source values as input:

public class AccessLevelToVisibilityConverter : IMultiValueConverter
{
    public object Convert(
        object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return values.All(v => (v is bool && (bool)v))
            ? Visibility.Visible
            : Visibility.Hidden;
    }

    public object[] ConvertBack(
        object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}
Capitalize answered 9/3, 2013 at 10:48 Comment(0)
G
38

No, unfortunately this will not be possible because ConverterParameter is not a DependencyProperty so you won't be able to use bindings

But perhaps you could cheat and use a MultiBinding with IMultiValueConverter to pass in the 2 Tag properties.

Gilson answered 9/3, 2013 at 10:47 Comment(0)
G
29

There is also an alternative way to use MarkupExtension in order to use Binding for a ConverterParameter. With this solution you can still use the default IValueConverter instead of the IMultiValueConverter because the ConverterParameter is passed into the IValueConverter just like you expected in your first sample.

Here is my reusable MarkupExtension:

/// <summary>
///     <example>
///         <TextBox>
///             <TextBox.Text>
///                 <wpfAdditions:ConverterBindableParameter Binding="{Binding FirstName}"
///                     Converter="{StaticResource TestValueConverter}"
///                     ConverterParameter="{Binding ConcatSign}" />
///             </TextBox.Text>
///         </TextBox>
///     </example>
/// </summary>
[ContentProperty(nameof(Binding))]
public class ConverterBindableParameter : MarkupExtension
{
    #region Public Properties

    public Binding Binding { get; set; }
    public BindingMode Mode { get; set; }
    public IValueConverter Converter { get; set; }
    public Binding ConverterParameter { get; set; }

    #endregion

    public ConverterBindableParameter()
    { }

    public ConverterBindableParameter(string path)
    {
        Binding = new Binding(path);
    }

    public ConverterBindableParameter(Binding binding)
    {
        Binding = binding;
    }

    #region Overridden Methods

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var multiBinding = new MultiBinding();
        Binding.Mode = Mode;
        multiBinding.Bindings.Add(Binding);
        if (ConverterParameter != null)
        {
            ConverterParameter.Mode = BindingMode.OneWay;
            multiBinding.Bindings.Add(ConverterParameter);
        }
        var adapter = new MultiValueConverterAdapter
        {
            Converter = Converter
        };
        multiBinding.Converter = adapter;
        return multiBinding.ProvideValue(serviceProvider);
    }

    #endregion

    [ContentProperty(nameof(Converter))]
    private class MultiValueConverterAdapter : IMultiValueConverter
    {
        public IValueConverter Converter { get; set; }

        private object lastParameter;

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (Converter == null) return values[0]; // Required for VS design-time
            if (values.Length > 1) lastParameter = values[1];
            return Converter.Convert(values[0], targetType, lastParameter, culture);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            if (Converter == null) return new object[] { value }; // Required for VS design-time

            return new object[] { Converter.ConvertBack(value, targetTypes[0], lastParameter, culture) };
        }
    }
}

With this MarkupExtension in your code base you can simply bind the ConverterParameter the following way:

<Style TargetType="FrameworkElement">
<Setter Property="Visibility">
    <Setter.Value>
     <wpfAdditions:ConverterBindableParameter Binding="{Binding Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}"
                 Converter="{StaticResource AccessLevelToVisibilityConverter}"
                 ConverterParameterBinding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag}" />          
    </Setter.Value>
</Setter>

Which looks almost like your initial proposal.

Garrettgarrick answered 16/1, 2019 at 17:10 Comment(4)
This is useful. However, MultiValueConverterAdapter is missing. I found it here.Sarmentose
It does not work on Xamarin Forms because multiBinding.ProvideValue(serviceProvider) does not exist. Any alternative ?Inshrine
try binding to DP: System.InvalidCastException:“Unable to cast object of type 'MS.Internal.NamedObject' to type.Placid
Worked exact like described in Framework 4.6.2. Great workPeoria

© 2022 - 2024 — McMap. All rights reserved.