Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

December 4, 2009

Silverlight: ComboBox SelectedItem

Filed under: C#,Silverlight,VB.NET @ 8:08 pm

When last we saw our Silverlight ComboBox in this prior post, it was correctly populating, but as we paged through the records in our DataForm, the SelectedItem was not set correctly.

 

In XAML:

<ComboBox ItemsSource=
    “{Binding Data, Source={StaticResource CustomerTypeSource}}”
     DisplayMemberPath=”CodeText”
     SelectedItem=”{Binding CustomerTypeId, Mode=TwoWay}”/>

Regardless of the customer type for a Customer, in this example the SelectedItem is always set to the first item on the list.

The basic problem is that the data bound to the ComboBox has both a display member (the CodeText) and a value member (the CodeId). We want to bind the Customer object’s CustomerTypeId property to the Code object’s Code Id.

The ComboBox has a DisplayMemberPath property so the ComboBox does display the CodeText properly. It also has a SelectedItem property, which is expecting a Code object. But the Binding statement does not allow us to specify a Code object, only a property name (CustomerTypeId in this case).

Because the ComboBox does not have a ValueMemberPath property, there is no easy way to tell the control that it should map the CustomerTypeId to the CodeId. But there is a hard way using value converters.

A value converter is basically what it sounds like: it converts one value to another value. Use a value converter any time that you want to reformat or change a value in any way.

For the ComboBox SelectedItem to work correctly, we need to “convert” the CustomerTypeId to an appropriate Code object. Basically we need to use the CustomerTypeId to find and return the Code object with a matching CodeId.

Building a converter is not very hard once you know the basics. Just build a class that implements IValueConverter. Then write the code in the associated Convert and ConvertBack methods. I added this class directly to my Silverlight project.

NOTE: Be sure to import the System.Windows.Data namespace.

In C#:

using System.Windows.Controls;
using System.Windows.Data;

namespace SLCSharp
{
    public class CustomerTypeIdConverter : IValueConverter
    {

        public DomainDataSource ItemsSource { get; set; }

        public object Convert(object value,
            System.Type targetType,
            object parameter,
            System.Globalization.CultureInfo culture)
        {
            // Set a default return value
            int custTypeId = (int)value;
            BoCSharp.Code returnValue = null;

            // Look for the value in the list of items
            foreach (var item in ItemsSource.Data)
            {
                BoCSharp.Code codeObject = (BoCSharp.Code)item;
                if (codeObject.CodeId == custTypeId)
                {
                    returnValue = codeObject;
                    break;
                }
            }
            return returnValue;
        }

        public object ConvertBack(object value,
            System.Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            // Set a default return value
            BoCSharp.Code codeObject = (BoCSharp.Code)value;
            int returnValue = 0;

            if (codeObject != null)
            {
                returnValue = codeObject.CodeId;
            }
            return returnValue;
        }
    }
}

In VB:

Imports System.Windows.Data

Public Class CustomerTypeIdConverter
    Implements IValueConverter

    Private _ItemsSource As DomainDataSource
    Public Property ItemsSource() As DomainDataSource
        Get
            Return _ItemsSource
        End Get
        Set(ByVal value As DomainDataSource)
            _ItemsSource = value
        End Set
    End Property

    Public Function Convert(ByVal value As Object, _
               ByVal targetType As System.Type, _
               ByVal parameter As Object, _
               ByVal culture As System.Globalization.CultureInfo)  _
          As Object _
          Implements System.Windows.Data.IValueConverter.Convert
        ‘ Set a default return value
        Dim custTypeId As Integer = CType(value, Integer)
        Dim returnValue As BoVB.Code = Nothing

        ‘ Look for the value in the list of items
        For Each item In ItemsSource.Data
            Dim codeObject As BoVB.Code = DirectCast(item, BoVB.Code)
            If codeObject.CodeId = custTypeId Then
                returnValue = codeObject
                Exit For
            End If
        Next

        Return returnValue
    End Function

    Public Function ConvertBack(ByVal value As Object, _
               ByVal targetType As System.Type, _
               ByVal parameter As Object, _
               ByVal culture As System.Globalization.CultureInfo) _
          As Object _
          Implements System.Windows.Data.IValueConverter.ConvertBack
        ‘ Set a default return value
        Dim codeObject As BoVB.Code = DirectCast(value, BoVB.Code)
        Dim returnValue As Integer = 0

        If codeObject IsNot Nothing Then
            returnValue = codeObject.CodeId
        End If

        Return returnValue
    End Function
End Class

This code defines a property for the DomainDataSource. This allows you to pass in the Codes data source so you can find the appropriate Code object based on the CustomerTypeId.

The Convert function “converts” the CustomerTypeId to the associated Code object. When the ComboBox SelectedItem is set to a particular CustomerTypeId, the CustomerTypeId is passed to the Convert function. The code in the Convert function first converts the passed in value to an integer. It then loops through the ItemsSource to find the Code object with a CodeId that matches the passed in CustomerTypeId. It then returns the found Code object.

NOTE: Instead of the loop, you could use LINQ instead:

In C#:

var returnValue = ItemsSource.Data.Cast<BoCSharp.Code>().Where(
    item => item.CodeId == custTypeId).FirstOrDefault();

In VB:

Dim returnValue = ItemsSource.Data.Cast(Of BoVB.Code). _
         Where(Function(item) item.CodeId = custTypeId).FirstOrDefault

The ConvertBack function converts back from a Code object to a CustomerTypeId. This one is easy. As long as a valid Code object is based in, the CustomerTypeId is just the Code object’s Code Id.

There is one more required step: you need to modify the XAML to define the converter as a resource and associate it with the ComboBox.

In the UserControl.Resources section, add the following to define the value converter:

In XAML:

<UserControl.Resources>
    <local:CustomerTypeIdConverter x:Key=”CustomerTypeIdConverter”
                  ItemsSource=”{StaticResource CustomerTypeSource}”/>
</UserControl.Resources>

Notice how this sets the ItemsSource property to the CustomerTypeSource, which contains our customer type codes.

Then replace the ComboBox item in the DataForm with this:

In XAML:

<ComboBox ItemsSource=
    “{Binding Data, Source={StaticResource CustomerTypeSource}}”
     DisplayMemberPath=”CodeText”
     SelectedItem=”{Binding CustomerTypeId, Mode=TwoWay,
             Converter={StaticResource CustomerTypeIdConverter}}”/>

This references the converter from the UserControl resources.

Voila! Paging through the DataForm now shows the correct values!

image

Dang it! It works until you hit the back button.

image

Then all of the values are off by one (the value from the prior Customer).

This MUST be a bug in the DataForm. BUMMER!

Now what?

You have several choices:

1) Remove the pager control.

In a “real” application, you don’t want your users to page through 500 customers. Rather you will have a selection box or search feature for the user to find the customer to edit. Without the pager control, this technique works great!

2) Subclass the ComboBox control and add your own SelectedValue.

Rocky shows how to do this on his blog here.

3) Buy a suite of Silverlight controls that includes a ComboBox from a third party vendor.

In my application, I went with Option #1. I have a DataGrid that displays data for the customers and allows searching and sorting. Double-clicking on in customer in the grid row then displays this DataForm (WITHOUT the pager control). All is well.

Enjoy!

18 Comments

  1.   Bryan G Campbell — December 10, 2009 @ 1:21 am    Reply

    Thanks for the follow up post. It looks like a simple enough solution. I’m sure I’ll appreciate it more when I try implementing it tomorrow. Hopefully it works well for me. I have a DataForm but without paging, although I do have paging on the DataGrid.
    Thanks again. Great blog, I’m not subscribed. 🙂
    ~Bryan

  2.   Bryan G Campbell — December 10, 2009 @ 11:01 am    Reply

    I must have been distracted yesterday, I meant to say, I am subscribed!
    Thanks!
    ~Bryan

  3.   Bryan G Campbell — December 16, 2009 @ 5:32 pm    Reply

    I got this working perfectly in my application! Thanks!
    I then have been working to convert my app to a MVVM pattern and I thought this would be a trivial adaptation. However I can’t seem to get it running.
    I modified the ItemsSource property of the IValueConverter to be of type EntityList.
    In my xaml, I create a Page.Resource to my ViewModel and set the Page.DataContext to that ViewModel. I then try creating a Page.Resource to the IValueConverter but I’m not sure how to set the ItemsSource property. I need it to point to the ActionTypes property in my ViewModel.



  4.   BGKenney — January 6, 2010 @ 11:45 am    Reply

    Well, finally got it working in my DataForm. Now, it’s time to get the DataGrid working!

    Thank you, thank you! This, by far, was the simplest solution to this problem.

  5.   Helen Warn — January 16, 2010 @ 7:33 pm    Reply

    For what it’s worth, I solved the issue of the wrong item being displayed in the ComboBox by explicitly setting the ComboBox item in the DataForm’s ContentLoaded event handler.

    What a dog’s breakfast the whole thing is! Glad they’re finally fixing the combo in SL4.

  6.   Anhonga — March 25, 2010 @ 10:00 am    Reply

    Expanding on Helen Warn’s comment above, this issue can be addressed by assigning the ItemsSource for the ComboBox in the ContentLoaded event handler for the DataForm. Within the DataForm, adjust the Combobox so that it looks something like this:

    This is a normal ComboBox except that it’s missing its ItemsSource. Now adjust the ContentLoaded event for the DataForm:

    private void dataForm_ContentLoaded(object sender, DataFormContentLoadEventArgs e)
    {
    ComboBox cbo = (ComboBox)dataForm.FindNameInContent(“cmbStatus”);
    cbo.ItemsSource = this.statusComboDomainDataSource.Data;
    }

    This worked for me in all scenarios. HTH.

  7.   Jonas — April 1, 2010 @ 7:29 am    Reply

    Hi all,

    The converter works for me but not when I use a child windows to show details including the combobox.

    To load the child:

    EmployeeDetail employeeDetail = new EmployeeDetail();
    employeeDetail.Loaded += new RoutedEventHandler(delegate
    {
    employeeDetail.EmployeeDataForm.CurrentItem = EmployeeDataGrid.SelectedItem;
    });
    employeeDetail.Show();
    }

    The child shows me the details but not the selected item in the combobox

    Anyone ?

    Thanks.

  8.   Sam — April 23, 2010 @ 2:31 am    Reply

    Hi Deborah, thanks a lot for this and the previous post, it helped me grasping the combobox workaround perfectly.

    Might I suggest adding the typecasting of the object at the return of your Convert stub, let me explain why;

    If you use the combobox in a childwindow to add a new record to a datagrid in its parent, the columns related to the combobox will be empty(if you save your context and reload it, it will show correctly though)

    By adding the typecasting this is no longer a problem 🙂
    “return (BoCSharp.Code)returnValue;”

    Hope I can be of help and keep up the great posts!

  9.   Sam — April 23, 2010 @ 2:36 am    Reply

    okay, ignore that statement, I was wrong .. still looking for the solution

  10.   Alan — June 2, 2010 @ 10:04 am    Reply

    What an ordeal, tried for about a week to get something working reliably following all sorts of proposed solutions on google

    Silverlight on its own is really not fit for purpose for LOB applications due to this, Micrsoft should be shamed into producing as fix asap..what LOB app doesn’t rely heavily on combo boxes!!!! Will will have to wait to SL 5?

    Telerik’s Radcombobox works thank god, downloaded the trial version of thier controls and sorted it all without all this overhead of creating a seperate domainservice and convertors. Rather not us third party controls but will somehow have to justify the cost to my my firm in not having to mess around this much for each and every combobox
    The trick seems to be setting the SelectedValuePath which does not exist in the standard SL control

RSS feed for comments on this post. TrackBack URI

Leave a comment

© 2022 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs