Search code examples
c#wpfdatagridcomboboxmultibinding

MultiValueConverter - NotifyPropertyChanged


Have a little annoying problem with my requirements and hope its possible to solve.

Lets assume you have the following classes:

public class Foo
{
    public string Name { get; set; }
    public List<FooB> FooBs { get; set; }
}

public class FooB
{
    public int Id1 { get; set; }
    public int Id2 { get; set; }
    public decimal Sum { get; set; }
}

Now the Foo class has a list of FooB with givenId1 and Id2 values and a Sum Value that gets calculated.

In my WPF User Interface, I have 2 ComboBoxes and 1 DataGrid. 1 ComboBox hold information about the Id1 and the other about Id2.

Now in my DataGrid, I have a list of Foo displayed with 2 Columns one is obviously the Name, but the other gives me a headache right now.

The second column should display the Sum property of the "correct" FooB class.

The correct FooB class is determined by the 2 ComboBoxes in the UI and there SelectedItem.

What I have done so far is create 2 Properties in my CodeBehind: (Notice they actually have backingfields and PropertyChanged specified I reduced the code to my main problem)

public int SelectedId1 { get; set; }
public int SelectedId2 { get; set; }

Those two properties are bound to the corresponding ComboBox:

<c1:C1ComboBox ItemsSource="{Binding Id1s}"
                SelectedItem="{Binding SelectedId1}" />
<c1:C1ComboBox ItemsSource="{Binding Id2s}"
                SelectedItem="{Binding SelectedId2}" />

My DataGrid so far looks like this:

<local:BindingProxy x:Key="BindingProxy"
                    Data="{Binding}" />

<DataGrid ItemsSource={Bindings Foos}>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Name}" />
        <DataGridTextColumn>
            <DataGridTextColumn.Binding>
                <MultiBinding Converter="{StaticResource GetCorrectFooB}">
                    <Binding Binding="."></Binding>
                    <Binding Binding="Data.SelectedId1"></Binding>
                    <Binding Binding="Data.SelectedId2"></Binding>
                </MultiBinding>
            </DataGridTextColumn.Binding>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

(Notice the BindingProxy is from here: http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ - to enable getting Data from the Window within the DataContext of a DataGridRow)

The Converter looks like this:

public class GetCorrectFooB : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var foo = (Foo)values[0];
        var id1 = (int) values[1];
        var id2 = (int) values[2];

        return foo.FooBs.First(x => x.Id1 == id1 && x.Id2 == id2).Sum;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

This works pretty well for now, even behaves correctly when I change values in the 2 ComboBoxes in UI and updates information correspondingly BUT... when the underlying Sum PropertyChanges and is getting notified by PropertyChanges the UI does not update.

Its working fine if I would just bind the column to

<DataGridTextColumn Binding="{Binding FooBs[12].Sum}" />

You can imagine that I dont want to have a Index there, since I need to update it through ComboBoxes in the UI.

Hope you can help me out.


Solution

  • First off, I'm struggling to understand how your current binding expression is actually presenting the sum, as your converter seems to just return the FooB object itself, and not it's sum property - so unless you've got a specific ToString() implementation on your FooB class that formats it to present the sum, I'm completely lost.

    Secondly, I think your problem stems from the fact that your MultiBinding is based off of two properties only - Data.SelectedId1 and Data.SelectedId2, therefore, unless either of those two properties change, the MultiBinding is not going to receive a PropertyChanged event and update.

    A change of value of the sum property of your result is neither of the two, and so this is most likely why the view is not being updated.

    I'm racking my brain as the best way to solve this, perhaps you should just handle this in the ViewModel, having a property named CurrentFooB or something that you can bind to. You would then set this value to the corresponding FooB instance in the SelectionChanged event handlers of your combo box.

    Your binding would then look something like:

    <DataGrid ItemsSource={Binding Foos}>
    
        <DataGrid.Columns>
    
            <DataGridTextColumn Binding="{Binding Name}"/>
            <DataGridTextColumn Binding="{Binding CurrentFooB.Sum}"/>
    
        </DataGrid.Columns>
    
    </DataGrid>
    

    Now, provided that FooB implements INotifyPropertyChanged, your view should update when the sum of the currently found FooB changes.

    EDIT 1 (attempt at setting the source of the binding dynamically):

    <DataGrid ItemsSource={Binding Foos}>
    
        <DataGrid.Columns>
    
            <DataGridTextColumn Binding="{Binding Name}"/>
            <DataGridTextColumn>
    
                <DataGridTextColumn.Binding>
    
                    <Binding Path="Sum">
    
                        <Binding.Source>
    
                            <MultiBinding Converter="{StaticResource GetCorrectFooB}">
                                <Binding Binding="."></Binding>
                                <Binding Binding="Data.SelectedId1"></Binding>
                                <Binding Binding="Data.SelectedId2"></Binding>
                            </MultiBinding>
    
                        </Binding.Source>
    
                    </Binding>
    
                </DataGridTextColumn.Binding>
    
            </DataGridTextColumn>
    
        </DataGrid.Columns>
    
    </DataGrid>