Elegant way to create combo box in C1FlexGrid (Tip 8)

You are asked to create a Silverlight application to show a grid where one or more columns are combo boxes. This is a very common scenario. It can be very easily solved by creating a data edit template on the grid column and show a combo box, with a converter. There are lots of examples for this scenario, one in code project and another one in Manish blog. When I binged, there are lot of information in stack overflow. There is a much better solution if you are using Component One.

If you have component one, then we can remove all these data template, combo box and converters with simple property called Value Converter. Lets see the problem statement. We need to create a grid, with employee name and job title.

image

When you enter title in edit mode, it show available jobs like the following

image

Nothing special here, it is the standard combo box which shows all the available jobs for user to pick from. So how do we solve this problem using the special Value Converter?

Lets start from ViewModel this time. 

Code Snippet
  1.  
  2. public List<Person> People
  3. {
  4.     get
  5.     {
  6.         List<Person> list = new List<Person>();
  7.         list.Add(new Person { ID = 1, Name = "Ajay", JobID = 1 });
  8.         list.Add(new Person { ID = 2, Name = "Jeeem", JobID = 2 });
  9.         return list;
  10.     }
  11. }

Nothing new in the view model, it is same as you would build a collection in any other program. How about View?

Code Snippet
  1. <c1:C1FlexGrid Name="_flex" ItemsSource="{Binding People}" AutoGenerateColumns="False">
  2.     <c1:C1FlexGrid.Columns>
  3.         <c1:Column Binding="{Binding Name}"/>
  4.         <c1:Column Binding="{Binding JobID, Mode=TwoWay}" Header="Title"/>
  5.     </c1:C1FlexGrid.Columns>
  6. </c1:C1FlexGrid>

Look at that, there is nothing special here either. We have a grid with two columns, first column is name and second column is Job ID. Please note that, we are not using combo box here. Ok so far we did not do anything new and I still want a combo box in second column. Lets go to the view code behind, that’s where the magic happens.

Code Snippet
  1. public Dictionary<int, string> dict = new Dictionary<int, string>();
  2.  
  3. public MainPage()
  4. {
  5.     InitializeComponent();
  6.     dict.Add(1, "Manager");
  7.     dict.Add(2, "Team Lead");
  8.     _flex.Columns[1].ValueConverter = new ColumnValueConverter(dict);
  9. }

We first create a dictionary, where we are going to keep the options available to show in the combo box. Line 6 and 7 adds two job titles to the dictionary and finally in line 8, we say, for second column use the value converter and point to new instance Column Value Converter with dictionary collection. This informs flex grid that, this column needs to a combo box and use the dictionary to show the string value of the value bound to the column.

Look at the code, it is very simple and elegant. I really like the approach more than writing data template, converters etc.,

One things, I haven’t figured out yet is to bind the value converter directly to view model property. If we could do it, we can eliminate all code from code behind. If I find a solution, I will post it here.

Why SelectedValue does not show up on initial page load in Silverlight for ComboBox Control?

One of my colleague ran into this problem last week and thought of sharing the problem solution and one excellent side benefit. Let me explain the problem. We have a combo box, which have list of years. Once the years property is loaded, we set the maximum value in the years field as the default selected year and show that year. Very simple, done it bunch of times no biggie. Also I want to point out here that what I have been talking about for my last couple of posts, we can  identify and resolve lot of problems with ‘Design Time Data’ than waiting for it to run.

For the people who wants to know the solution rather than all the explanation, the SlectedValue data type and ItemsSource data type were mismatching that’s why selected item did not show up in the first time load. Let me show you how to duplicate and fix the problem without even running the program.

First lets create the interface for run time and design time view model (best practices). I copied the code from the program that was failing and the properties look like the following in IMainViewModel.cs

public interface IMainViewModel
{
    List<Int16> Years { get; }
    int SelectedYear { get; }
}

you will notice that combo box going to be bound to Years of type Int16 while the selected year is int, that was the problem but at that time of writing the code I did not know that. With that interface, let me implement the Design time view model ‘DesignMainViewModel.cs’ as

public class DesignMainViewModel : IMainViewModel
{
    public List<Int16> Years
    {
        get
        {
            List<Int16> years = new List<Int16>();
            years.Add(2011);
            years.Add(2010);
            return years;
        }
    }

    public int SelectedYear
    {
        get
        {
            return 2011;
        }
    }
}

Lets look at the XAML

        <ComboBox ItemsSource="{Binding Years}" SelectedValue="{Binding SelectedYear, Mode=TwoWay}" Grid.Row="1"/>

Now if you look at the design time gird, you will see the selected year is blank

image

Here itself I know there is a problem with selected year since it is not showing 2011. Now looking at the definition on IMainViewModel, instead of using two different data types, if you would switch them to same data type and change the implementation in Interface, design time and run time view model, your design screen would look like the following

image

see 2011 shows up. The solution in here is that, when you are binding a collection to a combo box, make sure the data type of the property bound to SelectedValue also have the same data type.

The best part of it all, I was able to identify the problem during the design time and was able to fix it also at the design time.