Ostatnio napotkałem problem stworzenia menu zawierającego zarówno dynamiczne elementy (binding) jak i statyczne. Okazało się, że wcale nie jest to takie proste jak to jest w przypadku większości rzeczy w WPF. Zacznijmy jednak od przedstawienia sposobów tworzenia menu w WPF. Pierwszy to oczywiste statyczne menu, w całości zdefiniowane w XAML:
<Menu Height="23" Name="menu1" Width="200"> <MenuItem Header="Plik"> <MenuItem Header="statyczny tekst 1"/> <MenuItem Header="statyczny tekst 2"/> <MenuItem Header="statyczny tekst 3"/> </MenuItem> </Menu>
W powyższym kodzie nie ma nic nadzwyczajnego. Sytuacja jest również łatwa gdy chcemy w całości zdefiniować menu jako dynamiczne:
<Menu Height="23" Name="menu1" Width="200"> <MenuItem Header="Plik" ItemsSource="{Binding Items}"> <MenuItem.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding DisplayName}" /> </DataTemplate> </MenuItem.ItemTemplate> <MenuItem.ItemContainerStyle> <Style TargetType="MenuItem"> <Setter Property="Command" Value="{Binding ShowCmd}" /> </Style> </MenuItem.ItemContainerStyle> </MenuItem> </Menu>
Gdzie pojedynczy item to:
public class Item { public string DisplayName { get; set; } public ICommand ShowCmd { get; set; } }
Co jednak w przypadku gdy chcemy aby menu Plik zawierało część statycznych podmenu zdefiniowanych w XAML a część pochodziła z kolekcji (na dodatek z różnych kolekcji)? Sytuacja się trochę komplikuje ale możemy rozwiązań problem za pomocą CollectionContainer:
<Menu Height="23" Name="menu1" Width="200"> <Menu.Resources> <CollectionViewSource x:Key="dynamicItems" Source="{Binding Items}"/> </Menu.Resources> <MenuItem Header="Plik"> <MenuItem.ItemsSource> <CompositeCollection> <MenuItem Header="Statyczny element 1"/> <MenuItem Header="Statyczny element 2"/> <CollectionContainer Collection="{Binding Source={StaticResource dynamicItems }}"/> </CompositeCollection> </MenuItem.ItemsSource> </MenuItem> </Menu>
Przede wszystkim przenieśliśmy elementy do ItemsSource. CompositeCollection pozwala zdefiniować kolekcje menu składającej się z statycznych i dynamicznych elementów. Dynamiczne pozycje zdefiniowane są za pomocą CollectionContainer, który wykorzystuje jako źródło danych CollectionViewSource, który z kolei powiązany jest z Items. Po uruchomieniu powyższego przykładu zobaczymy menu składające się z różnych submenu ale niestety musimy jeszcze określić jaka właściwość powinna być wyświetlana oraz jaka komenda z klasy Item powinna zostać powiązana:
<MenuItem Header="Plik"> <MenuItem.Resources> <Style TargetType="{x:Type MenuItem}"> <Style.Triggers> <DataTrigger Binding="{Binding Converter={StaticResource objectToTypeConverter}}" Value="{x:Type itemsNamespace:Item }"> <Setter Property="Header" Value="{Binding DisplayName}"/> <Setter Property="Command" Value="{Binding ShowCmd}"/> </DataTrigger> </Style.Triggers> </Style> <CollectionViewSource x:Key="dynamicItems" Source="{Binding Items}"/> </MenuItem.Resources> <MenuItem.ItemsSource> <CompositeCollection> <CollectionContainer Collection="{Binding Source={StaticResource dynamicItems }}"/> <MenuItem Header="Statyczny element 1"/> <MenuItem Header="Statyczny element 2"/> </CompositeCollection> </MenuItem.ItemsSource> </MenuItem>
Za pomocą DataTrigger sprawdzamy czy typ DataContext jest równy typeof(Item). Jeśli tak to znaczy, że jest to element dynamiczny powiązany z kolekcją Items i możemy ustawić dowolne właściwości(w tym komendę i header). Na zakończenie konwerter objectToTypeConverter:
public class ObjectToTypeConverter:IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value == null) return null; else return value.GetType(); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion }
Bardzo ciekawy przykład 😉 Fajnie byłoby, gdybyś do postów dotyczących zagadnień UI dodawał jakieś graficzne przykłady np menu opisanego w aktualnym poście 😉
@netmajor:
OKI, nastepnym razem postaram sie cos dodac:)