WPF DataGrid as ComboBox Dropdown

By Michael Detras

This article describes how to display the items of a WPF ComboBox inside a DataGrid when the ComboBox is clicked. It also shows how to make the behavior same as that of a normal ComboBox, like clicking the item on the DataGrid should close the ComboBox's popup.

WPF DataGrid as ComboBox Dropdown

I had a WPF application wherein I want to display a list of objects (ex. customers, suppliers) in a ComboBox. What I want is to show many information at once, like name, address, telephone number, etc. This can be achieved by changing the ItemTemplate of the ComboBox. However, I want to display the properties of an item as columns and the items as rows. Fortunately, I found out about the WPF DataGrid. I decided to use the DataGrid to show the items when the ComboBox is clicked.

First things first. Since the WPF DataGrid is not officially released yet at this time, so you need to download the latest WPFToolkit binaries from CodePlex - http://wpf.codeplex.com/. The DataGrid is part of the WPFToolkit library. The library has other useful controls as well.  After installing the library you can already see the added controls in the Visual Studio toolbox.

The example I created is very simple. It contains a Window that has one ComboBox. The ItemsSource property is bounded to an ObservableCollection<Customer> object. The Customer class is shown in the following listing.

public class Customer

{

    public string Name { get; set; }

 

    public string Address { get; set; }

 

    public string TelephoneNumber { get; set; }

}

 

I could show all the information in the ComboBox without using a DataGrid by specifying the ItemTemplate, which is shown in the following code.

 

<Window.Resources>

    <local:Customers x:Key="Customers"/>

</Window.Resources>

 

<Grid>

    <ComboBox

        Margin="4"

        Height="23"

        Width="250"

        ItemsSource="{StaticResource Customers}">

        <ComboBox.ItemTemplate>

            <DataTemplate>

                <StackPanel Orientation="Horizontal">

                    <TextBlock Text="{Binding Path=Name}" Margin="4,0"/>

                    <TextBlock Text="{Binding Path=Address}" Margin="4,0"/>

                    <TextBlock Text="{Binding Path=TelephoneNumber}" Margin="4,0"/>

                </StackPanel>

            </DataTemplate>

        </ComboBox.ItemTemplate>

    </ComboBox>

</Grid>

This produces the following output using some data I supplied.




Now, the only thing left to do is to use a DataGrid as the dropdown of the ComboBox. To do this, we have to change the default ComboBox ControlTemplate. Getting the default template is easy by using Expression Blend. There is another way but it won’t be discussed in this article. Open the project in Expression Blend. Right-click on the ComboBox and choose Edit Control Parts (Template) then Edit a Copy... as shown in the following screenshot.



This will prompt you with a dialog asking about where to put the new style resource. Simply choose the default values. After clicking on OK, this generates a long listing in the XAML file, located in the Window’s resources region. I only copied the code that we are interested in.

<ControlTemplate

    TargetType="{x:Type ComboBox}">

    <Grid

        SnapsToDevicePixels="true"

        x:Name="MainGrid">

        <Grid.ColumnDefinitions>

            <ColumnDefinition

                Width="*"/>

            <ColumnDefinition

                MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}"

                Width="0"/>

        </Grid.ColumnDefinitions>

        <Popup

            AllowsTransparency="true"

            IsOpen="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"

            Placement="Bottom"

            PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"

            Margin="1"

            x:Name="PART_Popup"

            Grid.ColumnSpan="2">

            <Microsoft_Windows_Themes:SystemDropShadowChrome

                MaxHeight="{TemplateBinding MaxDropDownHeight}"

                MinWidth="{Binding Path=ActualWidth, ElementName=MainGrid}"

                x:Name="Shdw"

                Color="Transparent">

                <Border

                    x:Name="DropDownBorder"

                    Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"

                    BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"

                    BorderThickness="1">

                    <ScrollViewer

                        CanContentScroll="true">

                        <ItemsPresenter

                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"

                            KeyboardNavigation.DirectionalNavigation="Contained"/>

                    </ScrollViewer>

                </Border>

            </Microsoft_Windows_Themes:SystemDropShadowChrome>

        </Popup>

        <ToggleButton

            Background="{TemplateBinding Background}"

            BorderBrush="{TemplateBinding BorderBrush}"

            Style="{StaticResource ComboBoxReadonlyToggleButton}"

            Grid.ColumnSpan="2"

            IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>

        <ContentPresenter

            IsHitTestVisible="false"

            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"

            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"

            Margin="{TemplateBinding Padding}"

            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"

            Content="{TemplateBinding SelectionBoxItem}"

            ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"

            ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"/>

    </Grid>

    <ControlTemplate.Triggers>

        <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">

            <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/>

            <Setter Property="Color" TargetName="Shdw" Value="#71000000"/>

        </Trigger>

        <Trigger Property="HasItems" Value="false">

            <Setter Property="Height" TargetName="DropDownBorder" Value="95"/>

        </Trigger>

        <Trigger Property="IsEnabled" Value="false">

            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>

            <Setter Property="Background" Value="#FFF4F4F4"/>

        </Trigger>

        <Trigger Property="IsGrouping" Value="true">

            <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>

        </Trigger>

    </ControlTemplate.Triggers>

</ControlTemplate>

 

This is the ControlTemplate obtained from the ComboBox’s default style. In my example, the style is  identified as ComboBoxStyle1. What we need to edit is the Popup part of the ControlTemplate. The items are presented using an ItemsPresenter inside a ScrollViewer object. Let’s replace this with a DataGrid. To use the DataGrid, we have to add a reference to the WPF Toolkit in our project. After this, add the following XML namespace in the XAML file. You can use a different name if you want.

xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"

Now, replace the ScrollViewer with our DataGrid. I set the AutoGenerateColumns property to False because I want to set the Header property of each column. The ItemsSource is bound to the ItemsSource property of the ComboBox using TemplateBinding.

<toolkit:DataGrid

    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"

    ItemsSource="{TemplateBinding ItemsSource}"

    AutoGenerateColumns="False">

    <toolkit:DataGrid.Columns>

        <toolkit:DataGridTextColumn

            Header="Name"

            Binding="{Binding Name}"/>

        <toolkit:DataGridTextColumn

            Header="Address"

            Binding="{Binding Address}"/>

        <toolkit:DataGridTextColumn

            Header="Telephone No."

            Binding="{Binding TelephoneNumber}"/>

    </toolkit:DataGrid.Columns>

</toolkit:DataGrid>

Using the same data earlier, the window looks like this.



So far, so good. However, there are things that we still need to do. As much as possible, we want our control to behave much like a normal ComboBox. First, the DataGrid values can be edited and users can add or delete rows.  We certainly do not like this behavior so let’s set the IsReadOnly property of the DataGrid to True. Second, we can select an item in the DataGrid but it is not set as the selected item of the ComboBox. To solve this, bind the SelectedItem property of the DataGrid to the SelectedItem property of the ComboBox, as shown in the following code.

SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem}"

I thought I could use TemplateBinding here, but it does not work. I think TemplateBinding is a bit specialized and that the binding direction is only one-way, which makes sense since the templates do not normally set the value of a property of their templated control. This also improves performance. Anyway, we can solve our problem by using RelativeSource.

Lastly, the popup remains open even after selecting an item. No surprise here. To close the popup, we need to be able to set the IsDropDown property of the ComboBox.  To do this, we could bind the IsDropDown property to a property defined in the Window. The following code example shows the binding.

IsDropDownOpen="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=IsEditingCustomer}

We could set the value of the IsEditingCustomer property to control when to close the ComboBox popup. The IsEditingCustomer property is defined in the Window containing the ComboBox. So the question is when to set the value of this property. Well, it should be after the SelectedItem of the ComboBox has been changed. So let’s bind the SelectedItem to another property.

SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=SelectedCustomer}"

When the SeletedCustomer changes, the IsEditingCustomers property is set to false. The following listing shows the Window class.

public partial class Window1 : Window, INotifyPropertyChanged

{

    public event PropertyChangedEventHandler PropertyChanged;

 

    private Customer selectedCustomer;

 

    private bool isEditingCustomer;

 

    public Window1()

    {

        InitializeComponent();

    }

 

    public Customer SelectedCustomer

    {

        get

        {

            return selectedCustomer;

        }

        set

        {

            if (object.ReferenceEquals(selectedCustomer, value) != true)

            {

                selectedCustomer = value;

                if (PropertyChanged != null)

                {

                    PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer"));

                }

 

                IsEditingCustomer = false;

            }

        }

    }

 

    public bool IsEditingCustomer

    {

        get

        {

            return isEditingCustomer;

        }

        set

        {

            if (isEditingCustomer.Equals(value) != true)

            {

                isEditingCustomer = value;

                if (PropertyChanged != null)

                {

                    PropertyChanged(this, new PropertyChangedEventArgs("IsEditingCustomer"));

                }

            }

        }

    }

}

 

I implemented the INotifyPropertyChanged interface so that the UI is notified that the source properties are changed. Without this, we won’t be able to close the ComboBox popup. Notice that in the SelectedCustomer property’s setter, the IsEditingCustomer is set to false. There are other ways aside from using data binding in closing the ComboBox popup though.

That’s about it. That was a very simple implementation of a ComboBox that shows its items inside a WPF DataGrid. It can still be improved. The implementation changes the style of the ComboBox. If there are many ComboBox controls, then a style should be created for each one of them since they won’t have the same type of items. You could set the ControlTemplate directly instead to minimize defining many styles. Better yet, you can create a UserControl so that it can be reused easily.

Another thing that this implementation lacks is custom paging and sorting. For me, I don’t want to load all the database items in memory at once. It might affect the performance of the application or the system. So I would want to implement paging. When you have implemented paging already, then comes sorting. The default sorting mechanism of the DataGrid only applies to the current items. So I have to implement custom sorting so that all items are considered.

Here is the link to the example I created: WPFDataGridAsComooBoxDropdown.zip. Take note that you still need to download WPF Tookit from Codeplex to run the application.

Popularity  (20806 Views)
Biography - Michael Detras
.NET developer. Interested in WPF, Silverlight, and XNA.
My blog
My FAQs
Create New Account
Article Discussion: WPF DataGrid as ComboBox Dropdown
Michael Detras posted at Monday, August 31, 2009 2:08 PM
reply
Lina replied to Michael Detras at Saturday, October 16, 2010 8:08 AM
Hi Michael

Great post. I make a combobox as you explained here.
now i need to make a button outside the combobox. So any time I click on it, the cursor goes to the next row of the datagrid.

Can you help me with this?

Thanks a lot!!

Lina
reply
samar replied to Michael Detras at Saturday, October 16, 2010 8:08 AM

I am not able to find the tag "Microsoft_Windows_Themes". I tried adding the namespace

 

xmlns

 

:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Luna" but it not working. Can you please help me??

Thanks,

Samar

 

reply
samar replied to samar at Saturday, October 16, 2010 8:08 AM
Hi Micheal i got the answer for the query i posted earlier. I added a reference to PresentationFramework.Luna in my project and it worked.

But now i am getting an error in

Style

 

="{StaticResource ComboBoxReadonlyToggleButton}"

 


It is saying that the same is not found. Can you please clarify this?

Regards,

Samar
reply
samar replied to samar at Saturday, October 16, 2010 8:08 AM
Hi Micheal sorry to hv not informed u that i am not using expression blend and that i just copy pasted ur code and corrected it wherever necessary. 

Is it compulsory that we should bind the combo box first using the conventional method and then use the expression blend to convert it to datagrid-bound combo box??

If that is the case then i would have to get expression blend installed first and then only i can go ahead.

Please advice.

Regards
reply
Michael Detras replied to Lina at Saturday, October 16, 2010 8:08 AM
Hi Lina,

Sorry for the delayed response. Have you been able to fix the issue? If you haven't, you could bind the selected item of the datagrid and update this whenever the button gets clicked. This assumes that you are using data binding.

Regards,
Mike
reply
Michael Detras replied to samar at Saturday, October 16, 2010 8:08 AM
Hi Samar,

Sorry for the late response. I only used Expression Blend to get the default template of the combo box. It is not necessary to install one, as there are other tools, like ShowMeTheTemplate (just google it), to get a control's default template. Hope this clarify things.

Regards,
Mike
reply
button next
Lina replied to Michael Detras at Saturday, October 16, 2010 8:08 AM

No, I haven't find the solution.
Would you please look at my code? I am a designer, so it is hard for me to understand all this.
Thanks a lot!!

This is the code I have for the combobox and its style with the datagrid highlighted. What I would like is to have a "Next Button" to naviegate throuogh the content of the combobox, so the user does not have to go to the combo every time he wants to change the item selected.

<ComboBox
            Margin="229,8,0,0" 
            ToolTipService.ToolTip="Seleccione la Entrada"
            x:Name="cmbEntradas"
            Style="{DynamicResource ComboBoxStyle1}"
            SelectedItem="{Binding SelectedEntrada, RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}"
            IsDropDownOpen="{Binding IsEditingEntrada, RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}" VerticalAlignment="Top" Height="22.15" SelectionChanged="cmbEntradas_SelectionChanged" IsEnabled="False" d:LayoutOverrides="VerticalAlignment">
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock  Text="{Binding CodEntrada}" Margin="4,0" />
                            <TextBlock  Text="{Binding LOTID}" Margin="4,0"  />
                           
                            <TextBlock  Text="{Binding FechaEntrada}" Margin="4,0"   />
                           
                        </StackPanel>
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>

and this is the Style:

<Style x:Key="ComboBoxStyle1" TargetType="{x:Type ComboBox}">
            <Setter Property="FocusVisualStyle" Value="{StaticResource ComboBoxFocusVisual}"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
            <Setter Property="Background" Value="{StaticResource ButtonNormalBackground}"/>
            <Setter Property="BorderBrush" Value="{StaticResource ButtonNormalBorder}"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
            <Setter Property="Padding" Value="4,3"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ComboBox}">
                        <Grid x:Name="MainGrid"  SnapsToDevicePixels="true">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
                            </Grid.ColumnDefinitions>
                            <Popup x:Name="PART_Popup" Margin="1" AllowsTransparency="true" IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}" Placement="Bottom" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Grid.ColumnSpan="2">
                                <Microsoft_Windows_Themes:SystemDropShadowChrome x:Name="Shdw" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=MainGrid}" Color="Transparent">
                                    <Border x:Name="DropDownBorder" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1">
                                        <toolkit:DataGrid x:Name="dgrEntradas"
              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
              ItemsSource="{TemplateBinding ItemsSource}"
              AutoGenerateColumns="True"
           IsReadOnly="True"
           IsSynchronizedWithCurrentItem="True"
           SelectedItem="{Binding SelectedItem, RelativeSource={RelativeSource TemplatedParent}}">
                                           
                                        </toolkit:DataGrid>
                                    </Border>
                                </Microsoft_Windows_Themes:SystemDropShadowChrome>
                            </Popup>
                            <ToggleButton
        Style="{StaticResource ComboBoxReadonlyToggleButton}"
        Background="{TemplateBinding Background}"
        BorderBrush="{TemplateBinding BorderBrush}"
        Grid.ColumnSpan="2"
        IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
                            <ContentPresenter
        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
        Margin="{TemplateBinding Padding}"
        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
        IsHitTestVisible="false"
        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
        Content="{TemplateBinding SelectionBoxItem}"
        ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}"
        ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
        ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
                                <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/>
                                <Setter Property="Color" TargetName="Shdw" Value="#71000000"/>
                            </Trigger>
                            <Trigger Property="HasItems" Value="false">
                                <Setter Property="Height" TargetName="DropDownBorder" Value="95"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                                <Setter Property="Background" Value="#FFF4F4F4"/>
                            </Trigger>
                            <Trigger Property="IsGrouping" Value="true">
                                <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsEditable" Value="true">
                    <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
                    <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
                    <Setter Property="IsTabStop" Value="false"/>
                    <Setter Property="Padding" Value="3"/>
                    <Setter Property="Template" Value="{StaticResource ComboBoxEditableTemplate}"/>
                </Trigger>
            </Style.Triggers>
        </Style>


reply
Check this out
Michael Detras replied to Lina at Saturday, October 16, 2010 8:08 AM

Hi Lina,

You can check the this zip file containing the updated article source code having a next button. The things that I've done here are the following: move the Customers resource in the xaml.cs file and update the binding, add a next button and corresponding Click event handler, and update the SelectedCustomer property when the button is clicked. Hope this helps.

Regards,
Mike

reply
Lina replied to Michael Detras at Saturday, October 16, 2010 8:08 AM
Hey, Mike, you are a genious!!

Thanks a lot!!

You really help me so much with this.

thanks, thanks, thanks... many thanks

Lina
reply
Michael Detras replied to Lina at Saturday, October 16, 2010 8:08 AM
Hi Lina,

You're welcome. I'm glad that it helped you.

Best regards,
Mike
reply

WPF DataGrid as ComboBox DropdownThis article describes how to display the items of a WPF ComboBox inside a DataGrid when the ComboBox is clicked. It also shows how to make the behavior same as that of a normal ComboBox, like clicking the item on the DataGrid should close the ComboBox's popup.