none
Multibinding two way with Multiconverter

    Question

  • I have a settings panel with two checkboxes for two different settings, MarkFetched, and AutoDownload. AutoDownload requires MarkFetched to be true, so unchecking MarkFetched should disable and uncheck AutoDownload. I've tried to accomplish this with a multibinding, and it almost works. The problem is that unchecking the AutoDownload box automatically does not populate the change to the underlying Settings store.
    Because of the two-way binding I thought that I should see the ConvertBack method of the converter being called when unchecking the MarkFetched box - it certainly sucessfully unchecks the box, but the change isn't being reflected in the source property.

    XAML is as follows:
                <CheckBox Grid.Column="1" Grid.Row="2" Margin="10,15,10,10" Name="checkBox1" IsChecked="{Binding Default.MarkFetched, Source={StaticResource settings}, Mode=TwoWay}">Download marks item as fetched</CheckBox>
                <CheckBox Grid.Column="1" Grid.Row="2" Margin="30,35,10,10" Name="checkBox2" IsEnabled="{Binding IsChecked, ElementName=checkBox1}">
                    <CheckBox.IsChecked>
                        <MultiBinding Converter="{StaticResource autoDownConv}" Mode="TwoWay">
                            <Binding Path="Default.AutoDownload" Source="{StaticResource settings}" />
                            <Binding Path="IsChecked" ElementName="checkBox1" Mode="OneWay" />
                        </MultiBinding>
                    </CheckBox.IsChecked>
                    Automatically download jobs
                </CheckBox>

    and code for the MultiConverter is:
        public class AutoDownloadConverter : IMultiValueConverter
        {
            public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            {
                return (bool)values[0] && (bool)values[1];
            }

            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            {
                return new object[] { (bool)value, true };
            }
        }

    Any ideas? I thought perhaps the IsEnabled binding was stopping the change in IsChecked from triggering the multibinding, but removing that has not effect.
    Thursday, August 21, 2008 1:52 AM

Answers

  • I think the reason ConvertBack is not being called is that the reason IsChecked has changed is because of the binding updating in the source->target direction.  WPF figures that the target (checkBox2.IsChecked) now reflects the status of the data source, and therefore there's no need to update the data source *from* the target.  If the user were changing the value of the binding target e.g. by clicking on the check box, that would be a different matter.  But it would be inefficient (and potentially dangerous) for WPF to run both directions of the converter when it knows which end is "dirty" and therefore which direction the data needs to flow in.

    Here are a couple of other ways you could tackle this.

    The first option is to add logic to your data source so that it ensures that AutoDownload is set to false when MarkFetched is set to false.  That way you are building the rule that "if MarkFetched is false, AutoDownload should also be false" into your business object, so it will always hold, rather than relying on the UI to keep your business object in a consistent state for you.  Then just bind checkBox2.IsChecked to AutoDownload, and checkBox2.IsEnabled to MarkFetched.  If you do this, don't forget to raise PropertyChanged so WPF knows to update the AutoDownload UI.

    The second option is to say, "Well, my code never looks at AutoDownload unless MarkFetched is true.  So what the heck, let AutoDownload be true even if MarkFetched is false!  It's not going to do any harm!"  Then bind checkBox2.IsChecked to MarkFetched *and* AutoDownload (and checkBox2.IsEnabled to MarkFetched).  You'll still get the behaviour you're concerned about at the moment, where AutoDownload in the business object is not set to false, but do you necessarily care?  Is that doing any harm?

    (By the way, I am suggesting binding your controls to the business object rather than to each other.  Binding controls to each other makes sense when one control is directly in charge of the state of another, e.g. a "font size" combo box affecting the font size of a form, but in this case, your controls are reflecting the state of a business object, so all changes should pass through the business object, not be passed from control to control behind the business object's back.  This ensures that the business object's logic -- e.g. "if MarkFetched is false, AutoDownload should also be false" -- always runs and is reflected in the UI, rather than having to be duplicated in the UI.  Again, don't forget to have your business object raise PropertyChanged if you do this.)

    I would advise going for the first option: business logic about the relationships between properties should be in the business object.  But if at a code level you want the underlying object to be able to be in the seemingly-inconsistent (but benign) state, and just want to hide that possibility from the user, then the second option is appropriate.

    Mindscape WPF controls: http://www.mindscape.co.nz/products/
    • Marked as answer by Andrew Polden Tuesday, August 26, 2008 12:42 AM
    • Marked as answer by Andrew Polden Tuesday, August 26, 2008 12:42 AM
    • Marked as answer by Andrew Polden Tuesday, August 26, 2008 12:52 AM
    Monday, August 25, 2008 1:16 AM
  • Hello Andrew,
    if you don't mind creating your own control, you can go with this one:
    public class DependantCheckBox : CheckBox  
    {  
        public DependantCheckBox()  
        {  
            IsEnabledProperty.OverrideMetadata(typeof(CheckBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(IsEnabledChanged)));                  
        }  
     
        private static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)  
        {  
            if (!(bool)e.NewValue) ((CheckBox)d).IsChecked = false;  
        }  
    }                   
     
    (tested with this settings class:)
    public class Settings : INotifyPropertyChanged  
    {  
        private bool _markFetched, _autoDownload;  
        public bool MarkFetched { get { return _markFetched; } set { _markFetched = value; OnPropertyChanged("MarkFetched"); } }  
        public bool AutoDownload { get { return _autoDownload; } set { _autoDownload = value; OnPropertyChanged("AutoDownload"); } }  
     
        private void OnPropertyChanged(string p)  
        {  
            if (PropertyChanged != null)  
                PropertyChanged(thisnew PropertyChangedEventArgs(p));  
        }  
       public event PropertyChangedEventHandler PropertyChanged;  
    }  
     


    the XAML then can look like this:
    <Window x:Class="WpfApplication1.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:WpfApplication1" 
        Title="Window1" Height="300" Width="300">  
        <Window.Resources> 
            <local:Settings x:Key="MySettings"  /> 
        </Window.Resources> 
        <StackPanel> 
            <CheckBox Name="MarkFetched"  IsChecked="{Binding MarkFetched, Source={StaticResource MySettings}}">Mark fetched</CheckBox> 
            <local:DependantCheckBox x:Name="AutoDownload" IsEnabled="{Binding IsChecked, ElementName=MarkFetched}" IsChecked="{Binding AutoDownload, Source={StaticResource MySettings}}">Auto download</local:DependantCheckBox> 
              
            <ToggleButton IsChecked="{Binding MarkFetched, Source={StaticResource MySettings}, Mode=OneWay}">Settings.MarkFetched</ToggleButton> 
            <ToggleButton IsChecked="{Binding AutoDownload, Source={StaticResource MySettings}, Mode=OneWay}">Settings.AutoDownload</ToggleButton> 
       </StackPanel> 
    </Window> 
     


    Though I agree with Ivan to handle this in the business object, I guess I would implement both options. :)

    Jan
    • Marked as answer by Andrew Polden Tuesday, August 26, 2008 12:43 AM
    Monday, August 25, 2008 11:31 PM

All replies

  • No ideas anybody?
    Sunday, August 24, 2008 10:46 PM
  • I think the reason ConvertBack is not being called is that the reason IsChecked has changed is because of the binding updating in the source->target direction.  WPF figures that the target (checkBox2.IsChecked) now reflects the status of the data source, and therefore there's no need to update the data source *from* the target.  If the user were changing the value of the binding target e.g. by clicking on the check box, that would be a different matter.  But it would be inefficient (and potentially dangerous) for WPF to run both directions of the converter when it knows which end is "dirty" and therefore which direction the data needs to flow in.

    Here are a couple of other ways you could tackle this.

    The first option is to add logic to your data source so that it ensures that AutoDownload is set to false when MarkFetched is set to false.  That way you are building the rule that "if MarkFetched is false, AutoDownload should also be false" into your business object, so it will always hold, rather than relying on the UI to keep your business object in a consistent state for you.  Then just bind checkBox2.IsChecked to AutoDownload, and checkBox2.IsEnabled to MarkFetched.  If you do this, don't forget to raise PropertyChanged so WPF knows to update the AutoDownload UI.

    The second option is to say, "Well, my code never looks at AutoDownload unless MarkFetched is true.  So what the heck, let AutoDownload be true even if MarkFetched is false!  It's not going to do any harm!"  Then bind checkBox2.IsChecked to MarkFetched *and* AutoDownload (and checkBox2.IsEnabled to MarkFetched).  You'll still get the behaviour you're concerned about at the moment, where AutoDownload in the business object is not set to false, but do you necessarily care?  Is that doing any harm?

    (By the way, I am suggesting binding your controls to the business object rather than to each other.  Binding controls to each other makes sense when one control is directly in charge of the state of another, e.g. a "font size" combo box affecting the font size of a form, but in this case, your controls are reflecting the state of a business object, so all changes should pass through the business object, not be passed from control to control behind the business object's back.  This ensures that the business object's logic -- e.g. "if MarkFetched is false, AutoDownload should also be false" -- always runs and is reflected in the UI, rather than having to be duplicated in the UI.  Again, don't forget to have your business object raise PropertyChanged if you do this.)

    I would advise going for the first option: business logic about the relationships between properties should be in the business object.  But if at a code level you want the underlying object to be able to be in the seemingly-inconsistent (but benign) state, and just want to hide that possibility from the user, then the second option is appropriate.

    Mindscape WPF controls: http://www.mindscape.co.nz/products/
    • Marked as answer by Andrew Polden Tuesday, August 26, 2008 12:42 AM
    • Marked as answer by Andrew Polden Tuesday, August 26, 2008 12:42 AM
    • Marked as answer by Andrew Polden Tuesday, August 26, 2008 12:52 AM
    Monday, August 25, 2008 1:16 AM
  • Hello Andrew,
    if you don't mind creating your own control, you can go with this one:
    public class DependantCheckBox : CheckBox  
    {  
        public DependantCheckBox()  
        {  
            IsEnabledProperty.OverrideMetadata(typeof(CheckBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(IsEnabledChanged)));                  
        }  
     
        private static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)  
        {  
            if (!(bool)e.NewValue) ((CheckBox)d).IsChecked = false;  
        }  
    }                   
     
    (tested with this settings class:)
    public class Settings : INotifyPropertyChanged  
    {  
        private bool _markFetched, _autoDownload;  
        public bool MarkFetched { get { return _markFetched; } set { _markFetched = value; OnPropertyChanged("MarkFetched"); } }  
        public bool AutoDownload { get { return _autoDownload; } set { _autoDownload = value; OnPropertyChanged("AutoDownload"); } }  
     
        private void OnPropertyChanged(string p)  
        {  
            if (PropertyChanged != null)  
                PropertyChanged(thisnew PropertyChangedEventArgs(p));  
        }  
       public event PropertyChangedEventHandler PropertyChanged;  
    }  
     


    the XAML then can look like this:
    <Window x:Class="WpfApplication1.Window1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:WpfApplication1" 
        Title="Window1" Height="300" Width="300">  
        <Window.Resources> 
            <local:Settings x:Key="MySettings"  /> 
        </Window.Resources> 
        <StackPanel> 
            <CheckBox Name="MarkFetched"  IsChecked="{Binding MarkFetched, Source={StaticResource MySettings}}">Mark fetched</CheckBox> 
            <local:DependantCheckBox x:Name="AutoDownload" IsEnabled="{Binding IsChecked, ElementName=MarkFetched}" IsChecked="{Binding AutoDownload, Source={StaticResource MySettings}}">Auto download</local:DependantCheckBox> 
              
            <ToggleButton IsChecked="{Binding MarkFetched, Source={StaticResource MySettings}, Mode=OneWay}">Settings.MarkFetched</ToggleButton> 
            <ToggleButton IsChecked="{Binding AutoDownload, Source={StaticResource MySettings}, Mode=OneWay}">Settings.AutoDownload</ToggleButton> 
       </StackPanel> 
    </Window> 
     


    Though I agree with Ivan to handle this in the business object, I guess I would implement both options. :)

    Jan
    • Marked as answer by Andrew Polden Tuesday, August 26, 2008 12:43 AM
    Monday, August 25, 2008 11:31 PM
  • Thanks guys, I decided to go with the first of Ivan's options - the constraint is now applied on the settings object level.

    Logical UI/business separation decisions aside, It seems a pity that the multibinding is limited in this sense though. I understand the problem with the binding update direction for a single source binding, but surely a multibinding class could keep track of data updates for each of it's binding components? This is surely part of the reason why they decided to allow overriding of a multibinding's direction mode within each "subbinding".
    Tuesday, August 26, 2008 12:52 AM