Własności klasy ItemsControl:
• ItemsSource – źródło danych (kolekcja lub DataView)
• DisplayMemberPath – wskazuje, którą własność elementu wyświetlić
• ItemTemplate – szablon elementu
• ItemTemplateSelector – klasa dokonująca wyboru szablonu elementu
• ItemContainerStyle – styl kontenera otaczającego pojedynczy element
• ItemContainerStyleSelector
• ItemsPanel – panel, który przechowuje wszystkie elementy listy (zazwyczaj:
pionowy StackPanel)
• GroupStyle – styl formatowania grupy
• GroupStyleSelector
• Klasa Selector
◦ dodaje możliwość wybierania elementu (ToolBar czy Menu tego nie mają)
◦ własności: SelectedItem, SelectedIndex, SelectedValue (własność Value wybranego obiektu danych, zgodnie z SelectedValuePath)
◦ selector nie daje obsługi wielokrotnego wyboru – to jest dodane w ListBox, przez SelectionMode i SelectedItems
◦ zdarzenie SelectionChanged
• TreeView też pozwala wybierać elementy, ale (z powodu hierarchicznej struktury) działa to inaczej, choć własności nazywają się tak samo: SelectedItem,
SelectedValue, SelectedValue path properties. Do tego zdarzenie SelectedItemChanged.
ComboBox
• Składa się z dwóch części: okna wyboru (ukazuje aktualny wybór) i listy rozwijalnej (wszystkie możliwe wybory).
• Ustawienie IsEditable na true zmienia działanie:
◦ w okienku wyboru wyświetlany jest jedynie tekst zwrócony przez ToString
◦ użytkownik może wpisać tu jakąś wartość
◦ autouzupełnianie
<ComboBox IsEditable="True" ... />
ListView
• Umożliwia wyświetlanie różnych widoków do tych samych danych.
• Zwłaszcza przydaje się do tworzenia widoku wielokolumnowego.
• Dziedziczy z ListBox, dodając jedną własność: View.
• Widok to obiekt dziedziczący z ViewBase – klasy przechowującej wskazanie na style: styl kontrolki listy (DefaultStyleKey) i styl elementu listy
(ItemContainerDefaultStyleKey).
• Wydzielenie informacji o widoku daje lepszą organizację, pozwala np.
wykorzystywać ten sam widok w różnych listach lub używać zamienni kilku widoków w jednej liście.
• WPF dostarcza jeden gotowy widok: GridView. Mamy też możliwość tworzenia własnych.
• Dodawanie kolumn: kolekcja GridView.Columns. Header to nagłówek kolumny, a DisplayMemberBinding – wiązanie obiektem danych.
<ListView Name="lstBooks" Margin="3">
<ListView.View>
<GridView>
</ListView.View>
</ListView>
• Oczywiście wszystkie rzeczy, które działały dla list (konwertery, szablony) mogą być tu stosowane.
• Użytkownik ma możliwość zmiany kolejności kolumn i zmiany ich rozmiaru (domyślnie rozmiar jest ustalany na podstawie zawartości, o ile nie określimy ręcznie własności Width).
Szablony komórek
• Podobnie jak szablony danych dla ListBoxa, ale działa tylko dla jednej kolumny.
<GridViewColumn Header="Tytuł" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Title}"
TextWrapping="Wrap"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
• W ten sam sposób możemy umieścić w kolumnach dowolne elementy, np. obrazek z poprzedniego wykładu (ImagePathConverter).
• Ponieważ szablony kolumn nie mogą być używane w innym miejscu, przeważnie definiuje się je inline.
• GridViewColumn.CellTemplateSelector pozwala na ustawienie selectora szablonu.
• GridViewColumn.Header pozwala na zdefiniowanie wyglądu nagłówka;
GridViewColumn.HeaderTemplate pozwala na wybór szablonu dla nagłówka lub GridView.ColumnHeaderTemplate na wybór jednego szablonu dla wszystkich.
DataGrid
• Oferuje wielokolumnowy widok z edycją.
• Przeznaczony do wyświetlania całych tabel danych.
• Podstawowe wykorzystanie jest bardzo proste:
<DataGrid Name="data"/>
• I w kodzie:
data.ItemsSource = books;
• RowDetailsTemplate pozwala na prezentację dodatkowych informacji w wierszu:
<DataGrid Name="data" SelectionMode="Single">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<TextBlock Margin="10" FontWeight="Bold"
Text="{Binding Price}"/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
• Wypełnienie kolekcji DataGrid.Columns pozwala na własne definicje kolumn:
<DataGrid Name="data" SelectionMode="Single"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Title"
Binding="{Binding Title}"/>
<DataGridTextColumn Header="Author"
Binding="{Binding Author}"/>
</DataGrid.Columns>
</DataGrid>
TreeView
• Wspiera wiązanie danych.
• TreeView to specjalizowana kontrolka listy, która zawiera elementy TreeViewItem.
• TreeViewItem to nie kontrolka zawartości, ale kolejna kontrolka listy, która może zawierać kolejne elementy TreeViewItem.
• TreeViewItem dziedziczy z HeaderedItemsControl, która dodaje własność Header.
• Prosty przykład ręcznej definicji drzewa:
<TreeView>
<TreeViewItem Header="Filmy">
<TreeViewItem Header="Komedie"/>
<TreeViewItem Header="Dramaty"/>
<TreeViewItem Header="Seriale"/>
</TreeViewItem>
<TreeViewItem Header="Książki">
<TreeViewItem Header="Fantasy"/>
<TreeViewItem Header="Science fiction"/>
<TreeViewItem Header="Horror"/>
</TreeViewItem>
</TreeView>
• Podobnie, jak w wypadku ListBox, nie musimy budować drzewa z elementów TreeViewItem. Jednak przeważnie najlepiej jest opakowywać swoje obiekty w TreeViewItem, a zawartość do wyświetlenia udostępniać przez
TreeViewItem.Header.
TreeView i wiązanie danych
• Podobnie, jak wypadku pozostałych kontrolek listy, wystarczy ustawić własność ItemsSource.
• Wypełnia to jednak tylko pierwszy poziom drzewa:
public class Category {
public string Name { get; set; } public Category(string name) {
Name = name;
} }
<Grid>
<TreeView Name="drzewo" DisplayMemberPath="Name" />
</Grid>
private void Window_Loaded(object sender, RoutedEventArgs e) {
List<Category> lst = new List<Category>();
lst.Add(new Category("Fantasy"));
lst.Add(new Category("Science fiction"));
lst.Add(new Category("Horror"));
drzewo.ItemsSource = lst;
}
• Dodajmy książki do kategorii:
public class Category {
public string Name { get; set; } public Category(string name) {
Name = name;
books = new List<Book>();
}
private List<Book> books;
public List<Book> Books {
get { return books; } }
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
...
lst[0].Books.Add(new Book("Gra o tron",
"George R.R. Martin", 45M));
lst[0].Books.Add(new Book("Straż nocna",
"Terry Pratchett", 27.5M));
lst[0].Books.Add(new Book("Czarnoksiężnik z Archipelagu", "Ursula K. Le Guin", 19.9M));
...
}
• Aby wyświetlić zawartość kategorii, potrzebny jest szablon, który może obsłużyć dane hierarchiczne:
<TreeView Name="drzewo">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
• To wyświetli tylko nazwę kategorii.
• HierarchicalDataTemplate może zawierać kolejny szablon – a kolekcja elementów z pierwszego poziomu może być przekazana do kolejnego poziomu.
• Własność ItemsSource określa własność, która zawiera elementy dzieci.
<TreeView Name="drzewo">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Path=Books}">
<TextBlock Text="{Binding Path=Name}" />
<HierarchicalDataTemplate.ItemTemplate >
<DataTemplate>
<TextBlock Text="{Binding Path=Title}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
• Drzewo posługuje się teraz dwoma osobnymi szablonami dla pierwszego i drugiego poziomu.
• Drugi szablon wykorzystuje wybrany element pierwszego, jako źródło danych.
• Szablony możemy też przenieść do zasobów i wybierać na podstawie typów, a nie zagnieżdżenia:
<Window.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:Category}"
ItemsSource="{Binding Path=Books}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:Book}">
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView Name="drzewo"> </TreeView>
</Grid>
• Pozwala to również na kolejne zagnieżdżenia elementów w drzewie. Np. tak:
public class Category {
public string Name { get; set; } public Category(string name) {
Name = name;
books = new List<object>();
}
private List<object> books;
public List<object> Books {
get { return books; } }
}
...
lst[1].Books.Add(new Category("podkategoria"));
((Category)lst[1].Books[3]).Books.Add(new Book(...));
((Category)lst[1].Books[3]).Books.Add(new Book(...));
• Tworzenie gałęzi na żądanie:
◦ Jeśli drzewo zawiera dużą ilość danych, możemy nie wypełniać od razu wszystkich podkategorii, a odwlec to do czasu rozwinięcia gałezi przez użytkownika.
◦ TreeViewItem odpala zdarzenia Expanded i Collapsed, które na o tym informują. Można to wykorzystać, by wypełnić listę danymi.
◦ Początkowo, każda kategoria powinna zawierać jakiś wypełniacz, aby wyświetlony został plus, który umożliwi jej rozwinięcie.
◦ W obsłudze zdarzenia Expanded możemy wczytać potrzebne dane i wypełnić
Menu
• Mamy dostępne dwie kontrolki: Menu (menu aplikacji) i ContextMenu (menu kontekstowe).
• Menu:
◦ Podlega zwyczajnym regułom rozmieszczania elementów (przeważnie jest na szczycie DockPanela lub w pierwszym wierszu Grida).
◦ Można dodać dowolną liczbę menu.
◦ Własność IsMainMenu ustawiona na true powoduje, że menu dostanie focusa po naciśnięciu klawisza Alt lub F10.
◦ Menu zachowało wszelkie cechy kontrolek listy (wiązanie danych, szablony danych, grupowanie, style itemów, etc.)
MenuItem
• Menu składa się z obiektów klas MenuItem oraz Separator.
• Klasa MenuItem dziedziczy z HeaderedItemsControl.
• Separator to po prostu pozioma linia, nie reagująca na wejście użytkownika.
• Menu możemy (teoretycznie) wypełniać dowolnymi elementami (ale lepiej nie przesadzać).
<Menu DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="New"></MenuItem>
<MenuItem Header="Open"></MenuItem>
<MenuItem Header="Save"></MenuItem>
<Separator></Separator>
<MenuItem Header="Exit"></MenuItem>
</MenuItem>
<MenuItem Header="Edit">
<MenuItem Header="Undo"></MenuItem>
<MenuItem Header="Redo"></MenuItem>
<Separator></Separator>
<MenuItem Header="Cut"></MenuItem>
<MenuItem Header="Copy"></MenuItem>
<MenuItem Header="Paste"></MenuItem>
</MenuItem>
</Menu>
• Można używać znaku podkreślenia, aby oznaczyć skrót z Altem.
• Reakcja na kliknięcie w element Menu:
◦ zdarzenie MenuItem.Click (osobno dla każdego elementu lub jeden handler w korzeniu),
• MenuItem wyświetla:
◦ tekst z własności Header,
◦ ikonę: MenuItem.Icon (przyjmuje dowolny obiekt, co pozwala na dołączenie również grafiki wektorowej),
◦ checkmark: własność MenuItem.IsChecked; jeśli ustawimy dodatkowo IsCheckable, kliknięcie na menu zmieni ten stan (niestety, nie ma automatycznej obsługi grup),
◦ skrót klawiaturowy: można go wyświetlić podając
MenuItem.InputGestureText (to jest tylko tekst, nie daje żadnej obsługi – tu lepiej polegać na Commands, jeśli nie chcemy robić tego ręcznie).
<MenuItem Command="Open"></MenuItem>
• MenuItem pozwala na sprawdzenie stanu: IsChecked, IsHighlighted, IsPressed, IsSubmenuOpen. Możemy wykorzystać to np. w triggerach.
ContextMenu
• Podobnie jak Menu, przechowuje kolekcję obiektów MenuItem.
• Jedyna różnica – nie może być umieszczone w oknie.
• Służy do ustawienia własności ContextMenu innych elementów (pokazuje się na prawy przycisk myszy lub Shift+F10):
<TreeView ...>
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Command="Undo"/>
<MenuItem Command="Redo"/>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
• Menu kontekstowe domyślnie nie pokazuje się, jeśli element jest wyszarzony (isEnabled równe False), ale można to zmienić ustawiając
ContextMenuService.ShowOnDisabled na True.
Separator
• Służy do dzielenia elementów na grupy.
• Zawartość separatora można zmieniać przy pomocy szablonów – pozwala to definiować własne, nieklikalne elementy menu (nawet jeśli dodamy do menu wrzucimy element nie będący MenuItem, to będzie się zachowywał jak one;
Separator nie – np. nie jest podświetlany).
<Separator>
<Separator.Template>
<ControlTemplate>
</Separator.Template>
</Separator>
• Niestety, Separator to nie kontrolka zawartości, zatem nie można oddzielić zawartości od formatowania i zawartość musi siedzieć w szablonie.
Toolbar i StatusBar
• Standardowo pasek narzędzi przechowuje przyciski, a pasek statusu – tekst i kontrolki, z ktorymi użytkownik nie wchodzi w interakcję (np. pasek postępu).
• Nie ma elementów przeznaczonych specjalnie dla paska narzędzi czy statusu – można w nim umieszczać wszelkie podstawowe elementy WPF.
ToolBar
• Przeważnie zawiera obiekty typu Button, ComboBox, CheckBox, RadioButton i Separator.
• Button w toolbarze wygląda inaczej niż w oknie, gdyż toolbar nadpisuje domyślny styl przycisku.
• Własność Orientation pozwala na stworzenie pionowego toolbara.
<ToolBar DockPanel.Dock="Top">
<CheckBox FontWeight="Bold">Bold</CheckBox>
<CheckBox FontStyle="Italic">Italic</CheckBox>
<CheckBox>
<TextBlock TextDecorations="Underline">
Underline </TextBlock>
</CheckBox>
<Separator></Separator>
<ComboBox SelectedIndex="1">
<ComboBoxItem>120%</ComboBoxItem>
<ComboBoxItem>100%</ComboBoxItem>
<ComboBoxItem>80%</ComboBoxItem>
</ComboBox>
<Separator></Separator>
• Gdy wszystkie elementy paska narzędzi nie mieszczą się w oknie, zostają przeniesione do rozwijalnego menu.
• Elementy trafiają automatycznie, ale możemy wskazać (przy pomocy własności dołączonej ToolBar.OverflowMode), jakie elementy nie powinny tam trafić
(OverflowMode.Never), a jakie powinny być tam zawsze (OverflowMode.Always)
ToolBarTray
• Przechowuje kolekcję toolbarów (własność ToolBars) i daje możliwość ich przemieszczania przez użytkownika (o ile nie ustawiliśmy ToolBarTray.IsLocked na true).
• ToolBar przy pomocy własności Band może określić swoje położenie w ToolBarTray.
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button>One</Button> <Button>Two</Button>
<Button>Three</Button>
</ToolBar>
<ToolBar>
<Button>A</Button> <Button>B</Button>
<Button>C</Button>
</ToolBar>
<ToolBar Band="1">
<Button>Red</Button>
<Button>Blue</Button>
<Button>Green</Button>
<Button>Black</Button>
</ToolBar>
StatusBar
• Może przechowywać dowolną zawartość (która zostaje automatycznie opakowana przez StatusBarItem).
• Nadpisuje domyślne style niektórych elementów.
• Używany głównie do wyświetlania statycznej informacji.
• Domyślnie rozmieszcza elementy od lewej do prawej. Można podmienić mu panel, aby rozmieszczać w inny sposób.
<StatusBar DockPanel.Dock="Bottom">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<TextBlock>2/10</TextBlock>
<StatusBarItem Grid.Column="1">
<TextBlock>120%</TextBlock>
</StatusBarItem>