1
votes

I'm attempting to set the column combobox values based on the row's (object) individual collection value so that each row has a different list of options based on another value.

For example.

Column 1: Country combobox - e.g. UK selected from { "UK", "USA", "France" }

Column 2: City combobox - e.g. LA selected { "New York", "LA", "Texas" } (Based on USA selection of previous)

Below is my example data object

public class ExampleData
{
    public List<string> countries { get; set; }
    public List<string> cities { get; set; }

    public string country { get; set; }
    public string city { get; set; }

    public ExampleData(string country)
    {
        this.country = country;
        countries = new List<string>() { "UK", "USA", "France" };
    }

    // Example update method to change dependant options
    public void UpdateOptions()
    {
        if (country == "UK")
        {
            cities = new List<string>() { "London", "Bristol", "Birmingham" };
        }
        else if (country == "USA")
        {
            cities = new List<string>() { "New York", "LA", "Texas" };
        }
        else if (country == "France")
        {
            cities = new List<string>() { "Paris", "Lyon", "Nice" };
        }
        else
        {
            cities = new List<string>();
        }
    }
}

I would then create an example collection of the data, to be displayed within the datagrid

 public void TestScenario()
 {
     ExampleDataCollection = new List<ExampleData>();

     exampleDataList.Add(new ExampleData("UK"));
     exampleDataList.Add(new ExampleData("USA"));
     exampleDataList.Add(new ExampleData("France"));

     DataGrid.ItemsSource = ExampleDataCollection ;
  }

Then within the xaml, I would have the object collection bound to the datagrid as the ItemsSource, while each column combobox would be bound to the objects own collection which will be independent per row (object). This would be something like ExampleData.countries, with ExampleData.Country being the selected value.

<DataGrid
    Name="ExampleDataGrid"
    AutoGenerateColumns="False"
    ItemsSource="{Binding ExampleDataCollection}"
    >
    <DataGrid.Columns>
        <DataGridComboBoxColumn 
            Header="country" 
            ItemsSource="{Binding ExampleData.countries}"
            SelectedValueBinding="{Binding ExampleData.country}" 
            />
        <DataGridComboBoxColumn 
            Header="city" 
            ItemsSource="{Binding ExampleData.cities}"
            SelectedValueBinding="{Binding ExampleData.city}" 
            />
    </DataGrid.Columns>
</DataGrid>

Is this actually feasible to achieve, or should an alternative method be applied to this situation? Logically it is not that complex of a process, however I have been unable to implement a solution.

1

1 Answers

1
votes

In classic form, I have solved the issue shortly after almost giving up!

The solution was to bind the object collection to the datagrid within the main window class, then bind the individual properties of country, countries, city and cities. This also included the addition of update triggers for the dependant dropdowns so that an update method in the class could be called.

<DataGrid
    Name="ExampleDataGrid"
    AutoGenerateColumns="False"
    >
    <DataGrid.Columns>

        <DataGridTemplateColumn Header="Country">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding countries}" SelectedItem="{Binding Country, UpdateSourceTrigger=PropertyChanged}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="city">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding cities}" SelectedItem="{Binding City, UpdateSourceTrigger=PropertyChanged}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        
    </DataGrid.Columns>
</DataGrid>

The string lists of countries and cities was updated to observable collections, so that the combobox new to update them when changed. The addition of INotifyPropertyChanged meant that when the country or city was changed within from the dropdown selection bound to it, it called the OnPropertyChanged method.

This then allowed me to call another method to update the new lists of cities. It must be noted that you cannot simply just replace the existing collection with a new collection through equals. It must have a change associated with it such as addition or removal (not clear) to actually update. See below.

public class ExampleData : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<string> countries { get; set; }
    public ObservableCollection<string> cities { get; set; }
    public ObservableCollection<string> towns { get; set; }

    private string country;
    private string city;
    private string town;
    
    public string Country
    {
        get
        {
            return country;
        }
        set
        {
            country = value;
            // Call OnPropertyChanged whenever the property is updated
            OnPropertyChanged("Country");
        }
    }
    public string City
    {
        get
        {
            return city;
        }
        set
        {
            city = value;
        }
    }
    public string Town { get; set; }

    public ExampleData(string country)
    {
        this.country = country;
        countries = new ObservableCollection<string>() { "UK", "USA", "France" };
        cities = new ObservableCollection<string>() { "London", "Bristol", "Plymouth" };
    }

    // Create the OnPropertyChanged method to raise the event 
    protected void OnPropertyChanged(string name)
    {
        UpdateOptions();

        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
            
        }
    }

    public void UpdateOptions()
    {
        List<string> oldCities = cities.ToList<string>();

        List<string> newCities;

        foreach (string city in oldCities)
        {
            cities.Remove(city);
        }

        if (country == "UK")
        {
            newCities = new List<string>() { "London", "Bristol", "Birmingham" };
        }
        else if (country == "USA")
        {
            newCities = new List<string>() { "New York", "LA", "Texas" };
        }
        else if (country == "France")
        {
            newCities = new List<string>() { "Paris", "Lyon", "Nice" };
        }
        else
        {
            newCities = new List<string>();
        }

        foreach (string city in newCities)
        {
            cities.Add(city);
        }
    }
}

No change was made to the main window code, and it remains the same as below.

public partial class MainWindow : Window
{
    public List<ExampleData> exampleDataList;

    public MainWindow()
    {
        InitializeComponent();
        TestScenario();
    }

    public void TestScenario()
    {
        exampleDataList = new List<ExampleData>();

        exampleDataList.Add(new ExampleData("UK"));
        exampleDataList.Add(new ExampleData("USA"));
        exampleDataList.Add(new ExampleData("France"));
        ExampleDataGrid.ItemsSource = exampleDataList;
    }
}