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.