Reputation: 293
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>
Upvotes: 9
Views: 15122
Reputation: 11
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
Upvotes: 1
Reputation: 9383
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>
Upvotes: 0
Reputation: 763
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>
Upvotes: 2
Reputation: 38457
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.
Upvotes: 0
Reputation: 184516
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
.
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.
Upvotes: 16