Home > front end >  WPF XAML Comboboxes Master Detail
WPF XAML Comboboxes Master Detail

Time:01-16

I'm new to WPF and have a obviously very simple or basic problem which I failed to solve even though I have read for hours to find a solution. Here it is: I have two Comboboxes, a master and a detail combobox. The master has two entries: "Option 1" and "Option 2". The detail combobox should have two entries "Value 1" and "Value 2" when "Option 1" is selected or "Value A" and "Value B" when "Option 2" is selected in the master combobox. I think that should be possible with pure XAML.

Basically I tried to assign a dictionary collection to each of the comboboxes and tried to reference (bind) the master list via the "Key". Following is my xaml-code so far (I have withdrawn all my bind-attempts)

<Window x:Class="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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Test"
        mc:Ignorable="d"
        xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
        Title="MainWindow" Height="221" Width="457">
    <Window.Resources>
        <col:ArrayList x:Key="masterlist">
            <col:DictionaryEntry Key="option1" Value="Option one"/>
            <col:DictionaryEntry Key="option2" Value="Option two" />
        </col:ArrayList>

        <col:ArrayList x:Key="detaillist">
            <col:DictionaryEntry Key="option1" Value="Value 1"/>
            <col:DictionaryEntry Key="option1" Value="Value 2"/>
            <col:DictionaryEntry Key="option2" Value="Value A"/>
            <col:DictionaryEntry Key="option2" Value="Value B"/>
        </col:ArrayList>
    </Window.Resources>

    <Grid Margin="0,0,0,29">
        <ComboBox x:Name="ComboboxMaster" HorizontalAlignment="Left" Margin="33,39,0,0" VerticalAlignment="Top" Width="120"
            ItemsSource ="{StaticResource masterlist}"
            DisplayMemberPath="Value"
            SelectedValuePath="Key"
            IsSynchronizedWithCurrentItem="True"
            SelectedIndex="0">

        </ComboBox>
        <ComboBox x:Name="ComboboxDetail" HorizontalAlignment="Center" Margin="0,39,0,0" VerticalAlignment="Top" Width="120"
            ItemsSource ="{StaticResource detaillist}"
            DisplayMemberPath="Value"
            SelectedValuePath="Key"
            SelectedIndex="0">
        </ComboBox>
        <Label x:Name="labelMasterKey" HorizontalAlignment="Left" Margin="33,75,0,0" VerticalAlignment="Top" Width="120"
               Content="{Binding ElementName=ComboboxMaster, Path=SelectedValue}"/>
        <Button Content="Close" HorizontalAlignment="Left" Margin="33,123,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.071,-0.349" Click="Button_Click" Height="26" Width="64"/>
    </Grid>
</Window>

Your help for a dummy like me is appreciated

CodePudding user response:

Split the detail list into two lists and assign the appropriate one in the Setter of a DataTrigger on the SelectedValue of the master ComboBox:

<Window.Resources>
    <col:ArrayList x:Key="masterlist">
        <col:DictionaryEntry Key="option1" Value="Option one"/>
        <col:DictionaryEntry Key="option2" Value="Option two" />
    </col:ArrayList>

    <col:ArrayList x:Key="detaillist1">
        <col:DictionaryEntry Key="option1" Value="Value 1"/>
        <col:DictionaryEntry Key="option2" Value="Value 2"/>
    </col:ArrayList>

    <col:ArrayList x:Key="detaillist2">
        <col:DictionaryEntry Key="option1" Value="Value A"/>
        <col:DictionaryEntry Key="option2" Value="Value B"/>
    </col:ArrayList>
</Window.Resources>

<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
    <ComboBox x:Name="ComboboxMaster" Grid.Column="0" Width="120" Margin="20"
              ItemsSource="{StaticResource masterlist}"
              DisplayMemberPath="Value"
              SelectedValuePath="Key"
              SelectedIndex="0"/>

    <ComboBox x:Name="ComboboxDetail" Grid.Column="1" Width="120" Margin="20"
              DisplayMemberPath="Value"
              SelectedValuePath="Key">
        <ComboBox.Style>
            <Style TargetType="ComboBox">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding SelectedValue,
                                                   ElementName=ComboboxMaster}"
                                 Value="option1">
                        <Setter Property="ItemsSource"
                                Value="{StaticResource detaillist1}"/>
                        <Setter Property="SelectedValue"
                                Value="option1"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding SelectedValue,
                                                   ElementName=ComboboxMaster}"
                                 Value="option2">
                        <Setter Property="ItemsSource"
                                Value="{StaticResource detaillist2}"/>
                        <Setter Property="SelectedValue"
                                Value="option1"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ComboBox.Style>
    </ComboBox>
</StackPanel>

CodePudding user response:

Another possibility would be using the MVVM pattern. THis brings you to more flexibility with your data and seperating the data from your view.

First you have to create a new ViewModel class like this:

public class MainWindowViewModel : INotifyPropertyChanged
{
    private Dictionary<string, List<string>> _comboboxData;
    private string _selectedItem1;
    private string _selectedItem2;

    public event PropertyChangedEventHandler PropertyChanged;

    public MainWindowViewModel()
    {
        _comboboxData = new Dictionary<string, List<string>>
        {
            {"option1", new List<string>{"Value 1", "Value 2"}},
            {"option2", new List<string>{"Value A", "Value B"}},
        };
        _selectedItem1 = "option1";
        _selectedItem2 = _comboboxData[SelectedItem1][0];
    }

    public string SelectedItem1
    {
        get => _selectedItem1;
        set
        {
            if (value == _selectedItem1) return;
            _selectedItem1 = value;
            RaisePropertyChanged();
            RaisePropertyChanged(nameof(Combobox2Data));
            SelectedItem2 = _comboboxData[SelectedItem1][0]
        }
    }

    public string SelectedItem2
    {
        get => _selectedItem2;
        set
        {
            if (value == _selectedItem2) return;
            _selectedItem2 = value;
            RaisePropertyChanged();
        }
    }

    public ObservableCollection<string> Combobox1Data => new ObservableCollection<string>(_comboboxData.Keys);

    public ObservableCollection<string> Combobox2Data =>
        new ObservableCollection<string>(_comboboxData[_selectedItem1]);

    private void RaisePropertyChanged([CallerMemberName]string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

with this your WPF MainWindow.xaml will look like this:

<Window x:Class="WpfTest.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid Margin="0,0,0,29">
        <ComboBox x:Name="ComboboxMaster" HorizontalAlignment="Left" Margin="33,39,0,0" VerticalAlignment="Top" Width="120"
                  ItemsSource ="{Binding Combobox1Data}"
                  SelectedItem="{Binding SelectedItem1}">
        </ComboBox>
        <ComboBox x:Name="ComboboxDetail" HorizontalAlignment="Center" Margin="0,39,0,0" VerticalAlignment="Top" Width="120"
                  ItemsSource="{Binding Combobox2Data}"
                  SelectedItem="{Binding SelectedItem2}">
        </ComboBox>
        <Label x:Name="labelMasterKey" HorizontalAlignment="Left" Margin="33,75,0,0" VerticalAlignment="Top" Width="120"
               Content="{Binding SelectedItem2}"/>
        <Button Content="Close" HorizontalAlignment="Left" Margin="33,123,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.071,-0.349" Height="26" Width="64"/>
    </Grid>
</Window> 

With this you may change your data dictionary with more values e.g. for ComboBox 1 (Keys) or more values for each keyed List (Values) only at beginning of ViewModel class without other changes anywhere

  • Related