Search code examples
c#wpfmvvmwpfdatagrid

Passing DataGridComboBoxColumn value to a ObjectDataProvider's MethodParameter


I have two DataGridComboBoxColumns (technically DataGridTemplateColumns) in a DataGrid. I'm using MVVM.

The first column's ItemsSource is bound to a static resource -- no problem there.

The second column's ItemsSource is dependent on the value chosen by the first column. The first column's value (SelectedValue) is passed to an ObjectDataProvider as a MethodParameter. The ObjectDataProvider is the second column's ItemsSource.

The problem with using the first column's SelectedValue as ObjectDataProvider's MethodParameter is when I insert a second row in the DataGrid. If the second row uses a different value in column 1 than it did in the first row's column 1, it clears the first row's column 2 value (as the new SelectedValue changes the allowable list of items to choose from, provided by the ObjectDataProvider).

I'd really like to pass in the first column's Text value to the ObjectDataProvider as its MethodParameter, but how can I do so when the first column's Text value is already bound to update my model?

Here's excerpts of my problem XAML:

<!--
My ObjectDataProvider. It returns a collection of strings for the user to choose from via the second DataGridTemplateColumn.
The first DataGridTemplateColumn feeds ObjectDataProvider a MethodParameter. 

The method is simple. It looks like:
    public List<String> ProductLineCategoryList_CategoryCodes(string productLineCode)
    {
        // return a list of strings based from an object collection, filtered by the passed in argument.
    }
-->

<ObjectDataProvider x:Key="categoryCodes" ObjectType="{x:Type e:ItemsProvider}" MethodName="ProductLineCategoryList_CategoryCodes">
    <ObjectDataProvider.MethodParameters>
        <x:StaticExtension Member="sys:String.Empty"/>
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<!-- 
This DataGridComboBoxColumn lets the user choose a ProductLineCode.
Its SelectedValue provides a string value for the ObjectDataProvider's MethodParameter.
The ObjectDataProvider is used as the ItemsSource for the DataGridComboBoxColumn
below this one.
The problem with using SelectedValue to feed ObjectDataProvider a MethodParameter, when
a second row is added to my DataGrid and the second row uses a different ProductLineCode than
the first row, it clears the first row's ProductLineCategoryCode value.
-->
<DataGridTemplateColumn 
    Header="Product Line"
    ClipboardContentBinding="{Binding ProductLineCode}">
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ComboBox
                IsEditable="True"
                ItemsSource="{x:Static e:ItemsProvider.ProductLineCategoryList_ProductLineCodeList}"
                SelectedValue="{Binding Source={StaticResource categoryCodes}, 
                                Path=MethodParameters[0], BindsDirectlyToSource=True, 
                                UpdateSourceTrigger=PropertyChanged}"
                Text="{Binding ProductLineCode, UpdateSourceTrigger=PropertyChanged}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>                                               
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ProductLineCode}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>              
</DataGridTemplateColumn>

<!-- 
This DataGridComboBoxColumn uses the ObjectDataProvider for its ItemsSource.
ItemsSource s/b limited by the selection made from the above DataGridComboBoxColumn.
-->
<DataGridTemplateColumn 
    Header="Product Line Cat"
    ClipboardContentBinding="{Binding ProductLineCategoryCode}">
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ComboBox 
                IsEditable="True"
                ItemsSource="{Binding Source={StaticResource categoryCodes}}"
                Text="{Binding ProductLineCategoryCode, UpdateSourceTrigger=PropertyChanged}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>                                               
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding ProductLineCategoryCode}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>              
</DataGridTemplateColumn>                                           

I've referenced the net for help, but I can't find a solution that works for me. I only need to pass in a string, not an object (though I guess I could change my ObjectDataProvider's method to accept an object and then this might work). This MSDN solution would work great if this weren't a DataGrid.


Solution

  • IMHO you're trying to do too many things in the XAML.

    You'd better leverage your VM.

    Here is an example that mimics your model and situation:

    The business/VM entity:

    public class Product : INotifyPropertyChanged
    {
        private static readonly IDictionary<string, string[]> catalog = new Dictionary<string, string[]>
        {
            { "Fruit", new[]{ "Apple", "Banana", "Cherry" } },
            { "Vegatable", new[]{ "Amaranth", "Broccolini", "Celery" } }
        };
    
        public static IDictionary<string, string[]> Catalog { get { return catalog; } }
    
        private string productLineCategoryCode;
        public string ProductLineCategoryCode
        {
            get { return productLineCategoryCode; }
            set
            {
                if (value != productLineCategoryCode)
                {
                    productLineCategoryCode = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("ProductLineCategoryCode"));
                    PropertyChanged(this, new PropertyChangedEventArgs("ProductLineCodes"));
                }
            }
        }
    
        public IEnumerable<string> ProductLineCodes
        {
            get
            {
                return Catalog[ProductLineCategoryCode];
            }
        }
    
        private string productLineCode;
        public string ProductLineCode
        {
            get { return productLineCode; }
            set
            {
                if (value != productLineCode)
                {
                    productLineCode = value;
                    PropertyChanged(this, new PropertyChangedEventArgs("ProductLineCode"));
                }
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
    }
    

    The ProductLineCodes is the list of available codes for a given category, you simply bind to it.

    So when the user changes the category we notify that both it and the list of available codes have changed.

    The view:

    <DataGrid ItemsSource="{Binding Products}" CanUserAddRows="True" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Product Line Cat"
                                    ClipboardContentBinding="{Binding ProductLineCategoryCode}">
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <ComboBox IsEditable="True"
                                    ItemsSource="{Binding Path=(local:Product.Catalog).Keys}"
                                    SelectedValue="{Binding ProductLineCategoryCode, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding ProductLineCategoryCode}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTemplateColumn Header="Product Line" ClipboardContentBinding="{Binding ProductLineCode}">
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <ComboBox IsEditable="True"
                                    ItemsSource="{Binding ProductLineCodes}"
                                    SelectedValue="{Binding ProductLineCode,UpdateSourceTrigger=PropertyChanged}">                                
                        </ComboBox>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding ProductLineCode}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
    

    I can imagine another variant using an IValueConverter but I hope this one will be good enough for your use-case...