Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

November 25, 2009

Silverlight and RIA: Adding a ComboBox to a DataForm

Filed under: ASP.NET RIA Services,C#,Silverlight,VB.NET @ 5:44 pm

Building a DataForm is quick and easy and is detailed in this prior post. Even customizing it is a breeze as was also shown in that prior post. But now the design calls for a ComboBox. Ready to walk off a cliff?

It is surprisingly difficult to modify a DataForm to display a working ComboBox that is bound to a DomainDataSource. And it is even harder because so many of the available examples use a Fields collection that disappeared in July of 2009.

This post provides the steps for adding a ComboBox control to a DataForm. It retrieves the values for the ComboBox via RIA Services, but you could bind the ComboBox to any collection of data.

The ComboBox used in this example is the one from this prior post. To use it in this example, follow these steps:

1. Add a DomainDataSource for the codes that will populate the ComboBox.

2. Add the ComboBox and bind it to the DomainDataSource.

Before adding the ComboBox to the DataForm, let’s try the above steps adding the ComboBox directly to the UserControl.

In XAML:

<UserControl xmlns:dataFormToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"  x:Class="SLVB.CustomerSummaryUC"
    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:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls" 
    xmlns:domain="clr-namespace:SLVB.Web">

    <Grid x:Name="LayoutRoot" Background="BlanchedAlmond">
        <riaControls:DomainDataSource x:Name="CustomerSource"
               QueryName="GetCustomers" AutoLoad="True">
            <riaControls:DomainDataSource.DomainContext>
                <domain:CustomerContext/>
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>

        <riaControls:DomainDataSource x:Name="CustomerTypeSource" 
               QueryName="GetCustomerTypes" AutoLoad="True">
            <riaControls:DomainDataSource.DomainContext>
                <domain:CodeContext/>
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>

        <StackPanel Margin="5,5,200,5">
            <ComboBox ItemsSource=
                 "{Binding Data, ElementName=CustomerTypeSource}"
                  DisplayMemberPath="CodeText"/>
            <dataFormToolkit:DataForm
                  ItemsSource=
                  "{Binding Data, ElementName=CustomerSource}">
            </dataFormToolkit:DataForm>
        </StackPanel>
    </Grid>
</UserControl>

The CustomerTypeSource is the new DomainDataSource. The ComboBox binds to this data source and displays the customer types.

The results are as follows:

image

The ComboBox appears above the DataForm and partially covers it when it is open. So now we know that the ComboBox works. The next step is to insert it instead into the DataForm.

There is no way to change the type of one field on the DataForm without then specifying every field on the DataForm. So if we want to specify that the Customer Type is a ComboBox, we can no longer use the auto field generation and must instead manually define every field.

The DataForm XAML code is then significantly longer.

In XAML:

<StackPanel Margin="5,5,200,5">
    <dataFormToolkit:DataForm
              ItemsSource="{Binding Data, ElementName=CustomerSource}">
        <dataFormToolkit:DataForm.EditTemplate>
            <DataTemplate>
                <StackPanel
                       dataFormToolkit:DataField.IsFieldGroup="True">
                    <dataFormToolkit:DataField>
                        <TextBox Text=
                           "{Binding FirstName, Mode=TwoWay}" />
                    </dataFormToolkit:DataField>
                    <dataFormToolkit:DataField>
                        <TextBox Text=
                           "{Binding LastName, Mode=TwoWay}" />
                    </dataFormToolkit:DataField>
                    <dataFormToolkit:DataField>
                        <ComboBox ItemsSource=
                      "{Binding Data, ElementName=CustomerTypeSource}"
                      DisplayMemberPath="CodeText"/>
                    </dataFormToolkit:DataField>
                    <dataFormToolkit:DataField>
                        <TextBox Text=
                           "{Binding EmailAddress, Mode=TwoWay}" />
                    </dataFormToolkit:DataField>
                </StackPanel>
            </DataTemplate>
        </dataFormToolkit:DataForm.EditTemplate>
    </dataFormToolkit:DataForm>
</StackPanel>

A DataField element defines each field on the form. Within the DataField element is the control to display for that data field. In most case, this is a TextBox control. The Text property of the TextBox is bound to the appropriate field in the data source. The mode is TwoWay to provide review and edit.

So far, the ComboBox code is the same code defined earlier when the ComboBox was above the DataForm. But now it is in the desired location within the DataForm.

Let’s give this a try and see what we have:

image

Well, the ComboBox is there … but it is empty. We simply copied the working ComboBox from above the DataForm to inside the DataForm and now it no longer populates. Hmmm.

After spending several hours Bing’ing about this … I ran across this post and thought it might be relevant. But instead of building a proxy, I thought I would just move the DomainDataSource into a UserControl resource.

So I removed the CustomerTypeSource DomainDataSource from under the Grid element and instead added it to a resources section. I then changed the x:Name to x:Key.

In XAML:

<UserControl.Resources>
    <riaControls:DomainDataSource x:Key="CustomerTypeSource"
                   QueryName="GetCustomerTypes"
                   AutoLoad="True">
        <riaControls:DomainDataSource.DomainContext>
            <domain:CodeContext/>
        </riaControls:DomainDataSource.DomainContext>
    </riaControls:DomainDataSource>
</UserControl.Resources>

This required changing the binding on the ComboBox to use this as a StaticResource. The other change was setting the SelectedItem to the CustomerTypeId.

In XAML:

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

And the result:

image

Whohoo!!

Now the only problem is that the SelectedItem is not correct. Regardless of the customer type for a Customer, the SelectedValue is always set to the first item on the list. How do we fix this? Yes, another cliff.

The data bound to the ComboBox has both a display member (the CodeText) and a value member (the CodeId). We want to bind the CustomerTypeId property to the Code Id.

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 Converters.

The fact that the ComboBox is missing a ValueMemberPath seems like a bug that I hope will be corrected.

But I have to start baking Thanksgiving pies. So more on this in a future post.

Happy Thanksgiving!

Enjoy!

18 Comments

  1.   jess — November 18, 2010 @ 12:23 pm    Reply

    can u show how to insert that in resources. kinda new in SL :D

  2.   Ivan — December 10, 2010 @ 7:36 pm    Reply

    Thanks this is jus what i needed and it works

  3.   Zack — January 28, 2011 @ 10:40 pm    Reply

    This is excellent! Just what I was looking for… have you ever tried to do:
    2 combo boxes
    1 data form
    2 separate DomainDataSources

    This is reflective of my underlying data model where I have one table with two lookups represented as combo boxes. It’s probably something in my setup, but I get an error that randomly barfs on binding.

    Entity ‘UserAccount : 1′ cannot be attached to this EntityContainer because it is already attached to another EntityContainer.

    Great post! Just curious though if you’ve ever had success in doing what I’m trying.

  4.   Shimmy — February 14, 2011 @ 3:50 pm    Reply

    And how would you do that with MVVM??

  5.   rojorm — February 20, 2011 @ 3:26 am    Reply

    here http://stackoverflow.com/questions/5042596/databinding-combobox-in-dataform-silverlight-using-mvvm-in-update i have silimar question, DomainDataSources is good, but MVVM approach is better. How to do that using MVVM?

  6.   Imran Khan — August 11, 2011 @ 4:48 am    Reply

    i m working on it but not abling to add
    xmlns:domain=”clr-namespace:SLVB.Web”

    please help me how can i do this

  7.   Water Damage Restoration — September 15, 2011 @ 8:30 am    Reply

    It doesn’t update the database after performing a dataform edit…

  8.   Peter Klein — June 28, 2012 @ 6:01 am    Reply

    Hi Deborah,
    Your article on this subject helped me to get on the right track. Your suggestion to move the RIAControl to local Resources was a helpful suggestion. Based on that suggestion I have been experimenting further and found this: http://stackoverflow.com/questions/4902039/difference-between-selecteditem-selectedvalue-and-selectedvaluepath

    That led to the conclusion that there MIGHT be an option to get it running. And… indeed there is. This is the relevant part of my XAML:

    I’m sorry for not having translated the example into your LOTR figures…

    Peter

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