Nested MultiBinding(s)
Asked Answered
R

7

25

I've been playing around with WPF for quite a while now, but for the first time today, I needed to nest a MultiBinding inside another, something like:

<MultiBinding>
   <Binding />       
   <MultiBinding>
      <Binding />
      <Binding />
   </MultiBinding>
</MultiBinding>

I get an exception indicating it's not allowed by the framework:

XamlParseException was unhandled: Add value to collection of type 'System.Collections.ObjectModel.Collection(System.Windows.Data.BindingBase)' threw an exception.

The InnerException is more explicit:

BindingCollection does not support items of type MultiBinding. Only Binding is allowed.

So digging the web for more info, I stumbled upon this Microsoft Connect issue which is exactly my problem.

Thank you for the feedback. WPF doesn't support this today. This feature has been requested for years (most recently earlier this month - see https://connect.microsoft.com/WPF/feedback/details/650164/nested-multibinding). We'll continue to consider this for future releases.

Right now I've made my peace that I won't have it easy. Still I need this, how can I nest MultiBindings?

Rameau answered 23/4, 2012 at 12:23 Comment(6)
What is it that you're trying to do, that you want to nest multibindings?Unfavorable
I am converting boolean values with complex orders of precedence and while some of them are already booleans, others need a conversion first and thus are themselves embedded in other IMultiValueConverter(s).Rameau
If your other MultiValueConverters have no more than two input values, you could use a converter with a parameter instead... or you could use a converter with a complex parameter to input multiple values, which is a little bit of work but should do the trick. If that sounds feasible I could post a sample?Unfavorable
For argument's sake, let's say most of my converters only take 2 inputs. Parameter cannot be bound, it is not a dependency property :/Rameau
Now sure, I could go and leverage the Hillberg's Freezable trick, or DatacontextSpy and the likes, but if you ever used that, you know it's squishy...Rameau
There are ways of binding non-dependency properties, using some filthy attached properties - not quite as squishy as Hillberg's, as long as you can work within the limitations. Not come across DatacontextSpy before...Unfavorable
R
0

simply use one multibinding an a MultiConverter.

or what i prefer expose your condition in one property of your viewmodel/datacontext.

Ribble answered 23/4, 2012 at 12:29 Comment(4)
Yeah but that defeats the whole purpose of it, If I need to create a new converter that just combines other converters, and since i got around a hundred of them... That's a lot of boilerplate.Rameau
Indeed it would, but I'm trying to reuse what I already have ;)Rameau
can you expose your condition in one property of your viewmodel? so you wouldnt need a multiconverter/multibinding at allRibble
I ended up doing something like that.Rameau
B
9

I know that this is an old question but I think this this is a much nicer approach:

<FrameworkElement x:Name="IsBuyAndAmountInReference">
    <FrameworkElement.Tag>
        <MultiBinding Converter="{StaticResource LogicAndToBool}">
            <Binding Path="OrderData.IsBuy" />
            <Binding Path="OrderData.AmountInReference" />
        </MultiBinding>
    </FrameworkElement.Tag>
</FrameworkElement>
<FrameworkElement x:Name="IsSellAndAmountInBase">
    <FrameworkElement.Tag>
        <MultiBinding Converter="{StaticResource LogicAndToBool}">
            <Binding Path="OrderData.IsBuy" Converter="{StaticResource BooleanToBooleanInvert}" />
            <Binding Path="OrderData.AmountInReference" Converter="{StaticResource BooleanToBooleanInvert}" />
        </MultiBinding>
    </FrameworkElement.Tag>
</FrameworkElement>

<Slider Grid.Row="2" Grid.ColumnSpan="4">
    <Slider.Visibility>
        <MultiBinding Converter="{StaticResource LogicOrToVisibility}">
            <Binding ElementName="IsBuyAndAmountInReference" Path="Tag" />
            <Binding ElementName="IsSellAndAmountInBase" Path="Tag" />
        </MultiBinding>
    </Slider.Visibility>
</Slider>
Bronchiole answered 23/5, 2020 at 20:6 Comment(1)
Genius! You can actually create nested bindings with this trick. The 'nested' behavior is exactly what you get from this approach.Goggin
I
7

An alternative to the other suggestions is to use attached properties to hold nested MultiBindings as intermediate values.

For example, instead of:

<Element>
  <Element.Property>
    <MultiBinding>
      <Binding Path="A" />       
      <MultiBinding>
        <Binding Path="B" />
        <Binding Path="C" />
      </MultiBinding>
    </MultiBinding>
  </Element.Property>
</Element>

...do this:

<Element Name="ElementName">
  <ElementProperties.AttachedProperty>
    <MultiBinding>
      <Binding Path="B" />
      <Binding Path="C" />
    </MultiBinding>
  </ElementProperties.AttachedProperty>
  <Element.Property>
    <MultiBinding>
      <Binding Path="A" />       
      <Binding ElementName="ElementName" Path="(ElementProperties.AttachedProperty)" />
    </MultiBinding>
  </Element.Property>
</Element>

I know this question is more than six years old now, but I ran into it so someone else will, too.

Intrigant answered 10/12, 2018 at 14:36 Comment(0)
U
5

If you have a converter that takes a parameter, you can do something like this:

  • Create a class for passing the "fixed" data to your converter
  • Add DependencyProperties to the class (so that you can bind the values in Xaml)
  • In your xaml, use a binding with a converter instead of a multibinding, something like this:

    <MultiBinding>
        <Binding Source="SomeObject" Path="CoreValue" Converter="{StaticResource YourNewConverter}">
            <Binding.ConverterParameter>
                <ns:ParameterClass Value1="{Binding Parameter1}" Value2="{Binding Parameter1}" />
            </Binding.ConverterParameter>
        </Binding>
     .... 
    

The limitation is that (AFAIK) the value will only be recalculated if CoreValue changes - it won't automatically rebind if the converter parameters change.

(Apologies for any errors, I'm typing this without the benefit of VS to test in...)

Unfavorable answered 23/4, 2012 at 13:4 Comment(1)
I think it's a valid idea, but it would take too long to optimize with inheriting DataContext so that ParameterClass's properties can be bound to.Rameau
B
5

I realise that this is an old question now, but I just hit exactly the same problem as the OP. Fortunately in my case I could bind to a sub-element where the result of the multi-binding was already being calculated, but it got me thinking...

A (though admittedly not very clean) solution would be to write the value of the multi-value binding into a 'spare' property such as an element's 'Tag' which you can then reference in your other multi-value bindings by specifying the 'ElementName' attribute.

If you need more than a single nested multi-value binding then you could create a 'fake' object with some dependency properties on it to store multiple intermediate results.

A pity that Microsoft don't implement a properly nested system...

Breechblock answered 11/3, 2015 at 10:52 Comment(0)
R
0

simply use one multibinding an a MultiConverter.

or what i prefer expose your condition in one property of your viewmodel/datacontext.

Ribble answered 23/4, 2012 at 12:29 Comment(4)
Yeah but that defeats the whole purpose of it, If I need to create a new converter that just combines other converters, and since i got around a hundred of them... That's a lot of boilerplate.Rameau
Indeed it would, but I'm trying to reuse what I already have ;)Rameau
can you expose your condition in one property of your viewmodel? so you wouldnt need a multiconverter/multibinding at allRibble
I ended up doing something like that.Rameau
W
0

If you're binding to a string you can use StringFormat just like in this example:

 <TextBlock>
   <TextBlock.Text>
     <MultiBinding StringFormat="{}{0:0.###}/{1:0.###}" Mode="OneWay">
                        <Binding ElementName="This" Path="AggregatedDocDetail.ConfirmedQty"></Binding>
                        <Binding ElementName="This" Path="AggregatedDocDetail.Qty">   </Binding>
       </MultiBinding>
     </TextBlock.Text>
   </TextBlock>
Wartime answered 23/4, 2012 at 12:42 Comment(1)
I'm binding to booleans (see comments of the question) and also to POCOs.Rameau
A
0

You can use a simple binding proxy as well.

<DataGrid.ContextMenu>
    <ContextMenu>
        ...
        <MenuItem Click="MenuItem_Click" Header="Use in filter" Command="{Binding SetCatFilterCommand}">
            <MenuItem.Resources>
                <local:BindingProxy x:Key="AsLitterIsEnabled" >
                    <local:BindingProxy.Data>
                        <MultiBinding Converter="{local:ViewCatConverter}"  
                            ConverterParameter="AsLitterIsEnabled">
                            <Binding Path="Heart.Cat" Mode="OneWay"/>
                            <Binding Path="Heart.SelectedLitters"                                 Mode="OneWay"/>
                        </MultiBinding>
                    </local:BindingProxy.Data>
                </local:BindingProxy>
            </MenuItem.Resources>
            <MenuItem.CommandParameter>
                <MultiBinding Converter="{local:MultiValueToArrayConverter}">
                    <Binding Path="Heart.SelectedLitters"/>
                    <Binding Source="AsLitter"/>
                    <Binding Path="CatFilter"/>
                    <Binding Path="Data" Source="{StaticResource AsLitterIsEnabled}"/>
                </MultiBinding>
             </MenuItem.CommandParameter>
         </MenuItem>
         ...
    </ContextMenu>
</DataGrid.ContextMenu>

The BindingProxy class:

using System.Windows;

public class BindingProxy : Freezable
{
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object),
        typeof(BindingProxy));

    public object Data
    {
        get => GetValue(DataProperty);
        set => SetValue(DataProperty, value);
    }

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }
}
Altaf answered 26/1, 2023 at 8:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.