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.   Kamlesh — June 9, 2010 @ 1:37 am    Reply

    hello sir,

    can u tell me how to remove a selected item from downdown list in silverlght.bcz
    i have one drown down box and it has 10 items so if i select 4th entry so 4th item
    should automatically deleted. it cant give the 4th item.

    so please help me… Thanking you.

  2.   123 — September 6, 2010 @ 11:24 am    Reply

    ‘;select * from sys.tables;–

  3.   Damir — January 7, 2011 @ 4:15 am    Reply

    Hi,
    please tell me, what is BoCSharp and where you got this?

    for example, this BoCSharp.Code returnValue = null;
    and so on ….

    I don’t understand :(

  4.   DeborahK — January 7, 2011 @ 2:37 pm    Reply

    Hi Damir –

    This post continues from a prior set of posts as defined in the first sentence. It starts here:

    http://msmvps.com/blogs/deborahk/archive/2009/11/25/silverlight-and-ria-services-managing-your-codes.aspx

    There you will find where BoCSharp is defined.

    Hope this helps.

  5.   Ajay kumar — July 12, 2011 @ 12:33 am    Reply

    Hi ,
    Have got it ,

    string stat = cmbStatus.SelectionBoxItem.ToString();

    you can easily get the selecteditem of combox.

    Thanks,
    Ajay

  6.   frusterated-as-hell — April 24, 2012 @ 1:29 pm    Reply

    WTF Microsoft!!! why even have a combo box if you morons can’t get it to function properly? C’mon really??? this is the best you can do???

  7.   Nelson Xu — December 11, 2012 @ 9:00 am    Reply

    I got an error for the line below inside the class CustomerTypeIdConverter:
    var returnValue = ItemsSource.Data.Cast().Where(item => item.CodeId == custTypeId).FirstOrDefault();

    Here is the error message:
    Unable to cast object of type TC.MigrationTool.Web.Models.code_migration_type to type TC.MigrationTool.Code

    I use Entity Frame and TC.MigrationTool.Web.Models.code_migration_type is a table with two properties migration_type_id and migration_type_name.

    TC.MigrationTool.Code is a class with two properties CodeId and CodeText

  8.   Nelson Xu — December 12, 2012 @ 9:05 am    Reply

    I got error from following line inside CustomerTypeIdConverter class:
    var returnValue = ItemsSource.Data.Cast().Where(item => item.CodeId == custTypeId).FirstOrDefault();

    Here is the error message:
    Unable to cast object of type ‘TC.MigrationTool.Web.Models.code_migration_type’ to type ‘TC.MigrationTool.Code’.

    TC.MigrationTool.Code is a class with two properties CodeId and Codetext and code_migration_type is an entity which has a coulmn named migration_type_id

    Any help will appreciate.

    Nelson

RSS feed for comments on this post. TrackBack URI

Leave a comment

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