Search code examples
c#wpfcomboboxcompositecollection

WPF Combobox w/ Observable Collection in Composite Collection


I'm trying to populate a combobox with an observable collection and a header row using a composite collection. The header is there but I can't get the OC objects to populate. I'm just starting to use OC's so might be something basic I'm missing.

Customer class:

public class Customer : ViewModelBaseMain
{
    public string CustomerName { get; set; }
    public int CustomerId { get; set; }
}

I populate the OC, which is a dependency property, from a datatable (I verified that the OC is being populated correctly):

ObservableCollection<Customer> dpCustomerListOC = new ObservableCollection<Customer>();
foreach (DataRow drow in dpHeaderCustTable.Rows)
{
    Customer Customer = new Customer();
    Customer.CustomerId = Convert.ToInt32(drow["customer_id"]);
    Customer.CustomerName = drow["customer_name"].ToString();
    dpCustomerListOC.Add(Customer);                            
}

In the usercontrol resources, I specify the VM as a static resource:

<local:ReservationViewModel x:Key="ReserveVM"/>

I've tried two different ways from posts I've read.

First way w/ collection in the resources using x:reference:

<ComboBox x:Name="cboCustHeader" Grid.IsSharedSizeScope="True" IsEditable="False"
          ItemsSource="{DynamicResource items}">                                  
     <ComboBox.Resources>
         <CompositeCollection x:Key="items">
              <ComboBoxItem IsEnabled="False">
                  <Border Style="{StaticResource ComboHeaderBorder}">
                     <Grid Style="{StaticResource ComboHeaderStyle}">
                         <Grid.ColumnDefinitions>
                             <ColumnDefinition SharedSizeGroup="A"/>
                             <ColumnDefinition Width="7"/>
                             <ColumnDefinition SharedSizeGroup="B"/>
                         </Grid.ColumnDefinitions>
                         <Grid.Children>
                            <TextBlock Grid.Column="0" Text="ID"/>
                            <TextBlock Grid.Column="2" Text="Name"/>
                         </Grid.Children>
                     </Grid>
                  </Border>
              </ComboBoxItem>
              <CollectionContainer Collection="{Binding Source={x:Reference cboCustHeader}, Path=DataContext.dpCustomerListOC}"/>
         </CompositeCollection>
     </ComboBox.Resources>                               
     <ComboBox.ItemTemplate>
          <DataTemplate>
              <Grid>
                  <Grid.ColumnDefinitions>
                      <ColumnDefinition SharedSizeGroup="A"/>
                      <ColumnDefinition Width="7"/>
                      <ColumnDefinition SharedSizeGroup="B"/>
                  </Grid.ColumnDefinitions>
                  <Grid.Children>
                     <TextBlock Text="{Binding Path=CustomerId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                     <TextBlock Text="{Binding Path=CustomerName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                  </Grid.Children>
              </Grid>
          </DataTemplate>
     </ComboBox.ItemTemplate>
</ComboBox>

Second way specifying the source:

<ComboBox x:Name="cboCustHeader" Grid.IsSharedSizeScope="True">
    <ComboBox.ItemsSource>
        <CompositeCollection>
            <ComboBoxItem IsEnabled="False">
               <Border Style="{StaticResource ComboHeaderBorder}">
                   <Grid Style="{StaticResource ComboHeaderStyle}">
                       <Grid.ColumnDefinitions>
                           <ColumnDefinition SharedSizeGroup="A"/>
                           <ColumnDefinition Width="7"/>
                           <ColumnDefinition SharedSizeGroup="B"/>
                       </Grid.ColumnDefinitions>
                       <Grid.Children>
                           <TextBlock Grid.Column="0" Text="ID"/>
                           <TextBlock Grid.Column="2" Text="Name"/>
                       </Grid.Children>
                   </Grid>
               </Border>
           </ComboBoxItem>
           <CollectionContainer Collection="{Binding Path=dpCustomerListOC, Source={StaticResource ReserveVM}}"/>                                                                      
      </CompositeCollection>
  </ComboBox.ItemsSource>
  <ComboBox.ItemTemplate>
      <DataTemplate>
          <Grid>
             <Grid.ColumnDefinitions>
                 <ColumnDefinition SharedSizeGroup="A"/>
                 <ColumnDefinition Width="7"/>
                 <ColumnDefinition SharedSizeGroup="B"/>
            </Grid.ColumnDefinitions>
            <Grid.Children>
                <TextBlock Grid.Column="0" Text="{Binding Path=CustomerId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                <TextBlock Grid.Column="2" Text="{Binding Path=CustomerName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
            </Grid.Children>
          </Grid>
      </DataTemplate>
  </ComboBox.ItemTemplate>                        


Solution

  • Add a public property exposing the collection on your view model:

    public ObservableCollection<Customer> DpCustomerListOC 
    { 
        get { return dpCustomerListOC; }
    }
    

    And bind the combo's ItemsSource to it:

        <ComboBox x:Name="cboCustHeader" Grid.IsSharedSizeScope="True">
            <ComboBox.ItemsSource>
                <CompositeCollection>
                    <ComboBoxItem IsEnabled="False">
                        <Border Style="{StaticResource ComboHeaderBorder}">
                            <Grid Style="{StaticResource ComboHeaderStyle}">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition SharedSizeGroup="A"/>
                                    <ColumnDefinition Width="7"/>
                                    <ColumnDefinition SharedSizeGroup="B"/>
                                </Grid.ColumnDefinitions>
                                <Grid.Children>
                                    <TextBlock Grid.Column="0" Text="ID"/>
                                    <TextBlock Grid.Column="2" Text="Name"/>
                                </Grid.Children>
                            </Grid>
                        </Border>
                    </ComboBoxItem>
                    <CollectionContainer Collection="{Binding Path=DpCustomerListOC, Source={StaticResource ReserveVM}}"/>
                </CompositeCollection>
            </ComboBox.ItemsSource>
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition SharedSizeGroup="A"/>
                            <ColumnDefinition Width="7"/>
                            <ColumnDefinition SharedSizeGroup="B"/>
                        </Grid.ColumnDefinitions>
                        <Grid.Children>
                            <TextBlock Grid.Column="0" Text="{Binding Path=CustomerId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                            <TextBlock Grid.Column="2" Text="{Binding Path=CustomerName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                        </Grid.Children>
                    </Grid>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    

    Make sure the collection is populated in the constructor.