Manish Dalal's blog

Exploring .net!

ComboBox in DataGrid

This post examines usage of ComboBox in DataGrid. In particular, it shows how to implement foreign key scenarios in lieu of missing SelectedValue property. It also highlights workaround for a bug in RC0 that causes ComboBox dropdown to close immediately in DataGrid.

ComboBox

The ComboBox control is used to present users with a list of values to select from. This can be used to show a simple list of valid values that user can choose from or you might be showing user a complicated type. ComboBox.ItemTemplate can be used visualize the complex item, but you will find that current ComboBox implementation in Silverlight 2 (RC0) is missing SelectedValue and SelectedValuePath property. This is a common requirement in business applications, where in majority of cases foreign keys are used for represents joins/relationships. In these cases you normally use foreign key values to select the item and result of use selection is also saved as foreign key value. However user is shown a friendly name/description in place of foreign key value which might be numeric or guid.

Lets first see how to bind to a simple property, a string list and then we will modify the code to implement foreign key scenario by binding to a complex type .

Simple Data Binding

Create a new Silverlight application project. Also create the corresponding web application to host and test the Silverlight application. Next add System.Windows.Control.Data reference to the Silverlight project.

Add following code to page.xaml

<UserControl x:Class="Silverlight.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    xmlns:src="clr-namespace:Silverlight"
    Width="400" Height="300">
    <UserControl.Resources>
        <src:CityProvider x:Key="cityProvider"/>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <data:DataGrid x:Name="dataGrid" AutoGenerateColumns="False">
            <data:DataGrid.Columns>
                <data:DataGridTextColumn Header="Street Name" Binding="{Binding StreetName}"/>
                <data:DataGridTemplateColumn Header="City">
                    <data:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding CityName}" />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellTemplate>
                    <data:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <ComboBox SelectedItem="{Binding CityName, Mode=TwoWay}" 
                                      ItemsSource="{Binding CityList, Source={StaticResource cityProvider}}" 
                                  />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellEditingTemplate>
                </data:DataGridTemplateColumn>
                <data:DataGridTextColumn Header="Zip Code" Binding="{Binding ZipCode}"/>
            </data:DataGrid.Columns>
        </data:DataGrid>
    </Grid>
</UserControl>

Add following code to page.xaml.cs code behind

public partial class Page : UserControl {
    public Page() {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Page_Loaded);
        }

    void Page_Loaded(object sender, RoutedEventArgs e) {
        this.dataGrid.ItemsSource = new List<Address>() { new Address() { StreetName = "Street 1", CityName="City 1", ZipCode = "1"},
                                                          new Address() { StreetName = "Street 2", CityName="City 2", ZipCode = "2"},
                                                          new Address() { StreetName = "Street 3", CityName="City 3", ZipCode = "3"}
        };
    }
}

public class Address {
    public string StreetName { get; set; }
    public string CityName { get; set; }
    public string ZipCode { get; set; }
}

public class CityProvider {
    public List<string> CityList {
        get {
            return new List<string> { "City 1", "City 2", "City 3", "City 4" };
        }
    }
}

 

Here we are showing user Address business entity in the DataGrid. Address class consists of StreetName, CityName and ZipCode fields. User can change value of CityName using ComboBox, that provides list of valid cities.

ComboBox ItemsSource is bound to CityProvider that provides a list of cities(string list) to select from. ComboBox SelectedItem property is bound to CityName field of the Address business class. This selects proper value in ComboBox when it is shown and allows saving user selection back to the business class.

F5 and test the application. Change value of City field. Notice proper selection of item in ComboBox dropdown after the change.

Foreign Key Data Binding (alternative to missing SelectedValue property)

Now instead of binding to simple string City field, lets say that we are storing CityId (an integer) in Address class in place of City. Here CityId represents the foreign key. Lets modify code to select using CItyId foreign key and also save result of selection as CityId back to Address business class.

Modify Page.xaml as follows

<UserControl x:Class="Silverlight.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    xmlns:src="clr-namespace:Silverlight"
    Width="400" Height="300">
    <UserControl.Resources>
        <src:CityProvider x:Key="cityProvider"/>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <data:DataGrid x:Name="dataGrid" AutoGenerateColumns="False">
            <data:DataGrid.Columns>
                <data:DataGridTextColumn Header="Street Name" Binding="{Binding StreetName}"/>
                <data:DataGridTemplateColumn Header="City">
                    <data:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding CityInfo.CityName}" />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellTemplate>
                    <data:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <ComboBox SelectedItem="{Binding CityInfo, Mode=TwoWay}" 
                                      ItemsSource="{Binding CityList, Source={StaticResource cityProvider}}" 
                                      DisplayMemberPath="CityName"
                                  />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellEditingTemplate>
                </data:DataGridTemplateColumn>
                <data:DataGridTextColumn Header="Zip Code" Binding="{Binding ZipCode}"/>
            </data:DataGrid.Columns>
        </data:DataGrid>
    </Grid>
</UserControl>

Change Page.xaml.cs code behind as:

public partial class Page : UserControl {
    public Page() {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Page_Loaded);
        }

    void Page_Loaded(object sender, RoutedEventArgs e) {
        this.dataGrid.ItemsSource = new List<Address>() { new Address() { StreetName = "Street 1", CityId=1, ZipCode = "1"},
                                                          new Address() { StreetName = "Street 2", CityId=2, ZipCode = "2"},
                                                          new Address() { StreetName = "Street 3", CityId=3, ZipCode = "3"}
        };
    }
}

public class Address {
    public string StreetName { get; set; }
    //public string CityName { get; set; }
    public int CityId { get; set; }
    public string ZipCode { get; set; }
    //
    private City _cityInfo;

    public City CityInfo {
        get {
            if (null == _cityInfo) {
                _cityInfo = new CityProvider().CityList.Where(c => c.CityId == CityId).SingleOrDefault();
            }
            return _cityInfo; 
        }
        set { 
            _cityInfo = value;
            CityId = _cityInfo.CityId;
        }
    }

}

public class CityProvider {
    public List<City> CityList {
        get {
            //return new List<string> { "City 1", "City 2", "City 3", "City 4" };
            return new List<City> { new City() { CityName ="City 1", CityId=1},  
                                    new City() { CityName ="City 2", CityId=2},   
                                    new City() { CityName ="City 3", CityId=3},  
                                    new City() { CityName ="City 4", CityId=4} };
        }
    }
}

public class City {
    public string CityName { get; set; }
    public int CityId { get; set; }

    public override bool Equals(object obj) {
        if (null == obj) {
            return false;
        }
        return this.CityId == ((City)obj).CityId;
    }

    public override int GetHashCode() {
        return CityName.GetHashCode();
    }
}

We have changed CityProvider to return City class that consists of CityName and CityId fields. We have also modified Address business class by removing CityName and adding CityId field to store value of foreign key.

Notice that we have also added new CityInfo property to the Address class. Address class provides this helper property so that we can bind it to ComboBox SelectedItem property. It is this helper property that is key to the data binding in foreign key scenarios. In get method we use current CityId value from Address business class and return an instance of City class. This instance sets the SelectedItem by way of data binding. In set method, we get values from ComboBox selected item and set CityId to the selection. (Demo Note: In production code please consider caching CityList)

Now when you run the application, and change values, proper CityId is saved back to Address class.

image image

RC0 ComboBox Issue

In RC0, there is issue with ComboBox displaying properly in DataGrid. When you try to open ComboBox, it display and then immediately closes. I posted about it here and  Lee found a work around. Here is the work around. Use MyComboBox in place of ComboBox in above code.

public class MyComboBox : ComboBox {
    public MyComboBox() {
        DefaultStyleKey = typeof(ComboBox);
        this.Loaded += new RoutedEventHandler(MyComboBox_Loaded);
    }
    void MyComboBox_Loaded(object sender, RoutedEventArgs e) {
        IsDropDownOpen = true;
    } 

}

 

 

 

 

 

 

Alternatively if you wound rather not inherit and create new control, you can use Attached Property to provide same behavior modification. I have already blogged about using Attached Property to provide service and this uses the same technique to force open ComboBox dropdown

public class ComboBoxService {
    public static readonly DependencyProperty ForceOpenProperty =
        DependencyProperty.RegisterAttached("ForceOpen", typeof(bool), typeof(ComboBoxService),
                                            new PropertyMetadata(OnForceOpenChanged));

    public static bool GetForceOpen(DependencyObject d) {
        return (bool)d.GetValue(ForceOpenProperty);
    }

    public static void SetForceOpen(DependencyObject d, bool value) {
        d.SetValue(ForceOpenProperty, value);
    }

    private static void OnForceOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
        ComboBox comboBox = d as ComboBox;
        if ((bool)e.OldValue) {
            comboBox.Loaded -= new RoutedEventHandler(comboBox_Loaded);
        }
        if ((bool)e.NewValue) {
            comboBox.Loaded +=new RoutedEventHandler(comboBox_Loaded);
        }
    }

    static void comboBox_Loaded(object sender, RoutedEventArgs e) {
        ComboBox comboBox = sender as ComboBox;
        if (null == comboBox) {
            comboBox = e.OriginalSource as ComboBox;
        }
        //
        comboBox.IsDropDownOpen = true;
    }
}

 

Usage:

<ComboBox SelectedItem="{Binding CityInfo, Mode=TwoWay}" 
          ItemsSource="{Binding CityList, Source={StaticResource cityProvider}}" 
          DisplayMemberPath="CityName"
          src:ComboBoxService.ForceOpen="true"
      />

Source Code: ComboBoxUsage.zip

Comments

funny wallpaper » ComboBox in DataGrid said:

Pingback from  funny wallpaper &raquo; ComboBox in DataGrid

# September 29, 2008 10:24 AM

Rachida Dukes said:

Thanks for sharing your knowledge. Keep the good work. Thanks again.

Rachida

# September 29, 2008 12:19 PM

DrewPierce said:

Manish,

very nicely done.

Thanks for the effort. Hopefully we can return the favor sometime. Don't hesitate to ask me about WCF or security if the need does arise.

# September 29, 2008 8:48 PM

Gary Hanson said:

Manish-

Thank you. This was very frustrating.

-Gary

# September 30, 2008 12:58 AM

Community Blogs said:

Boyan Mihaylov on SL/Amazon, David Hyde with SL Stock Portfolio, Chris Anderson with SL LOB app, Jesse

# September 30, 2008 2:01 AM

2008 September 30 - Links for today « My (almost) Daily Links said:

Pingback from  2008 September 30 - Links for today &laquo; My (almost) Daily Links

# September 30, 2008 3:52 AM

Silverlight news for September 30, 2008 said:

Pingback from  Silverlight news for September 30, 2008

# September 30, 2008 9:45 AM

rascunho » Blog Archive » links for 2008-09-30 said:

Pingback from  rascunho  &raquo; Blog Archive   &raquo; links for 2008-09-30

# September 30, 2008 4:08 PM

Maksim said:

Nice article!

Could you please explain how to sort by this template column as far as implementation of IComparable in the City class doesn't seems to work. And one more issue is that after starting to edit with a combobox current value isn't selected automatically and is set to blank after pop-up is shown.

# October 8, 2008 9:56 AM

manish.dalal said:

Maksim,

Sort works, here is the markup

<data:DataGridTemplateColumn Header="City" SortMemberPath="CityInfo.CityName">

Currenty value seems to work for me, but if it does not work for you, try this markup

<ComboBox ItemsSource="{Binding CityList, Source={StaticResource cityProvider}}"

                                     SelectedItem="{Binding CityInfo, Mode=TwoWay}"

                                     DisplayMemberPath="CityName"

                                     src:ComboBoxService.ForceOpen="true"

                                 />

Make sure ItemSource is before SelectedItem

# October 9, 2008 9:04 PM

Manish Dalal's blog said:

In the post ComboBox in DataGrid , we examined the usage of a simple ComboBox, displaying a fixed list

# October 22, 2008 11:55 AM

Dave Burke said:

Silverlight 2.0 Immersivity: Datagrid to ComboBox Data Binding

# October 24, 2008 8:56 AM

Mike said:

Thanks for posting this solution. SelectedValuePath seems like a pretty big oversight. Definitely caught me off guard

# October 24, 2008 10:27 AM

Nick said:

Cool Article ...

but i have the following issues.

Combobox isn't selected automatically and also

after selecting any calue it goes back to blank .

Please help

# November 4, 2008 4:36 PM

Sharker Khaleed Mahmud said:

Hi,

I was going through your article and i was wondering if you could tell me how get the get the combobox in SelectionChanged event in datagrid when a particular row is selected.

private void dataGrid_SelectionChanged(object sender,SelectionChangedEventArgs e)

{

ComboBox selectedRowComboBox = ??

}

Thanks

email: shamrat231@hotmail.com

Sharker Khaleed Mahmud

# December 31, 2008 2:11 AM

Rontch said:

How do you get the selected item of the combobox, in the selected row?

The thing is that the combobox is displayed of type DataGridTemplateColumn)

# March 29, 2009 6:55 PM

Srikanth said:

hi Manish,

Can i know if this works with Data which is retrieved from database.....

Kindly let me if its possible...Since i have problems doing it using database....

Thanks In Advance

# May 21, 2009 12:56 AM

Kunal said:

Hi, I am using the same source code but binding data using WCF service. But During Page Load it is not showing datain cityId coloumn. but when I click on the column combobox appears and on selected item it gets bind. I dont know why this is happening. I am getting list from WCF service and counverting it to List<city>..Please Help

# May 21, 2009 12:58 AM

Marc said:

Same here: what if you get your addresses from a server using WCF. The Address classes are generated for you. They can be extended with Extension Methods but you cannot add a property like CityInfo. Does this mean I have to copy the ObservableCollection<Address> to an ObservableCollection<AddressSubClass> so that I have the CityInfo property? Or is there a way to define the CityInfo on the server and then WCF somehow gets it on the client?

# May 21, 2009 2:20 PM

manish.dalal said:

@Marc,

Classes that are generated for you are "partial" classes, allowing you to extend them as needed... alternatively you can always use ViewModel for binding  

@Kunal,

How are you loading initial data? Note that there are two parts, Foreign Key and some Description, in addition to ItemsSource. When you get data from server for a particular record, you need to get Id and Description (CityId and CityDescription) and make defult CityInfo object out of it. This will be CityInfo and then load ItemsSource.

@Srikanth,

Code works with database (WCF) service. I will have an example soon for SL3 + RIA Services (it is quite a bit easier!)

# May 21, 2009 7:51 PM

Marc said:

Classes can be extended with methods but not with properties. Properties are essential in your solution.

# May 26, 2009 4:19 PM

manish.dalal said:

@March,

You are probably confusing Extended methods with partial class. Partial class allows you to provide other partial class(es) and compiler will combine them. You can include any property, methods or other attributes!

# May 26, 2009 7:38 PM

Marc said:

Of course! Add partial classes to extend what's generated. Thanks!

# June 23, 2009 1:08 PM

Community Blogs said:

This post outlines technique for displaying ComboBox control with .net RIA services to handle Foreign

# July 3, 2009 1:42 PM

Silverlight 3 ComboBox Control | I love .NET! said:

Pingback from  Silverlight 3 ComboBox Control | I love .NET!

# July 3, 2009 4:15 PM

Peter Wone said:

I'm trying to do this with DomainDataSources with little success.

This example is similar to your example above; I want the UI to show a combobox supporting selection from a table of parties.

Here's an excerpt.

       <data:DataGrid x:Name="dataGrid2"

           IsReadOnly="False" AutoGenerateColumns="False"

           HorizontalAlignment="Left"

           HorizontalScrollBarVisibility="Disabled"

           ItemsSource="{Binding Data, ElementName=ddsTransfer}"

           >

         <data:DataGrid.Columns>

           <data:DataGridTemplateColumn Header="Party" >

             <data:DataGridTemplateColumn.CellTemplate>

               <DataTemplate>

                 <TextBlock

                 Style="{StaticResource ContentTextStyle}"

                 Text="{Binding PartyId}"

                 />

               </DataTemplate>

             </data:DataGridTemplateColumn.CellTemplate>

             <data:DataGridTemplateColumn.CellEditingTemplate>

               <DataTemplate>

                 <ComboBox

                 SelectedItem="{Binding PartyId, Mode=TwoWay}"

                 ItemsSource="{Binding Data, ElementName=ddsParty}"

                 DisplayMemberPath="Name" />

               </DataTemplate>

             </data:DataGridTemplateColumn.CellEditingTemplate>

           </data:DataGridTemplateColumn>

I would normally show the party name but this snippet is simplified for the purpose of the question.

# July 25, 2009 12:38 PM

Simon said:

There appears to be a problem with the combobox losing its settings when the data grid has enough rows to need a scrollbar. When a row is off page the combobox is reset (various effects depending on binding - for me the selected item is removed) and eventually it throws an exception way down in the bowls of the combobox. Any thoughts ?

# August 17, 2009 7:43 PM

Karol Stachyra said:

Thx for the article! its very useful:)

# October 11, 2009 4:53 PM

Jacky Kenhjiro said:

This Method work on DataGrid but in DataForm not Work ....

# October 21, 2009 6:39 AM

Mamoon ur Rasheed said:

Check out this article to bind data to combobox inside data grid, i hope this will help to start

aspilham.blogspot.com/.../data-binding-to-combox-inside-datagrid.html

# November 5, 2009 12:06 PM

vaddi said:

Manish, Your sample projects are very helpful for Silverlight Beginner you have covered more than enough to deal with ComboBox. Thanks and I will look forward to see more from you. Vaddi.

# November 12, 2009 5:46 PM

sa said:

Greeting Manish,

This is a Nice article!

Could you please explain how use it using Service Reference. I tried to do using Service Reference. But  sort And one more issue is that after starting to edit with a combobox current value isn't selected automatically and is

set to blank after pop-up is shown

Could you explain it.

Thanks

SA

# February 4, 2010 10:08 PM

JdZ said:

Hello,

Thanks for such a well written artice. I'm new to Silverlight so it really helped.

The XAML provided uses a textblock as the celltemplate in view mode and a combobox for edit mode. In my application I want the users to be able to identify which columns are of type combobox and which are text without having to click to try to enter edit mode.

I tried changing the xaml to display a combobox in both view and edit modes but have not been successful, does anyone have a code example for this.

Regards,

JdZ

# February 5, 2010 3:30 AM

Morten Grøtan said:

Awesome technique, in the regard that it actually worked on the first attempt. I just had to realize that I was in fact having to use the foreign key approach, as I bind to an object with properties rather than a single value.

I can't help feeling, though, that the syntax to achieve such a trivial thing is overwhelming for all but experienced object-oriented developers. Coming from ASP.NET, I'd probably solve the same task in just a fraction of the time I spent now, which is kind of disappointing.

# February 18, 2010 6:30 PM

Twitter Mirror said:

http://weblogs. asp.net /manishdalal/archive/2008/09/28/combobox-in-datagrid.aspx

# March 17, 2010 12:26 PM

sachin said:

How to handle the case when the data is loaded asynchronously from a database ?

Lets say Address and City are loaded from DB. Then Address can be bound to the grid, how do I bind the City to the combo box ?

# April 12, 2010 9:31 AM

Elegant way to create combo box in C1FlexGrid (Tip 8) « Unni's space said:

Pingback from  Elegant way to create combo box in C1FlexGrid (Tip 8) &laquo; Unni&#039;s space

# December 1, 2011 1:05 PM