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