Search code examples
c#wpfcomboboxdatatriggeritemssource

Combobox with two itemssources


I'm trying to make a list of either customers or suppliers based on a radio button. When I bind one list to a combobox it works. When I try to use a datatrigger to dynamically change the collection to display, nothing happens.

Since the seperate list work with the non-changing combobox, I already know it's not a datacontext type of problem. I also have installed Fody Weaver to automatically make INotifyProperties.

The XAML:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>

    <RadioButton Content="Klant"
                 Grid.Column="0"
                 Foreground="White"
                 Margin="10"
                 IsChecked="{Binding ButtonCustomerIsChecked}"
                 GroupName="CustomerSupplier"
                 Name="RadioButtonCustomer"
                 />
    <RadioButton Content="Leverancier"
                 Grid.Column="1"
                 Foreground="White"
                 Margin="10"
                 IsChecked="{Binding ButtonSupplierIsChecked}"
                 GroupName="CustomerSupplier"
                 Name="RadioButtonSupplier"
                 />
</Grid>

<ComboBox ItemsSource="{Binding SupplierList}"
          DisplayMemberPath="Name"
          SelectedItem="{Binding Path=SelectedCustomerSupplier}"
          Style="{StaticResource InputComboBox}"
          />

<ComboBox Style="{StaticResource InputComboBox}"
          ItemsSource="{Binding}"
          DisplayMemberPath="Name"
          >
    <Style TargetType="{x:Type ComboBox}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsChecked, ElementName=ButtonCustomerIsChecked, diag:PresentationTraceSources.TraceLevel=High}"  Value="True">
                <Setter Property="ItemsSource" Value="{Binding CustomerList}"/>
            </DataTrigger>

            <DataTrigger Binding="{Binding IsChecked, ElementName=ButtonSupplierIsChecked, diag:PresentationTraceSources.TraceLevel=High}" Value="True">
                <Setter Property="ItemsSource" Value="{Binding SupplierList}"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</ComboBox>

The C# (ViewModel):

CustomerList = new ObservableCollection<Customer>
{
    new Customer{Id=1, Name="Bart"},
    new Customer{Id=1, Name="Nick"},
    new Customer{Id=1, Name="Erwin"},
};

SupplierList = new ObservableCollection<Customer>
{
    new Customer{Id=1, Name="Rita"},
    new Customer{Id=1, Name="Sascha"},
    new Customer{Id=1, Name="Didier"},
};

More C# (Customer class):

public class Customer : IStakeholder
{
    /// <summary>
    /// The ID of the customer
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// The name of the customer
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// The phone number
    /// </summary>
    public string PhNr { get; set; }

    /// <summary>
    /// The VAT number
    /// can be null
    /// </summary>
    public string VatNr { get; set; }

    /// <summary>
    /// The country where the customer is located
    /// </summary>
    public CountryCat CountryCat { get; set; }

    /// <summary>
    /// A list of payments made by the customer
    /// </summary>
    public List<IPayments> Payments { get; set; }
}

Solution

  • Instead of

    <ComboBox ItemsSource="{Binding SupplierList}" ...>
        <ComboBox.Style>
            <Style TargetType="{x:Type ComboBox}">
                <Style.Triggers>
                    <DataTrigger ...>
                        <Setter Property="ItemsSource" Value="{Binding CustomerList}"/>
                    </DataTrigger>
                </Style.Triggers>
            <Style>
        <ComboBox.Style>
    </ComboBox>
    

    You have to move initial assignment of ItemsSource into style setters like this:

    <ComboBox ...>
        <ComboBox.Style>
            <Style TargetType="{x:Type ComboBox}">
                <Style.Setters>
                    <Setter Property="ItemsSource" Value="{Binding SupplierList}" />
                </Style.Setters>
                <Style.Triggers>
                    <DataTrigger ...>
                        <Setter Property="ItemsSource" Value="{Binding CustomerList}"/>
                    </DataTrigger>
                </Style.Triggers>
            <Style>
        <ComboBox.Style>
    </ComboBox>
    

    Otherwise style trigger can't override local value, see dependency property value precedence.


    Another problem is what in style you are binding to specific element in the view, this makes it less reusable, you see what you have to create as many data triggers as many radio-buttons you have. Also you are using wrong ElementName in bindings there, e.g. it's not ButtonCustomerIsChecked, should be RadioButtonCustomer (same for suppliers).

    Here is a fix, you need only one trigger:

     <Style.Triggers>
         <DataTrigger Binding="{Binding IsChecked, ElementName=RadioButtonCustomer}" Value="True">
             <Setter Property="ItemsSource" Value="{Binding CustomerList}"/>
         </DataTrigger>
     </Style.Triggers>
    

    The better approach is to provide a property in ViewModel (it's MVVM term, viewmodel is a class where you have CustomerList defined) which will be used to run trigger.

    Or even easier is to to change single property which is bound to ItemsSource in viewmodel, this way you don't need triggers in the view.