SilverLight – ListBox mit multiplen ListBoxItem Templates
Manchmal macht es Sinn die ListBoxItems in der ListBox unterschiedlich darzustellen. Um dies zu machen müsste man das Template beim Erstellen eines ListBoxItems zuweisen. Das ganze kann man leicht mit einem einfachen ValueConverter erreichen.
Angenommen wir haben in unseren Model eine Eigenschaft anhand deren wir das Template wählen möchten. Falls man Objekte mit unterschiedlichen Typen bindet und anhand des Typen das Template wählen möchte dann bindet man das ganze Objekt an die Style Eigenschaft des ListBoxItems und passt den Converter dem entsprechend an.
public enum ItemType { Default, Editable } public class ItemViewModel : INotifyPropertyChanged { private ItemType _type; public ItemType ItemType { get { return _type; } set { if (value != _type) { _type = value; NotifyPropertyChanged("ItemType"); } } } ...
Damit man aus dem ValueConverter der kein FrameworkElement ist trozdem Ressourcen findet brauchen wir eine Helper Klasse die nach Ressourcen sucht. Man könnte es auch weglassen aber ich finde die Klasse ziemlich hilfreich.
public static class ResourceHelper { public static object FindResource(string name) { if (App.Current.Resources.Contains(name)) return App.Current.Resources[name]; else { FrameworkElement root = App.Current.RootVisual as FrameworkElement; return root.FindResource(name); } } internal static object FindResource(this FrameworkElement root, string name) { if (root != null && root.Resources.Contains(name)) return root.Resources[name]; else { try { return root.FindName(name); } catch { } } return null; } }
Der ValueConverter konvertiert den gebundenen Wert in die Ressource. Die ConvertBack Methode braucht man in diesem Fall nicht.
public class ListBoxItemTemplateConverter : IValueConverter { public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) { return ResourceHelper.FindResource(string.Format("ListBoxItem{0}Style", value.ToString())); } public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new System.NotImplementedException(); } }
Jetzt muss man die eigentlichen Ressourcen definieren, es sind Styles mit dem TargetType ListBoxItem
<local:ListBoxItemTemplateConverter x:Key="ListBoxItemTemplateConverter"/> <Style x:Key="ListBoxItemDefaultStyle" TargetType="ListBoxItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBoxItem"> <StackPanel Margin="0,0,0,17" Width="432" > <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/> <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="ListBoxItemEditableStyle" TargetType="ListBoxItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBoxItem"> <StackPanel Margin="0,0,0,17" Width="432" > <TextBox Text="{Binding LineOne, Mode=TwoWay}" /> <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style>
Und jetzt kommt der eigentliche Trick, man erstellt ein DataTemplate in der ein ListBoxItem ist dessen Style Eigenschaft über den ValueConverter an die Eigenschaft des Models gebunden ist. Der ValueConverter gibt dann einfach die passende Ressource zurück. Dieser Trick klappt ohne Probleme in Blend.
<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="MainListBox_SelectionChanged"> <ListBox.ItemTemplate> <DataTemplate> <!-- is blendable if you use sample data --> <ListBoxItem Style="{Binding Path=ItemType, Converter={StaticResource ListBoxItemTemplateConverter}}" /> <!-- Bind a item --> <!--<ListBoxItem Style="{Binding Converter={StaticResource ListBoxItemTemplateConverter}}" /> --> <!-- Works only after compiling --> <!--<ListBoxItem Style="{Binding Path=ListBoxItemStyle}" /> --> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
Genau so was habe ich gesucht !
Danke fürs posten !
Allerdings gestaltet sich das ganze schwierig wenn man z.B. besagte Listbox in einem eigenen UserControl hat und man eine Liste von verschiedenen Objekten befüllen möchte.
Denn dann wird das ganze zu früh ausgeführt, und die Objekt-Liste welche man anzeigen möchte ist natürlich leer.
Hattest du auch so eine Problematik? Hättest du da evtl. eine Idee wie man das angehen könnte (PreLoader oder so)?
Gruss & Danke
Mike
Kommentar von Mike — 21. Juni 2011 @ %H:%M
Ich muss für ein Paar eigene Projekte ähnliches bauen, da könnte dann dieses Szenario vorkommen. Wenn ich gelöst habe werde ich darüber Posten.
PS: Sorry, das Email-Konto auf welches mir mein Blog Mails sendet hatte ich gelöscht daher habe ich erst jetzt gemerkt dass du geschrieben hast.
Kommentar von aruss — 17. August 2011 @ %H:%M