9

Is it possible to have "column headers" on a combo box bound to multiple items? For example a combo box that displays a persons name. The combo box would display John Doe. But I'd like to display column headers:

First   Last
John    Doe
Jane    Doe
Jimmy   Doe

Is this possible without the use of a data grid? What about a simple solution that includes the use of a data grid? I found one solution for embedding a data grid into a combo box but it looks difficult and requires MS Blend.

I'd be happy if I could just get some headers as the first row in the drop down.

G

Here is my xaml code with HB's attempt that produces a compile error as mentioned in the comments.

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
<ComboBox Name="cboPlaceNames" Grid.IsSharedSizeScope="True" ItemsSource="{DynamicResource items}" Height="22" Width="285" Margin="0,6,165,0" SelectedIndex="0" HorizontalAlignment="Right" VerticalAlignment="Top" SelectionChanged="cboPlaceNames_SelectionChanged">
  <ComboBox.Resources>
    <CompositeCollection x:Key="items">
      <ComboBoxItem IsEnabled="False">
        <Grid TextElement.FontWeight="Bold">
          <Grid.ColumnDefinitions>
            <ColumnDefinition SharedSizeGroup="A"/>
            <ColumnDefinition Width="5"/>
            <ColumnDefinition SharedSizeGroup="B"/>
            <ColumnDefinition Width="5"/>
            <ColumnDefinition SharedSizeGroup="C"/>
          </Grid.ColumnDefinitions>
          <Grid.Children>
            <TextBlock Grid.Column="0" Text="Name"/>
            <TextBlock Grid.Column="2" Text="CLLI"/>
            <TextBlock Grid.Column="4" Text="Street"/>
          </Grid.Children>
        </Grid>
      </ComboBoxItem>
      <Separator/>
      <CollectionContainer Collection="{Binding Source={x:Reference cboPlaceNames}, Path=DataContext.Data}"/>
    </CompositeCollection>

    <DataTemplate DataType="x:Type obj:PlaceName">
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition SharedSizeGroup="A"/>
          <ColumnDefinition Width="5"/>
          <ColumnDefinition SharedSizeGroup="B"/>
          <ColumnDefinition Width="5"/>
          <ColumnDefinition SharedSizeGroup="C"/>
        </Grid.ColumnDefinitions>
        <Grid.Children>
          <TextBlock Grid.Column="0" Text="{Binding Name}"/>
          <TextBlock Grid.Column="2" Text="{Binding CLLI}"/>
          <TextBlock Grid.Column="4" Text="{Binding Street}"/>
        </Grid.Children>
      </Grid>
    </DataTemplate>
  </ComboBox.Resources>
</ComboBox>      
H.B.
  • 166,899
  • 29
  • 327
  • 400
GAR8
  • 293
  • 1
  • 5
  • 14

5 Answers5

16

Example:

<ComboBox Name="cb" Grid.IsSharedSizeScope="True" ItemsSource="{DynamicResource items}">
    <ComboBox.Resources>
        <CompositeCollection x:Key="items">
            <ComboBoxItem IsEnabled="False">
                <Grid TextElement.FontWeight="Bold">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="A"/>
                        <ColumnDefinition Width="5"/>
                        <ColumnDefinition SharedSizeGroup="B"/>
                    </Grid.ColumnDefinitions>
                    <Grid.Children>
                        <TextBlock Grid.Column="0" Text="Name"/>
                        <TextBlock Grid.Column="2" Text="Occupation"/>
                    </Grid.Children>
                </Grid>
            </ComboBoxItem>
            <Separator/>
            <CollectionContainer Collection="{Binding Source={x:Reference cb}, Path=DataContext.Data}"/>
        </CompositeCollection>

        <DataTemplate DataType="{x:Type obj:Employee}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition SharedSizeGroup="A"/>
                    <ColumnDefinition Width="5"/>
                    <ColumnDefinition SharedSizeGroup="B"/>
                </Grid.ColumnDefinitions>
                <Grid.Children>
                    <TextBlock Grid.Column="0" Text="{Binding Name}"/>
                    <TextBlock Grid.Column="2" Text="{Binding Occupation}"/>
                </Grid.Children>
            </Grid>
        </DataTemplate>
    </ComboBox.Resources>
</ComboBox>

Note that getting the Collection-binding right is not that easy because there is neither DataContext nor VisualTree to rely on, ElementName and RelativeSource does not work, this is because CompositeCollection is just a collection, not a FrameworkElement.

Other than that the way this is done is via Grids that have shared size columns. The DataTemplate is applied automatically via the DataType.

screenshot

Edit: Setting the header-ComboBoxItem's IsHitTestVisible property to False is not enough since it still can be selected using the keyboard. I now changed it to IsEnabled="False" which fades out the item a bit. You could probably re-template that item to not do that. Or if you find another way of disabling it from selection that would of course work out too.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • Very nice! I did not know about CompositeCollection before seeing this. If you also set Focusable="False" on the heading item it's not selectable and you should not need IsEnabled="False" – jschroedl Apr 27 '11 at 19:14
  • 1
    @jschroedl: Thank you; unfortunately focus does not matter for selection so this does not work. – H.B. Apr 27 '11 at 19:18
  • HB this looks very promising. Can you post the references you used. I'm getting an error regarding the use of "Collection="{Binding Source={x:Reference", Thank you, Greg – GAR8 Apr 28 '11 at 15:58
  • What kind of error? That `x:Reference` references the ComboBox by name. – H.B. Apr 28 '11 at 18:50
  • Error 1 The tag 'Reference' does not exist in XML namespace 'http://schemas.microsoft.com/winfx/2006/xaml'. Line 55 Position 32. – GAR8 Apr 28 '11 at 19:27
  • Are you using an older version of .NET? – H.B. Apr 28 '11 at 19:31
  • VS 2010 3.5 framework, posted xaml in my original question above. – GAR8 Apr 28 '11 at 19:34
  • Suspected as much, 3.5 does not have [`x:Reference`](http://msdn.microsoft.com/en-us/library/ee795380.aspx). I do not know of a way to create the composite collection in XAML in 3.5, you might need to set it up in code behind unless you find a way to reference the source without the VisualTree. (as i noted `ElementName` will not work and there is no `DataContext`) – H.B. Apr 28 '11 at 19:39
  • Okay, thanks again HB. I'm stuck with 3.5 due to third party constraints. – GAR8 Apr 28 '11 at 20:14
  • @H.B. I know that this post is very old. But If you can help then please read further. I get an error stating that Object reference not set to an instance of the object in Collection Property Of CollectionContainer Element. Can you explain the Collection Property in Detail? – Khushi Oct 26 '13 at 18:36
  • @Khushi: Read the documentation, it's just something you have to set yourself. You either did not set it or you set it to `null`. – H.B. Oct 27 '13 at 01:43
  • @H.B. Thanks for replying. I have read the documentation but just did not understand Path of Collection Property. It will be really helpful if you explain the path in somewhat detail it will be very much helpful. – Khushi Oct 27 '13 at 11:24
  • 1
    @Khushi: `Path` is a property of the `Binding` markup extension, and i will not explain it, it also has an extensive documentation along with [articles on binding](http://msdn.microsoft.com/en-us/library/ms752347.aspx). – H.B. Oct 27 '13 at 17:22
  • @H.B. Thanks for not giving me answer and giving a nice article on data Binding. From that I understood the four basic elements needed for data Binding in detail. Can you suggest me any good post explaining CollectionContainer and x:Reference in detail? And I learnt something from articles on binding that if you reference any control as a source then you need to use ElementName. But in the above comments you say that ElementName will not work here. Why so? – Khushi Oct 28 '13 at 15:42
  • @Khushi: It has to do with how UI elements form trees and how the binding system uses those trees to navigate. `CollectionContainer` is not a UI element and will not be in any tree, thus certain bindings will not work. `x:Reference` just references by name but works differently internally. And *like everything else it has its own documentation on MSDN*. – H.B. Oct 28 '13 at 16:09
  • @H.B. I have tried to understand the line of code for CollectionContainer that you used in the above code including msdn. But nowhere I find a good and deep tutorial or document on CollectionContainer. Please explain the use of Collection container for god's sake. – Khushi Nov 10 '13 at 21:00
  • @Khushi: Forget it, read MSDN and ask a separate, proper question if you don't get it, making sure to explain what you do not get. I will not answer questions or explain things in detail in the comments. – H.B. Nov 10 '13 at 21:21
  • If anybody has got a problem with binding CollectionContainer and getting error: Object Reference Not Set To an instance of the object please visit http://social.msdn.microsoft.com/Forums/vstudio/en-US/cc8fc068-5f3e-4179-be8b-b6b4c50803ff/how-to-use-collectioncontainer-as-itemssource?forum=wpf – Vishal Mar 01 '14 at 00:04
  • @H.B. Suppose I dont want to have the second column right aligned in the above combobox, then how can I achieve this? – Vishal Mar 01 '14 at 00:07
  • @Vishal: Give the spacing column in the middle `*` as `Width`. – H.B. Mar 01 '14 at 08:12
  • @H.B. I have tried it but I get first and second columns together and the space comes after the second column. – Vishal Mar 01 '14 at 09:41
  • @Vishal: Then the layout of the surrounding panel uses left content alignment instead of stretch. Try setting `HorizontalContentAlignment` to `Stretch` on the `ComboBox` (if this does not affect the drop-down you may need to change the `Template`). – H.B. Mar 01 '14 at 10:19
  • @H.B. I have got a small problem with the multi-column combobox with headers. SelectedIndex of this combobox always remains -1, no matter I select any item using keyboard or mouse. Can you tell me what is the exact problem and probably how to overcome this? – Vishal Jul 19 '14 at 19:55
  • @Vishal: No way to know without the code, i suggest you ask a separate question and provide it there. Probably someone else will be able to answer it, don't just rely on one person. – H.B. Jul 20 '14 at 00:23
  • Great! One thing: for whatever reason, in order to get the `Separator` to appear, I had to place it inside the `ComboBoxItem` after the `Grid` with a `StackPanel` wrapped around both. – dlf Feb 05 '16 at 13:51
2

The simplest way to add columns headers to combobox is to use listview in combobox. The following code is give the solution to it.

            <ComboBox HorizontalAlignment="Center" 
                      IsTextSearchEnabled="False" Width="200"                                            
                      IsEditable="True" Text="{Binding }"> 
                 <ListView ItemsSource="{Binding YOURITEMSOURCE}" 
                        SelectedItem="{Binding Path=SELECTEDITEMSOURCE}"
                         Height="200" ScrollViewer.VerticalScrollBarVisibility="Visible">                  
                           <ListView.View>
                               <GridView>
                                  <GridViewColumn Width="130"  Header="Name" DisplayMemberBinding="{Binding Name}"   />
                                  <GridViewColumn Width="130" Header="Occupation" DisplayMemberBinding="{Binding Occupation}" />
                                  <GridViewColumn Width="130"  Header="Age" DisplayMemberBinding="{Binding Age}" />
                                  <GridViewColumn Width="130" Header="Salary" DisplayMemberBinding="{Binding Salary}" />
                               </GridView>
                           </ListView.View>
                  </ListView>

            </ComboBox>
reVerse
  • 35,075
  • 22
  • 89
  • 84
Nandha kumar
  • 693
  • 7
  • 15
1

I liked Nandha's answer because this was what I was trying to achieve. However, the text field of the combo box will not work correctly.

Since I am lazy, to get around this I created a StackPanel of Horizontal orientation, with a TextBox bound to a field from the selected item, and the Combo Box with the embedded ListView. The Combo Box now has a width of 20 so just the down arrow shows (you can play with the width and margins to get just the right look. This makes the text box look like a combo box, with all the benefits of the ListView, and much less coding.

I hope this helps.enter image description here

0

Apply the following style to the ComboBox.

<Style x:Key="ListViewComboBox" TargetType="{x:Type ComboBox}">
    <Style.Resources>
        <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" 
                         Color="LightBlue"/>
    </Style.Resources>
    <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
    <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
    <Setter Property="BorderBrush" Value="Black"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="FontFamily" Value="Arial"/>
    <Setter Property="FontSize" Value="12"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="Margin" Value="0"/>
    <Setter Property="Padding" Value="0"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ComboBox}">
                <Border Background="{TemplateBinding Background}" 
                        BorderBrush="{TemplateBinding BorderBrush}" 
                        BorderThickness="{TemplateBinding BorderThickness}"
                        CornerRadius="3"
                        SnapsToDevicePixels="True">
                    <Grid>
                        <Border x:Name="Border">
                            <Popup x:Name="PART_Popup" AllowsTransparency="true" IsOpen="{TemplateBinding IsDropDownOpen}" Placement="Bottom" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Focusable="False">
                                <Border x:Name="Shdw" 
                                        MaxHeight="{TemplateBinding MaxDropDownHeight}" 
                                        MinWidth="{Binding ActualWidth, ElementName=Border}">
                                    <Border x:Name="DropDownBorder" 
                                            Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" 
                                            BorderBrush="{TemplateBinding BorderBrush}" 
                                            BorderThickness="1">
                                        <ListView KeyboardNavigation.DirectionalNavigation="Contained"
                                                  ItemsSource="{TemplateBinding ItemsSource}"
                                                  SelectedItem="{Binding Mode=TwoWay, Path=SelectedItem, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                                                  View="{TemplateBinding Tag}"/>
                                    </Border>
                                </Border>
                            </Popup>
                        </Border>
                        <DockPanel Margin="2">
                            <FrameworkElement DockPanel.Dock="Right"
                                              Width="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"/>
                            <Border x:Name="SelectedItemBorder" Margin="{TemplateBinding Padding}">
                                <Grid>
                                    <ContentPresenter Content="{TemplateBinding SelectionBoxItem}" 
                                                      ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" 
                                                      ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" 
                                                      ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
                                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                                      Margin="1,1,1,1" 
                                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                                </Grid>
                            </Border>
                        </DockPanel>
                        <ToggleButton x:Name="DropDownToggleButton" 
                                      ClickMode="Press"
                                      Focusable="false" 
                                      Foreground="{TemplateBinding BorderBrush}"
                                      IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                                      Margin="2"
                                      MinHeight="0" 
                                      MinWidth="0" 
                                      Width="Auto"/>
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelectionBoxHighlighted" Value="true"/>
                            <Condition Property="IsDropDownOpen" Value="false"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                    </MultiTrigger>
                    <Trigger Property="IsSelectionBoxHighlighted" Value="true">
                        <Setter Property="Background" TargetName="SelectedItemBorder" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                    </Trigger>
                    <Trigger Property="HasItems" Value="false">
                        <Setter Property="MinHeight" TargetName="DropDownBorder" Value="95"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                    </Trigger>
                    <Trigger Property="IsGrouping" Value="true">
                        <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                    </Trigger>
                    <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
                        <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/>
                    </Trigger>
                    <Trigger Property="IsReadOnly" Value="True">
                        <Setter Property="Visibility" TargetName="DropDownToggleButton" Value="Collapsed"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="IsEditable" Value="true">
            <Setter Property="IsTabStop" Value="false"/>
            <Setter Property="Padding" Value="1"/>
            <Setter Property="Template" Value="{StaticResource ComboBoxEditableTemplate}"/>
        </Trigger>
    </Style.Triggers>
</Style>

Apply the View you want to the Tag property of the ComboBox

<ComboBox ItemsSource={Binding Items}>
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <!-- Enter your item template shown as the selected item. -->
        </DataTemplate>
    </ComboBox.ItemTemplate>
    <ComboBox.Tag>
        <GridView>
            <GridViewColumn DisplayMemberBinding="{Binding Name}"
                            Header="Name" 
                            Width="100"/>
        </GridView>
    </ComboBox.Tag>
</ComboBox>

Thats all folks.

Muhammad Rehan Saeed
  • 35,627
  • 39
  • 202
  • 311
0

I like H.B.'s answer, but unfortunately when I use it I see databinding errors in the output for the header ComboBoxItem's HorizontalContentAlignment and VerticalContentAlignment properties:

Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is 'ComboBoxItem' (Name=''); target property is 'HorizontalContentAlignment' (type 'HorizontalAlignment')

These don't crash the program, but they do clutter up the output and cause perceptible delays when running debug builds. Whatever's causing them seems to be deep in the bowels of ComboBox or ComboBoxItem; in any case, I couldn't figure out any way to prevent them (setting these properties manually or via a Style didn't help). So I ended up going with a slight variation. This is longer and hackier than I like things to be, but it gets the job done:

<ComboBox Name="cb" Grid.IsSharedSizeScope="True" ItemsSource="{DynamicResource items}">
 <ComboBox.Resources>
    <!-- We'll use this dummy value to represent the header row. -->
    <!-- The type and value are arbitrary; we just need a unique type -->
    <!-- for DataTemplate selection to work with. -->
    <system:Int32 x:Key="HeaderPlaceholder">-1</system:Int32>

    <CompositeCollection x:Key="items">
      <StaticResource ResourceKey="HeaderPlaceholder" />
      <CollectionContainer Collection="{Binding Source={x:Reference cb},
         Path=DataContext.Data}"/>
    </CompositeCollection>

    <!-- DataTemplate for the header item -->
    <DataTemplate DataType="{x:Type system:Int32}">
      <DataTemplate.Resources>
        <!-- Make the TextBlocks black even though they are disabled -->
        <Style TargetType="TextBlock">
          <Style.Triggers>
            <Trigger Property="IsEnabled" Value="False">
              <Setter Property="Foreground" Value="Black" />
            </Trigger>
          </Style.Triggers>
        </Style>
      </DataTemplate.Resources>

      <StackPanel>
        <Grid TextElement.FontWeight="Bold">
          <Grid.ColumnDefinitions>
            <ColumnDefinition SharedSizeGroup="A"/>
            <ColumnDefinition Width="5"/>
            <ColumnDefinition SharedSizeGroup="B"/>
          </Grid.ColumnDefinitions>
          <Grid.Children>
              <TextBlock Grid.Column="0" Text="Name"/>
              <TextBlock Grid.Column="2" Text="Occupation"/>
          </Grid.Children>
        </Grid>
        <Separator />
      </StackPanel>
    </DataTemplate>

    <!-- DataTemplate for a normal, selectable item -->
    <DataTemplate DataType="{x:Type obj:Employee}">
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition SharedSizeGroup="A"/>
          <ColumnDefinition Width="5"/>
          <ColumnDefinition SharedSizeGroup="B"/>
        </Grid.ColumnDefinitions>
        <Grid.Children>
          <TextBlock Grid.Column="0" Text="{Binding Name}"/>
          <TextBlock Grid.Column="2" Text="{Binding Occupation}"/>
        </Grid.Children>
      </Grid>
    </DataTemplate>
  </ComboBox.Resources>

  <ComboBox.ItemContainerStyle>
    <!-- Make sure the header item is disabled so it can't be selected -->
    <Style TargetType="ComboBoxItem">
      <Style.Triggers>
        <Trigger Property="DataContext" Value="{StaticResource HeaderPlaceholder}">
          <Setter Property="IsEnabled" Value="False" />
        </Trigger>
      </Style.Triggers>
    </Style>
  </ComboBox.ItemContainerStyle>
</ComboBox>
Community
  • 1
  • 1
dlf
  • 9,045
  • 4
  • 32
  • 58
  • 1 year later and the error message is back, even with identical XAML. Don't understand it... :( – dlf Feb 22 '17 at 16:16