• Nie Znaleziono Wyników

Programowanie w technologii .NET wykład 8 – Style, listy, drzewa, toolbary, menu

N/A
N/A
Protected

Academic year: 2021

Share "Programowanie w technologii .NET wykład 8 – Style, listy, drzewa, toolbary, menu"

Copied!
70
0
0

Pełen tekst

(1)

Programowanie w technologii .NET

wykład 8 – Style, listy, drzewa, toolbary, menu

Podstawy

• Chcąc wielokrotnie wykorzystać pewne ustawienia stylu, może zdefiniować je w zasobach:

<Window.Resources>

<FontWeight x:Key="ButtonFontWeight">

Bold

</FontWeight>

<SolidColorBrush x:Key="ButtonBackground">

Orange

</SolidColorBrush>

</Window.Resources>

(2)

• A następnie z nich skorzystać, ustawiając własności elementów:

<Button Background="{StaticResource ButtonBackground}"

FontWeight="{StaticResource ButtonFontWeight}">

OK

</Button>

<Button Background="{StaticResource ButtonBackground}"

FontWeight="{StaticResource ButtonFontWeight}">

Anuluj

</Button>

• Problemy:

◦ Nic (poza nazwą) nie wskazuje, że te zasoby są powiązane i że powinniśmy używać ich łącznie.

◦ Korzystanie z takich zasobów wymaga dodatkowego kodu XAMLa, przez co staje się nieczytelne.

◦ Lepsze rozwiązanie, to zdefiniowanie Stylu – pojedynczy Style będzie zawierał

(3)

<Window.Resources>

<Style x:Key="OrangeButton">

<Setter Property="Control.Background" Value="Orange"/>

<Setter Property="Control.FontWeight" Value="Bold"/>

</Style>

</Window.Resources>

• Tworzymy obiekt klasy System.Windows.Style.

• Zawiera on kolekcję Setterów – jeden na własność, której wartość chcemy ustawić.

• Setter określa nazwę własności i wartość, jaką ma jej nadać.

Każdy element WPF może używać jednego Stylu (lub żadnego) – wybieramy go przez własność Style.

<Button Style="{StaticResource OrangeButton}">

OK

</Button>

<Button Style="{StaticResource OrangeButton}">

Anuluj

</Button>

• Możemy też ustawić to programistycznie:

(4)

Styl ustawia bazowy wygląd elementu – możemy to nadpisać, ustawiając daną własność w tym elemencie (przeważnie jednak, zamiast na tym polegać, będziemy definiować różne warianty stylów).

• Styl pozwala stworzyć grupę powiązanych ustawień i w łatwy sposób nadać je jakiemuś elementowi. Nie musimy się martwić, jakie własności on ustawia, ani wiedzieć, gdy się zmienia:

<Window.Resources>

<Style x:Key="OrangeButton">

<Setter Property="Control.Background" Value="Orange"/>

<Setter Property="Control.FontWeight" Value="Bold"/>

<Setter Property="Control.FontSize" Value="16"/>

<Setter Property="FrameworkElement.Margin" Value="3"/>

<Setter Property="Control.Padding" Value="10,3"/>

</Style>

</Window.Resources>

(5)

Własności klasy Style:

Setters – kolekcja Setterów i EventSetterów, ustawiających wartości własności i dołączających obsługę zdarzeń.

Triggers – pozwalają na automatyczną modyfikację stylu, np. w reakcji na zmianę jakiejś własności lub zdarzenie.

Resources – zasoby używane przez style dobrze jest definiować wewnątrz niego.

BasedOn – pozwala na dziedziczenie stylów.

TargetType – określa typ elementu, jakiego dotyczy styl; pozwala stworzyć styl, który będzie stosowany automatycznie do elementów określonego typu.

(6)

Tworzenie obiektu stylu

• Można je definiować w zasobach (na dowolnym poziomie: okna, aplikacji, kontenera, kontrolki).

• Można też ustawiać od razu w miejscu użycia:

<Button>

<Button.Style>

<Style>

<Setter .../>

<Setter .../>

</Style>

</Button.Style>

OK

</Button>

Wydaje się to mało użyteczne w wypadku setterów, ale bywa przydatne z triggerami.

(7)

Setters

• Styl zawiera kolekcję obiektów klasy Setter.

• Każdy Setter ustawia pojedynczą własność (tylko zależnościową!) w elemencie.

• Aby ustawić złożoną wartość, możemy (jak zwykle) zastąpić atrybut zagnieżdżeniem:

<Setter Property="Control.Background">

<Setter.Value>

<LinearGradientBrush>

<GradientStop Color="AliceBlue" Offset="0"/>

<GradientStop Color="SteelBlue" Offset="1"/>

</LinearGradientBrush>

</Setter.Value>

</Setter>

(8)

• Aby określić własność, którą chcemy ustawić, musimy podać zarówno jej nazwę jak i nazwę klasy, w której jest zawarta. Nie musi to być jednak nazwa klasy, w której jest ona zdefiniowana (może to być klasa potomna, która dziedziczy tę własność):

<Style x:Key="OrangeButton">

<Setter Property="Button.FontWeight" Value="Bold"/>

<Setter Property="Button.FontSize" Value="16"/>

</Style>

• Działanie się nie zmieni, bo nadal odwołujemy się do tej samej własności.

Pozwala to również ustawiać własności, które nie będą istnieć w elemencie używającym tego stylu.

(9)

• Poniższy przykład nie ustawi innego stylu dla przycisku, a innego dla pola tekstowego. Własności będą ustawione dwukrotnie, zatem w efekcie otrzymamy wynik ostatniego ustawienia. Dzieje się tak, mimo iż FontSize jest deklarowane osobno w Button i TextBlock – wskazują bowiem na tę samą własność

zależnościową.

<Style x:Key="DifferentStyles">

<Setter Property="Button.Foreground" Value="Blue"/>

<Setter Property="Button.FontSize" Value="18"/>

<Setter Property="TextBox.Foreground" Value="Green"/>

<Setter Property="TextBox.FontSize " Value="16"/>

</Style>

• Jeśli wszystkie własności, które ustawiamy, dotyczą jednego typu elementów, możemy ustawić TargetType stylu:

<Style x:Key="OrangeButton" TargetType="Button">

<Setter Property="Background" Value="Orange"/>

<Setter Property="FontWeight" Value="Bold"/>

</Style>

(10)

EventSetters

• Dowiązują do elementu obsługę zdarzeń.

<Style x:Key="OrangeButton">

...

<EventSetter Event="UIElement.MouseEnter"

Handler="element_MouseEnter"/>

<EventSetter Event="UIElement.MouseLeave"

Handler="element_MouseLeave"/>

</Style>

• Metody obsługi zdarzeń:

private void element_MouseEnter(...) {

((UIElement)sender).FontStyle = FontStyles.Italic;

}

private void element_MouseLeave(...) {

((Control)sender).ClearValue(Control.FontStyleProperty);

}

(11)

Zdarzenia MouseEnter i MouseLeave nie wykorzystują tunneling i bubbling, dlatego nie moglibyśmy ustawić ich np. w kontenerze dla grupy elementów.

EventSetter jest niezłym rozwiązaniem.

EventSettery są rzadko wykorzystywane w WPF – podobną funkcjonalność mogą zapewnić nam bowiem triggery.

(12)

Dziedziczenie stylów

• Możemy zdefiniować dowolną liczbę stylów na dowolnym poziomie.

• Ale jeden element może używać tylko jednego stylu.

• Z pomocą przychodzi dziedziczenie własności i dziedziczenie stylów.

• Niektóre własności są dziedziczone z kontenera (IsEnabled, IsVisible, Foreground, własności czcionek).

• Możemy też stworzyć nowy styl na podstawie innego (atrybut BasedOn):

<Window.Resources>

<Style x:Key="OrangeButton">

<Setter Property="Control.Background" Value="Orange"/>

<Setter Property="Control.Margin " Value="3"/>

<Setter Property="Control.Padding" Value="10,3"/>

</Style>

<Style x:Key="EmphasizedOrangeButton"

BasedOn="{StaticResource OrangeButton}">

<Setter Property="Control.FontWeight" Value="Bold"/>

</Style>

</Window.Resources>

(13)

• Jeśli ponownie ustawimy tę samą własność, setter potomny nadpisze wartość z settera bazowego.

Dziedziczenie stylów zwiększa złożoność, dlatego dobrze jest ograniczyć jego użycie do sytuacji gdy jeden styl jest pewnym wariantem drugiego. W pozostałych przypadkach lepiej jest tworzyć style z różnymi kombinacjami formatowania.

(14)

Automatyczne stosowanie stylów

• Aby automatycznie używać stylu do wszystkich elementów określonego typu, należy:

◦ ustawić własność TargetType,

◦ nie ustawiać klucza (WPF zrobi to za nas: x:Key="{x:Type Button}").

• Styl będzie automatycznie używany przez wszystkie elementy określonego typu w

dół drzewa elementów .

<Window.Resources>

<Style TargetType="Button">

<Setter Property="Background" Value="Orange"/>

<Setter Property="FontWeight" Value="Bold"/>

<Setter Property="Margin " Value="3"/>

<Setter Property="Padding" Value="10,3"/>

</Style>

</Window.Resources>

<WrapPanel>

<Button>OK</Button>

<Button Style="{x:Null}">Anuluj</Button>

</WrapPanel>

(15)

Przycisk Anuluj zastępuje automatyczny styl swoim własnym ustawieniem (w tym wypadku nullem, co po prostu powoduje usunięcie stylu).

W wypadku złożonych okien, kłopotliwe bywa śledzenie w jaki sposób ustawiana jest dana własność. Dlatego z automatycznych stylów lepiej korzystać oszczędnie, np. tylko aby ustawić jednakowy margines wszystkich Buttonów w kontenerze.

(16)

Triggers

• Pozwalają na automatyzację prostych zmian stylu (bez potrzeby pisania kodu) – np.

w reakcji na zdarzenie lub zmianę wartości.

• Każdy styl może mieć dowolną liczbę triggerów: obiektów klas dziedziczących z System.Windows.TriggerBase, zawartych w kolekcji Style.Triggers.

Triggery mogą być użyte bezpośrednio w elementach, z pominięciem Stylu, dzięki kolekcji FrameworkElement.Triggers.

Klasy dziedziczące z TriggerBase

Trigger – najprostszy z triggerów, reaguje na zmianę wartości własności zależnościowej.

MultiTrigger – jak wyżej, ale pozwala łączyć kilka warunków.

DataTrigger – podobny do Triggera, ale reaguje na zmianę w dowiązanych danych – nei wymaga własności zależnościowej.

MultiDataTrigger – jak wyżej, ale dla kilku warunków.

EventTrigger – uruchamiają określone akcji w reakcji na zdarzenie, służą np. do wyświetlania animacji.

(17)

Trigger

Może być dołączony do dowolnej własności zależnościwej (np. IsFocused, IsMouseOver, IsPressed).

• Określamy własność, która ma być obserwowana i wartość, jakiej oczekujemy.

• Gdy własność przyjmie tę wartość, uruchomione zostaną settery z kolekcji Trigger.Setters.

Nie jest możliwe stworzenie bardziej złożonych reguł: tylko proste porównanie wartości.

<Style TargetType="Button">

<Style.Setters>

<Setter Property="Background" Value="Orange"/>

<Setter Property="Margin " Value="3"/>

<Setter Property="Padding" Value="10,3"/>

</Style.Setters>

<Style.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter Property="Foreground" Value="White"/>

</Trigger>

</Style.Triggers>

</Style>

(18)

• Kolor znaków zmieni się, gdy wskaźnik myszy znajduje się nad przyciskiem.

Nie musimy deklarować „powrotu do poprzedniej wartości”: oryginalna wartość zostaje zapamiętana i własność do niej wraca, gdy trigger przestanie być aktywny.

Możemy stworzyć kilka triggerów do jednego elementu. Jeśli ustawiają one tę samą własność, wygrywa zawsze ostatni na naszej liście (niezależnie od czasu zdarzeń).

(19)

<Style TargetType="Button">

<Style.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter Property="Foreground" Value="White"/>

</Trigger>

<Trigger Property="IsFocused" Value="True">

<Setter Property="Foreground" Value="Yellow"/>

</Trigger>

<Trigger Property="IsPressed" Value="True">

<Setter Property="Foreground" Value="Magenta"/>

</Trigger>

</Style.Triggers>

</Style>

• Gdy naciskamy przycisk, wygrywa IsPressed.

(20)

Triggery można wykorzystać do informowania o błędzie:

• Pola z wiązaniem danych (i walidacją!):

<TextBox Text="{Binding Path=Imię,

ValidatesOnDataErrors=True}"/>

<TextBox Text="{Binding Path=Nazwisko, ValidatesOnDataErrors=True}"/>

• Trigger reaguje na Validation.HasError:

<Style TargetType="{x:Type TextBox}">

<Style.Triggers>

<Trigger Property="Validation.HasError" Value="True">

<Setter Property="Background" Value="Red"/>

<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},

Path=(Validation.Errors)[0].ErrorContent}"/>

</Trigger>

</Style.Triggers>

</Style>

(21)

MultiTrigger

Kolekcja Conditions pozwala zdefiniować szereg oczekiwanych wartości własności.

Trigger uruchamia się tylko wówczas, gdy wszystkie one są spełnione.

<Style TargetType="Button">

<Style.Triggers>

<MultiTrigger>

<MultiTrigger.Conditions>

<Condition Property="IsMouseOver" Value="True"/>

<Condition Property="IsFocused" Value="True"/>

</MultiTrigger.Conditions>

<MultiTrigger.Setters>

<Setter Property="Foreground" Value="White"/>

</MultiTrigger.Setters>

</MultiTrigger>

</Style.Triggers>

</Style>

Kolor tekstu zostanie ustawiony tylko gdy przycisk ma focusa, a wskaźnik myszy znajduje się nad nim.

(22)

EventTrigger

• Oczekuje na odpalenie konkretnego zdarzenia i uruchamia przewidzianą akcję.

Przeważnie służy to do włączania animacji.

• Przykład:

<Style TargetType="Button">

...

<Style.Triggers>

<EventTrigger RoutedEvent="Mouse.MouseEnter">

<EventTrigger.Actions>

<BeginStoryboard>

<Storyboard>

<DoubleAnimation Duration="0:0:0.2"

Storyboard.TargetProperty="FontSize"

To="22" />

</Storyboard>

</BeginStoryboard>

</EventTrigger.Actions>

</EventTrigger>

</Style.Triggers>

</Style>

(23)

Animacja zdefiniowana jest w Storyboardzie. Animacja sprowadza się do modyfikacji własności zależniościowych w określonym czasie.

W przeciwieństwie do Triggerów, nie ma automatycznego „powrotu” i musimy zadeklarować to sami:

<Style TargetType="Button">

...

<Style.Triggers>

...

<EventTrigger RoutedEvent="Mouse.MouseLeave">

<EventTrigger.Actions>

<BeginStoryboard>

<Storyboard>

<DoubleAnimation Duration="0:0:1"

Storyboard.TargetProperty="FontSize" />

</Storyboard>

</BeginStoryboard>

</EventTrigger.Actions>

</EventTrigger>

</Style.Triggers>

</Style>

Tym razem jednak nie podajemy docelowego rozmiaru czcionki, WPF uznaje

(24)

DataTrigger

• Jak triggery, ale nie wymagają własności zależnościowych.

<Window.Resources>

<Style x:Key="DangerStyle">

<Style.Triggers>

<DataTrigger Binding="{Binding

RelativeSource={RelativeSource Self}, Path=Text}" Value="EX">

<Setter Property="Control.Background"

Value="Orange"/>

</DataTrigger>

</Style.Triggers>

</Style>

</Window.Resources>

<StackPanel>

<TextBox Margin="3"

Style="{StaticResource DangerStyle}" />

</StackPanel>

(25)

Style w listach: przy pomocy ItemContainerStyle możemy wskazać styl, który będzie użyty do każdego elementu listy.

<ListBox ...>

<ListBox.ItemContainerStyle>

<Style>

<Setter Property="ListBoxItem.Background"

Value="LightSteelBlue" />

<Setter Property="ListBoxItem.Margin"

Value="5" />

<Setter Property="ListBoxItem.Padding"

Value="5" />

</Style>

</ListBox.ItemContainerStyle>

</ListBox>

(26)
(27)

• Przy pomocy triggerów możemy formatować wygląd elementu wybranego:

<ListBox.ItemContainerStyle>

<Style TargetType="{x:Type ListBoxItem}">

...

<Style.Triggers>

<Trigger Property="IsSelected" Value="True">

<Setter ... />

<Setter ... />

<Setter ... />

</Trigger>

</Style.Triggers>

</Style>

</ListBox.ItemContainerStyle>

(28)

Naprzemienne podświetlanie wierszy: AlternationCount i AlternationIndex.

<ListBox AlternationCount="2" ...>

<ListBox.ItemContainerStyle>

<Style TargetType="{x:Type ListBoxItem}">

<Setter Property="Background" Value="LightSteelBlue" />

<Setter Property="Margin" Value="5" />

<Setter Property="Padding" Value="5" />

<Style.Triggers>

<Trigger Property="ItemsControl.AlternationIndex"

Value="1">

<Setter Property="Background" Value="LightBlue" />

</Trigger>

</Style.Triggers>

</Style>

</ListBox.ItemContainerStyle>

</ListBox>

(29)

StyleSelector – gdy chcemy nadać różny styl bazując na bardziej złożonych warunkach. Działa bardzo podobnie, jak DataTemplateSelector.

(30)

• DataTriggery można wykorzystać również do warunkowego formatowania w szablonach danych:

<DataTemplate x:Key="DefaultBookTemplate">

<DataTemplate.Triggers>

<DataTrigger Binding="{Binding Path=CategoryName}"

Value="Horror">

<Setter Property="Control.Foreground"

Value="Red" />

</DataTrigger>

</DataTemplate.Triggers>

<Border ...>

<Grid ...>

...

</Grid>

</Border>

</DataTemplate>

(31)

Wady – zmieniamy pojedyncze własności, testujemy tylko pojedyncze warunki.

(32)

DataTrigger pozwala utworzyć listę z elementami, które wyświetlają dodatkowe dane po wybraniu (a raczej ukrywają, gdy element nie jest wybrany).

• Należy sprawdzić wartość IsSelected elementu listy i ustawić wartość Visibility innego elementu.

<DataTemplate x:Key="DefaultBookTemplate">

<Border ...">

<Grid ...>

<Grid.RowDefinitions>

<RowDefinition></RowDefinition>

<RowDefinition></RowDefinition>

</Grid.RowDefinitions>

<TextBlock ... Text="{Binding Path=Title}" />

<TextBlock ... Text="{Binding Path=Author}">

<TextBlock.Style>

<Style>

...

</Style>

</TextBlock.Style>

</TextBlock>

</Grid>

</Border>

(33)

<Style>

<Style.Triggers>

<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="False">

<Setter Property="TextBlock.Visibility"

Value="Collapsed" />

</DataTrigger>

</Style.Triggers>

</Style>

(34)

Kontrolki list

(35)

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

(36)

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.

(37)

ComboBox

• Składa się z dwóch części: okna wyboru (ukazuje aktualny wybór) i listy rozwijalnej (wszystkie możliwe wybory).

(38)

• 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" ... />

(39)

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.

(40)

• Dodawanie kolumn: kolekcja GridView.Columns. Header to nagłówek kolumny, a DisplayMemberBinding – wiązanie obiektem danych.

<ListView Name="lstBooks" Margin="3">

<ListView.View>

<GridView>

<GridView.Columns>

<GridViewColumn Header="Tytuł"

DisplayMemberBinding="{Binding Path=Title}"/>

<GridViewColumn Header="Autor"

DisplayMemberBinding="{Binding Path=Author}"/>

<GridViewColumn Header="Cena"

DisplayMemberBinding="{Binding Path=Price}"/>

</GridView.Columns>

</GridView>

</ListView.View>

</ListView>

(41)

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

(42)

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.

(43)
(44)

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;

(45)

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>

(46)
(47)

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>

(48)

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.

(49)

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>

(50)

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;

}

(51)

• 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; } }

}

(52)

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>

(53)

• 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>

(54)

• Drzewo posługuje się teraz dwoma osobnymi szablonami dla pierwszego i drugiego poziomu.

• Drugi szablon wykorzystuje wybrany element pierwszego, jako źródło danych.

(55)

• 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>

(56)

• 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(...));

(57)

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ć

(58)

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ć).

(59)

<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),

(60)

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

(61)

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>

(62)

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

(63)

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>

<Border CornerRadius="4" Padding="5"

Background="Black">

<TextBlock FontWeight="Bold">

Editing Commands </TextBlock>

</Border>

</ControlTemplate>

</Separator.Template>

</Separator>

(64)

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.

(65)

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>

(66)

• 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)

(67)

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>

(68)
(69)

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>

<Grid><Grid.ColumnDefinitions>

<ColumnDefinition Width="*" />

<ColumnDefinition Width="Auto" />

</Grid.ColumnDefinitions></Grid>

</ItemsPanelTemplate>

</StatusBar.ItemsPanel>

<TextBlock>2/10</TextBlock>

<StatusBarItem Grid.Column="1">

<TextBlock>120%</TextBlock>

</StatusBarItem>

(70)

Cytaty

Powiązane dokumenty

SKŁADNIKI: dorsz- ryba; przyprawy: sól, pieprz czarny, czosnek granulowany; szpinak, ser żółty – mleko (łącznie z laktozą); ziemniaki; masło- mleko (łącznie z

danie główne serwowane kolejne danie serowane bufet dań ogólnodostępny zupa serwowana..

wybór sałatek/surówek/jarzynek na ciepło | kompot wieloowocowy lub lemoniada/woda z cytryną zupa 6,00zł | drugie danie/z rybą 18,00zł/20,00zł | pełny zestaw obiadowy/z rybą

Cabernet Sauvignon , Syrah, Mourverde - Bekaa Valley/ Liban / wytrawne Baron de Ley Gran Reserva Tempranillo - DOCa Rioja / Hiszpania / wytrawne. Il

Marchewka z jabłkiem Buraczki z chrzanem Surówka z kapusty pekińskej Marchewka po koreańsku z czosnkiem Surówka z selera z jabłkiem i rodzynkami. Mizeria (sezonowo)

wheat roll, mozzarella cheese, tomatoe sauce, rucola, parmesan, salami piccante, olives, basil, beef 200 g, homemade fries-. Burger Devil

Passoa, limonka, woda gazowana, cukier brązowy, mięta.

Łosoś wędzony na ciepło - ziemniaki kremowe, szpinak, zestaw surówek 4 4,00 Filet z dorsza - ziemniaki au gratine, sos z pieczonych pomidorów, zielony pieprz, koperek 4 1,00