ComboBox w DataGrid oraz brak wsparcia SelectedValue w Silverlight

Siliverlight niestety nie jeszcze tak potężną biblioteką jak WPF. Czasami proste rzeczy wymagają większego nakładu pracy niż w przypadku tworzenia aplikacji w WPF.

Rozważmy sytuację, w której mamy listę telefonów do danej osoby.Telefon definiujemy poprzez jego numer oraz przydzieloną kategorię(dom, praca itp). W DataGrid będą więc 2 kolumny: jedna typu TextBox dla numeru oraz druga typu ComboBox dla kategorii. DataGrid będzie przedstawiał listę telefonów opisanych klasą Phone:

class Phone
{
    public string Value{get;set;}
    public PhoneType PhoneType{get;set;}
}

gdzie PhoneType to:

class PhoneType
{
    public string Value{get;set;}// nazwa kategorii
    public Guid ID_PHONE_TYPE{get;set;}
}

Zrzut z gotowej kontrolki DataGrid:

Clipboard01

W wpf kod byłby bardzo prosty a mianowicie:

<data:DataGrid   Name="m_Phones" ItemsSource="{Binding Path=Contact.Phones}" AutoGenerateColumns="False">
    <data:DataGrid.Columns>
                           
        <data:DataGridTextColumn Header="Phone" Binding="{Binding Path=Value}"/>
                            
            <data:DataGridTemplateColumn Header="PhoneGroup">
                <data:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox SelectedValuePath="ID_PHONE_TYPE" SelectedValue="{Binding Path=ID_PHONE_TYPE"  DisplayMemberPath="Value" ItemsSource="{StaticResource AllPhoneTypes}">
                           </ComboBox>
                    </DataTemplate>
                </data:DataGridTemplateColumn.CellTemplate>

        </data:DataGridTemplateColumn>

    </data:DataGrid.Columns>
</data:DataGrid>

SelectedValuePath wskazuje na identyfikator item’a w ComboBox. SelectedValue służy do zaznaczania aktualnej kategorii.Wszystko w wpf ładnie będzie nam się odświeżać. Załadowane telefony automatycznie będą zaznaczać kategorię w ComboBox.

Niestety w Silverlight brakuje właściwości SelectedValue oraz SelectedValuePath.Wszystko co możemy zrobić za pomocą xaml to:

<data:DataGrid   Name="m_Phones" ItemsSource="{Binding Path=Contact.Phones}" AutoGenerateColumns="False">
    <data:DataGrid.Columns>
                           
        <data:DataGridTextColumn Header="Phone" Binding="{Binding Path=Value}"/>
                            
            <data:DataGridTemplateColumn Header="PhoneGroup">
                <data:DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox DisplayMemberPath="Value"    ItemsSource="{StaticResource AllPhoneTypes}">
                           </ComboBox>
                    </DataTemplate>
                </data:DataGridTemplateColumn.CellTemplate>

        </data:DataGridTemplateColumn>

    </data:DataGrid.Columns>
</data:DataGrid>

W SL do dyspozycji mamy wyłącznie właściwość SelectedItem jeśli chodzi o zaznaczanie pozycji w ComboBox. Niestety ItemsSource dla ComboBox pochodzi z innego źródła niż aktualnie wybrana kategoria. Nie możemy zatem użyć poniższego kodu:

<ComboBox SelectedItem="{Binding Path=PhoneType}" DisplayMemberPath="Value"  ItemsSource="{StaticResource AllPhoneTypes}"/>

Musimy powiązać SelectedItem za pomocą ID_PHONE_TYPE a nie instancji klasy PhoneType.Problem polega na tym, że SelectedItem spodziewa się typu PhoneType a nie GUID. Co więc możemy zrobić?

Na szczęście  SilverLight wspiera konwertery IValueConverter. Umożliwiają one podczas bindingu zamianę jednej wartości na drugą. W naszym przypadku zamienimy GUID na klasę PhoneType. Zatem do dzieła, zacznijmy od implementacji tego konwertera:

public class PhoneTypeConverter : IValueConverter
{
    public ObservableCollection<PhoneType> ItemsSource { get; set; }
    
    public object Convert(object value,
    System.Type targetType,
    object parameter,
    System.Globalization.CultureInfo culture)
    {
        Guid phoneTypeId = (Guid)value;
        PhoneType phoneType = null;
        
        foreach (PhoneType item in ItemsSource)
        {
            if (item.ID_PHONE_TYPE == phoneTypeId)
            {
                phoneType = item;
            }
        }
        return phoneType;
    }
        
    public object ConvertBack(object value,
    System.Type targetType, object parameter,
    System.Globalization.CultureInfo culture)
    {
        PhoneType phoneType = value as PhoneType;
        Guid phoneTypeId = Guid.Empty;
        phoneTypeId = phoneType.ID_PHONE_TYPE;
        return phoneTypeId;
    }
}

Konwerter zawiera dwie metody Convert(Guid –> PhoneType) oraz ConvertBack(PhoneType –> Guid).Teraz możemy już użyć SelectedItem:

<ComboBox SelectedItem="{Binding Path=ID_PHONE_TYPE,Converter={StaticResource PhoneTypeConverter}}" DisplayMemberPath="Value"  ItemsSource="{StaticResource AllPhoneTypes}"/>

Konwerter zamieni Guid(ID_PHONE_TYPE) na odpowiednią  instancję PhoneType, która jest już podłączona do ItemsSource. Ostatnią sprawą jest zdefiniowanie zasobu PhoneTypeConverter:

<UserControl.Resources>
   <local:PhoneTypeConverter x:Key="PhoneTypeConverter" ItemsSource={StaticResource AllPhoneTypes} />        
</UserControl.Resources>

Takim to sposobem poradziliśmy sobie z brakiem wsparcia dla SelectedValue w Silverlight. Mam nadzieję, że oszczędziłem komuś męczarni z tym – samemu sporo sie nakombinowałem zanim udało mi się osiągnąć zamierzony  efekt.Dodam, że SilverLight 4 Beta posiada już właściwość SelectedValue więc takie scenariusze będą łatwiejsze w implementacji.

Leave a Reply

Your email address will not be published.