poniedziałek, 28 listopada 2011

WPF - Templates

DataTemplate

W kontrolkach, typu ItemsControl (np. ListBox, ComboBox), które wyświetlają wiele obiektów, można zdefiniować szablon, który definiuje jak ma wyglądać pojedynczy wyświetlany obiekt. Robimy to za pomocą property ItemTemplate, do której przypisujemy obiekt typu DataTemplate.
...Resources>
<DataTemplate x:Key="ItemTemplateKey">
<TextBlock Text="item"/>
</DataTemplate>
</...Resources>

<ListBox ItemTemplate="{StaticResource ItemTemplateKey}" ItemsSource="{Binding Items}"/>

W takim wypadku, każdy Item w listboxie będzie napisem “item”. Dla DataTemplate’a można ustawiać triggery i odwoływać się do elementów wewnątrz za pomocą TargetName.
<DataTemplate.Triggers>
<SomeTrigger...
<Setter Property="BorderBrush" Value="Red" TargetName="border" />
</SomeTrigger>
</DataTemplate.Triggers>


DataTemplateSelector

Jest to klasa, która umożliwia nam wybór jednego z pośród wielu templatów w zależności od wartości lub typu obiektu. Zasadniczo użycie DataTemplateSelector wygląda tak:
public class Selector: DataTemplateSelector
{

public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if(item is int && (int)item == 1) return Template1;
if(item is double) return Template2;
return Template3;
}

public DataTemplate Template1 { get; set; }
public DataTemplate Template2 { get; set; }
public DataTemplate Template3 { get; set; }
}

Użycie takiego DataTemplateSelector’a w xaml’u wygląda tak:
<...Resources>
<DataTemplate x:Key="Template1Key">
<TextBlock Text="Pierwszy template"/>
</DateTemplate>
<DataTemplate x:Key="Template2Key">
<TextBlock Text="Drugi template"/>
</DateTemplate>
<DataTemplate x:Key="Template3Key">
<TextBlock Text="Trzeci template"/>
</DateTemplate>
<Selector x:Key="SelectorKey"
Template1="{StaticResource Template1Key}"
Template2="{StaticResource Template2Key}"
Template3="{StaticResource Template3Key}"/>
</...Resources>
<ListBox ItemTemplateSelector="{StaticResource SelectorKey}" ItemsSource="{Binding Items}"/>

W zależności od tego jakiego typu jest dany item i jaką ma wartość zostanie wyświetlony odpowiedni template (odpowiedni napis: “Pierwszy tempalte”, “Drugi template” lub “Trzeci template”).

Na bazie DataTemplateSelector można zrobić bardzo prosty mechanizm MVVM. Załóżmy, że mamy w DataContexcie jakiś obiekt, który ma wystawioną property ViewModel. W widoku mamy
<ContentPresenter Content="{Binding ViewModel}" ContentTemplateSelector="{StaticResource ViewModelTemplateSelector}"/>

Gdzie ViewModelTemplateSelector może wyglądać tak:
public class Selector: DataTemplateSelector
{

public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if(item is ViewModel1) return View1;
if(item is ViewModel2) return View2;
return View3;
}

public DataTemplate View1 { get; set; }
public DataTemplate View2 { get; set; }
public DataTemplate View3 { get; set; }
}

DataTemplate możemy zastosować również w przypadku ContentControl lub klasy dziedziczącej po niej. Dobrym przykładem może być Button. Zwykły button wyświetla dane w takiej postaci jak wkleimy do właściwości Content (property content jest domyślna, wszystko co znajduje się pomiędzy jest przypisywane do właściwości Content). Jeżeli napiszemy:
<Button>Napis</Button>

Wówczas uzyskamy przycisk z napisem “napis”. Jeżeli chcemy aby koło naszego napisu zawsze pojawiał się jakiś obrazek wówczas powinniśmy zdefiniować specjalny DataTemplate, który przypisujemy do właściwości ContentTemplate kontrolki typu ContentControl lub pochodnej (np. Button). Nasz DataTemplate wyglądałby mniej więcej tak:
<DataTemplate x:Key="ButtonContentTemplate">
<StackPanel Orientation="Horizontal>
<Image Source="jakiesZrodlo" Widh="10" Height="10"/>
<ContentPresenter Content="{Binding .}"/>
</StackPanel>
</DataTemplate>

Element ContentPresenter jest niezbędny i wskazuje on, w którym miejscu powinna wyświetlić się treść, którą podpinamy do Buttona. Nasz ostateczny wpis wygląda więc tak:
<Button ContentTemplate="{StaticResource ButtonContentTemplate}">Napis</Button>


ControlTemplates

Ok, ale co jeżeli chcemy zmienić cały wygląd buttona, nie tylko treść jaka jest wyświetlana. Wówczas mamy do dyspozycji Property Template. Jest ona typu ControlTemplate i definiujemy ją bezpośrednio w danej kontrolce, w resourcach z odpowiednim kluczem lub w stylu.
<ControlTemplate TargetType="Button" x:Key="ButtonControlTemplate">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>

W tym wypadku również umieszczamy element ContentPresenter, aby wskazać w którym miejscu kontrolki powinna się wyświetlić treść.
TemplateBinding oznacza podpięcie domyślnej wartości właściwości Background. TemplateBinding działa podobnie jak RelativeSource.TemplatedParent z tą różnicą, że RelativeSource jest ustalany w trakcie dzialania programu, natomiast TemplateBinding w czasie kompilacji.
Jeżeli chcemy całkowicie zmienić wygląd kontrolki powinniśmy używać właściwości Template, natomiast jeżeli chcemy zachować obecny styl i jedynie zmodyfikować wyświetlane dane wówczas powinniśmy skorzystać z właściwości ContentTemplate.

1 komentarze:

Tomek pisze...

Fajny wpis, nie wiedziałem, że istnieje coś takiego jak ContentTemplateSelector. Mógłbyś jeszcze dopisać jak zdefiniować własny template w stylu, a potem pokazać jak go użyć

Prześlij komentarz

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Online Project management