Pergunta

I have a datagrid with 2 DataGridComboxClolumn elements and want to change the ItemsSource of the second when the value of the first is changed. How can I set up such a binding?

Note: the datagrid has it's own itemssource and the data is hierachical following:

ItemsSource of the datagrid (list op type Channel):

public class Root
{
    public List<Channel> Ch { get; set; }  // This is the ItemsSource of the datagrid itself
}


public class Channel
{
    public Settings TheSettings { get; set; }
}

The thing I want to accomplish is that the TheSettings is set with selected value of the second ComboBox. This is easyly done by setting the ComboBox ItemsSource. Although I require that the ItemsSource of the second combox to be dynamic. E.g. it has to change to the source selected in the FIRST combobox. How can this be done?

Foi útil?

Solução

Option 1

You can create your a DataGridTemplateColumn with two combobox. The first one can be populated with items from the main ViewModel and the second one will be bound to the items of the SelectedItem of the first.

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:local="clr-namespace:WpfApplication1"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="525"
    Height="350"
    mc:Ignorable="d">
<Window.DataContext>
    <local:Root/>
</Window.DataContext>
<Grid x:Name="LayoutRoot">
    <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Ch}" CanUserAddRows="False">
        <DataGrid.Columns>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <ComboBox  Width="100" ItemsSource="{Binding DataContext.ComboBox1Items, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" x:Name="ComboBox1"/>
                        <ComboBox Grid.Column="1" Width="100" ItemsSource="{Binding SelectedItem.Items, ElementName=ComboBox1}" SelectedItem="{Binding TheSettings}" IsEditable="True" IsReadOnly="True"/>
                        </Grid>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

</Grid>
</Window>

public class ComboBox1Item
{
    public string Label { get; set; }
    public List<string> Items { get; set; }

    public override string ToString()
    {
        return this.Label;
    }
}

public class Root
{
    public List<Channel> Ch { get; set; }

    public List<ComboBox1Item> ComboBox1Items { get; set; }

    public Root()
    {
        this.Ch = new List<Channel>(){
            new Channel(){ TheSettings = "Settings1"},
            new Channel(){ TheSettings = "Settings2"},
        };

        this.ComboBox1Items = new List<ComboBox1Item>{
            new ComboBox1Item(){ Label = "Item1",
                Items = new List<string>(){ "Settings1", "Settings2"}
            },
            new ComboBox1Item(){ Label = "Item2",
                Items = new List<string>(){ "Settings3", "Settings4"}
            }
        };
    }
}

Option 2

Create an object to wrap your Channel objects, and put in it the logic to allow one combobox to drive the items of the other:

public class ChannelWrapper : INotifyPropertyChanged
{
    #region INotifyPropertyChanged values

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion


    private object comboBox1SelectedItem;
    public object ComboBox1SelectedItem
    {
        get { return this.comboBox1SelectedItem; }
        set
        {
            if (this.comboBox1SelectedItem != value)
            {
                this.comboBox1SelectedItem = value;
                this.OnPropertyChanged("ComboBox1SelectedItem");

                // Put the logic to change the items available in the second combobox here
                if (value == "Value1")
                    this.ComboBox2ItemsSource = new List<object>() { "Setting1", "Setting2" };
                if (value == "Value2")
                    this.ComboBox2ItemsSource = new List<object>() { "Setting3", "Setting4" };
            }
        }
    }

    private List<object> comboBox2ItemsSource;
    public List<object> ComboBox2ItemsSource
    {
        get { return this.comboBox2ItemsSource; }
        set
        {
            if (this.comboBox2ItemsSource != value)
            {
                this.comboBox2ItemsSource = value;
                this.OnPropertyChanged("ComboBox2ItemsSource");
            }
        }
    }

    public Channel Ch { get; set; }
}

Your Root class would then expose a collection of wrappers instead a collection of channels. Your DataGrid would have 2 ComboBoxColumns. The SelectedItem of the first would be bound to the property "ComboBox1SelectedItem" of the wrapper. The ItemsSource of the second would be bound to the property "ComboBox2ItemsSource" of the wrapper and the SelectedItem of the second column would be bound the setting of the Channel instance of the wrapper, with the path "Ch.TheSettting".

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top