Home > Software design >  WPF Twoway MultiBinding => lost binding?
WPF Twoway MultiBinding => lost binding?

Time:01-31

I have a checkbox with a MultiBinding where one binding is twoway (to a viewmodel) and the other is oneway (to it's own IsEnabled property). Everything seems to work fine until I touch the multibound checkbox. Then I suddenly loose a binding.

The following sample demonstrates this effect. In the real program, the IsEnabled property is also a multibinding, but that doesn't seem to make a difference.

<Window x:Class="TwowayMultiBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TwowayMultiBinding"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <StackPanel.Resources>
            <local:LogicalAndConverter x:Key="LogicalAndConverter"/>


        </StackPanel.Resources>
        <CheckBox Content="Enabled" Name="EnableCheck"/>
        <CheckBox Content="Ticked" Name="TickCheck"/>
        <CheckBox Content="Test" Name="TestCheck" IsEnabled="{Binding ElementName=EnableCheck, Path=IsChecked}">
            <CheckBox.IsChecked>
                <MultiBinding Converter="{StaticResource LogicalAndConverter}">
                    <Binding ElementName="TestCheck" Path="IsEnabled" Mode="OneWay"/>
                    <Binding ElementName="TickCheck" Path="IsChecked" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>
                </MultiBinding>
            </CheckBox.IsChecked>
        </CheckBox>

    </StackPanel>
</Window>

And I'm using the following converter:

public class LogicalAndConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values == null || values.Length < 2)
        {
            return DependencyProperty.UnsetValue;
        }
        for (int i = 0; i < values.Length; i  )
        {
            bool result;
            if (values[i] is bool bValue)
            {
                result = bValue;
            }
            else if (values[i] is bool?)
            {
                result = ((bool?)values[i]) ?? false;
            }
            else
            {
                return DependencyProperty.UnsetValue;
            }
            if (!result)
            {
                return false;   // early exit.
            }
        }
        return true;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is bool bValue)
        {
            return Enumerable.Repeat(value, targetTypes.Length).ToArray();
        }
        else if (value is bool?)
        {
            bool result = ((bool?)value) ?? false;
            return Enumerable.Repeat((object)result, targetTypes.Length).ToArray();
        }
        return null;
    }

When I run this example, the Test Check is disabled, because the Enabled checkbox is not checked (correct).

To reproduce the problem:

  • Click Enabled => Test check is enabled (correct).
  • Click Ticked => Test check is ticked (correct).
  • Click Ticked => Test check is unticked (correct).
  • Click Ticked => Test check is ticked (correct).
  • Click Enabled => Test check is disabled AND unticked (correct).
  • Click Enabled => Test check is enabled and ticked (since Tick Check is still checked). (correct)
  • Click Test => Test check is unchecked (correct).

But now, the Enabled check does not work anymore. Did I loose the binding?

Short route:

  • Start
  • Click Enabled => Test check is enabled (correct).
  • Click Test => Test check is unchecked (correct).

Once again, Enabled doesn't work anymore.

What I'm trying to do is:

  • Bind the check value to it's viewmodel (two way).
  • If Enabled is not checked, then the Test check should not be able to be checked in any way.

CodePudding user response:

Yes, you effectively lost the binding.

If you set a dependencyproperty to a value then that value over rides any binding unless the binding is twoway.

When you click, you are setting the ischecked value on Test.

One way to avoid this is to move your logic into the viewmodel. You can then have fine control in the setter and getters of your various properties over exactly what you want to happen.

You may want to consider intermediate properties so you have a properties representing the view and input, you have other properties or just fields represent internal state.

Maybe you're trying to use the Test checkbox for two different purposes where two different controls might be more appropriate. Maybe it should be read only.

CodePudding user response:

I think you need to change the back converter:

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        object[] objs = Enumerable.Repeat(Binding.DoNothing, targetTypes.Length).ToArray();
        if (value is bool bValue)
        {
            objs[1] = bValue;
        }
        else if(value is bool? bnValue)
        {
            objs[1] = (bool)(bnValue ?? false);
        }
        return objs;
    }
  • Related