Search code examples
wpfmultibindingimultivalueconverter

WPF ConvertBack MultiBinding


i'm facing a problem using MultiBinding and my custom IMulitValueConverter (using wpf and c#). First i'll show you my ViewModel and some xaml code and then i'll be describing my problem.

My ViewModel:

 public class ParametersViewModel
 {

     private ICollection<ParameterValue> _paramValues;

     public ICollection<ParameterValue> ParameterValues
     {
         get => _paramValues;
         set
         {
             if (_paramValues != value)
             {
                 _paramValues = value;
                 OnPropertyChanged(nameof(ParameterValues));
             }
         }
     }

     private ICollection<Parameter> _parameters;

     public ICollection<Parameter> Parameters
     {
         get => _parameters;
         set
         {
             if (value != _parameters)
             {
                 _parameters= value;
                 OnPropertyChanged(nameof(Parameters));
             }
         }
     }

Model:

    public class Parameter : IModelBase
    {
        [Key]
        public Guid ID { get; set; }

        [StringLength(50)]
        public string Name { get; set; }
    }

    public class ParameterValue : IModelBase
    {
        [Key]
        public Guid ID { get; set; }

        public Guid ParameterID { get; set; }
    }

The converter:

    public class ParameterVisibilityConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values.Length != 2 || !(values[0] is Guid) || !(values[1] is ICollection<ParameterValue>))
                return null;

            Guid parameterID = (Guid)values[0];
            ICollection<ParameterValue> valueInstances = (ICollection<ParameterValue>)values[1];
            if (!(valueInstances ?.Any() ?? false)) return null;

            // Find the SecondObject with the corresponding ID
            ParameterValue parameterValue = valueInstances 
                .FirstOrDefault(c => c.ParameterID == parameterID);
            if (parameterValue is null) return null;

            // Return the Name property of the found SecondObject
            return parameterValue.Visible;
        }

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

The XAML:

        <DataGrid x:Name="dataGrid" Height="auto" Width="auto" AutoGenerateColumns="False" 
                  ItemsSource="{Binding Parameters}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding ID}" Width="Auto" Header="ID" IsReadOnly="True" />
                <DataGridCheckBoxColumn Header="Visibility" Width="Auto" MinWidth="0" >
                    <DataGridCheckBoxColumn.Binding>
                        <MultiBinding Converter="{StaticResource ParameterVisibilityConverter}" UpdateSourceTrigger="PropertyChanged">
                            <Binding Path="ID"/>
                            <Binding Path="DataContext.ParameterValues" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=DataGrid}"/>
                        </MultiBinding>
                    </DataGridCheckBoxColumn.Binding>
                </DataGridCheckBoxColumn>

The CheckBox will be shown and also its state but there is no possibility to change the state from the View. I tried several ways of implementing a ConvertBack method but all have failed.

How could a proper way look like to implement this functionality?

Thanks for your Input!

Tried to implement a ConvertBack method but there is no access to the ID of the parameter and the collection of parameter values.


Solution

  • Most multibindings are not meant to go two ways. But there are cases where you need it. There is a way to do it, but it is a hack which I recommend avoiding if you can. It goes like this

    1. Make the converter remember the input values areray in some private member variable
    2. Make sure the converter object is never shared when used in this two-way scenario. You can do this by either a) using the x:Shared="False" attribute when you declare it in your resources or b) making it a MarkupExtension object and always creating an instance in the binding (rather than using StaticResource)

    So your converter might change to look like something like this

    public class ParameterVisibilityConverter : IMultiValueConverter
    {
        private object[] Values { get; set; }
    
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values.Length != 2 || !(values[0] is Guid) || !(values[1] is ICollection<ParameterValue>))
                return null;
    
            Guid parameterID = (Guid)values[0];
            ICollection<ParameterValue> valueInstances = (ICollection<ParameterValue>)values[1];
            if (!(valueInstances ?.Any() ?? false)) return null;
    
            Values = values;
    
            // Find the SecondObject with the corresponding ID
            ParameterValue parameterValue = valueInstances 
                    .FirstOrDefault(c => c.ParameterID == parameterID);
            if (parameterValue is null) return null;
    
            // Return the Name property of the found SecondObject
            return parameterValue.Visible;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
           // We remembered the values.  Just return them.  
           // If more complicated logic is required, do that instead
           return Values;
        }
    }
    

    But avoid this if you can. There are sometimes ways. For example, if your ICollection<ParameterValue> argument were some never-changing value that you could declare as a in XAML as a static resource then you could instead supply it to a single-value (IValueConverter.Convert) method as the ConverterParameter argument. That is preferrable, IMHO.