Style i szablony w WPF

Styl i tworzenie szablonów w programie Windows Presentation Foundation (WPF) odnoszą się do zestawu funkcji, które pozwalają deweloperom i projektantom tworzyć wizualnie atrakcyjne efekty i spójny wygląd ich produktu. Podczas dostosowywania wyglądu aplikacji potrzebujesz silnego modelu stylów i tworzenia szablonów, który umożliwia konserwację i udostępnianie wyglądu w aplikacjach i między nimi. WPF zapewnia ten model.

Kolejną cechą modelu stylów WPF jest rozdzielenie prezentacji i logiki. Projektant mogą pracować nad wyglądem aplikacji, używając jednocześnie tylko języka XAML, który deweloperzy pracują nad logiką programowania przy użyciu języka C# lub Visual Basic.

To omówienie koncentruje się na aspektach stylów i tworzenia szablonów aplikacji i nie omawia żadnych pojęć związanych z powiązaniem danych. Aby uzyskać informacje o powiązaniu danych, zobacz Omówienie powiązania danych.

Ważne jest, aby zrozumieć zasoby, które umożliwiają ponowne użycie stylów i szablonów. Aby uzyskać więcej informacji na temat zasobów, zobacz Zasoby XAML.

Przykład

Przykładowy kod podany w tym omówieniu jest oparty na prostej aplikacji do przeglądania zdjęć pokazanej na poniższej ilustracji.

Styled ListView

Ten prosty przykład zdjęć używa stylów i tworzenia szablonów w celu utworzenia wizualnie atrakcyjnego środowiska użytkownika. Przykład zawiera dwa TextBlock elementy i kontrolkę ListBox powiązaną z listą obrazów.

Aby zapoznać się z kompletnym przykładem, zobacz Wprowadzenie do stylu i przykład tworzenia szablonów.

Style

Można traktować Style jako wygodny sposób stosowania zestawu wartości właściwości do wielu elementów. Możesz użyć stylu dla dowolnego elementu, który pochodzi z FrameworkElement lub FrameworkContentElement , na przykład Window lub Button.

Najbardziej typowym sposobem deklarowania stylu jest jako zasób w Resources sekcji w pliku XAML. Ponieważ style są zasobami, przestrzegają tych samych reguł określania zakresu, które mają zastosowanie do wszystkich zasobów. Mówiąc po prostu, gdzie deklarujesz styl, wpływa na to, gdzie można zastosować styl. Jeśli na przykład zadeklarujesz styl w elemecie głównym pliku XAML definicji aplikacji, styl może być używany w dowolnym miejscu w aplikacji.

Na przykład poniższy kod XAML deklaruje dwa style dla TextBlockelementu , jeden automatycznie zastosowany do wszystkich TextBlock elementów, a drugi, do którego należy jawnie odwoływać się.

<Window.Resources>
    <!-- .... other resources .... -->

    <!--A Style that affects all TextBlocks-->
    <Style TargetType="TextBlock">
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="Comic Sans MS"/>
        <Setter Property="FontSize" Value="14"/>
    </Style>
    
    <!--A Style that extends the previous TextBlock Style with an x:Key of TitleText-->
    <Style BasedOn="{StaticResource {x:Type TextBlock}}"
           TargetType="TextBlock"
           x:Key="TitleText">
        <Setter Property="FontSize" Value="26"/>
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                    <LinearGradientBrush.GradientStops>
                        <GradientStop Offset="0.0" Color="#90DDDD" />
                        <GradientStop Offset="1.0" Color="#5BFFFF" />
                    </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

Oto przykład stylów zadeklarowanych powyżej używanych.

<StackPanel>
    <TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
    <TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>

Styled textblocks

ControlTemplates

W WPF ControlTemplate kontrolka definiuje wygląd kontrolki. Możesz zmienić strukturę i wygląd kontrolki, definiując nową ControlTemplate i przypisując ją do kontrolki. W wielu przypadkach szablony zapewniają wystarczającą elastyczność, dzięki czemu nie trzeba pisać własnych kontrolek niestandardowych.

Każda kontrolka ma szablon domyślny przypisany do właściwości Control.Template . Szablon łączy wizualną prezentację kontrolki z możliwościami kontrolki. Ponieważ definiujesz szablon w języku XAML, możesz zmienić wygląd kontrolki bez konieczności pisania kodu. Każdy szablon jest przeznaczony dla określonej kontrolki, takiej jak Button.

Często deklarujesz szablon jako zasób w Resources sekcji pliku XAML. Podobnie jak w przypadku wszystkich zasobów, mają zastosowanie reguły określania zakresu.

Szablony kontrolek są o wiele bardziej zaangażowane niż styl. Dzieje się tak, ponieważ szablon kontrolki ponownie zapisuje wygląd wizualizacji całej kontrolki, a styl po prostu stosuje zmiany właściwości do istniejącej kontrolki. Jednak ponieważ szablon kontrolki jest stosowany przez ustawienie właściwości Control.Template , można użyć stylu do definiowania lub ustawiania szablonu.

Projektant zazwyczaj umożliwiają utworzenie kopii istniejącego szablonu i zmodyfikowanie go. Na przykład w projektancie WPF programu Visual Studio wybierz kontrolkę, a następnie kliknij prawym przyciskiem CheckBox myszy i wybierz polecenie Edytuj szablon>Utwórz kopię. To polecenie generuje styl, który definiuje szablon.

<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual1}"/>
    <Setter Property="Background" Value="{StaticResource OptionMark.Static.Background1}"/>
    <Setter Property="BorderBrush" Value="{StaticResource OptionMark.Static.Border1}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CheckBox}">
                <Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Border x:Name="checkBoxBorder" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                        <Grid x:Name="markGrid">
                            <Path x:Name="optionMark" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="1" Opacity="0" Stretch="None"/>
                            <Rectangle x:Name="indeterminateMark" Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="2" Opacity="0"/>
                        </Grid>
                    </Border>
                    <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasContent" Value="true">
                        <Setter Property="FocusVisualStyle" Value="{StaticResource OptionMarkFocusVisual1}"/>
                        <Setter Property="Padding" Value="4,-1,0,0"/>

... content removed to save space ...

Edytowanie kopii szablonu to doskonały sposób na poznanie sposobu działania szablonów. Zamiast tworzyć nowy pusty szablon, łatwiej jest edytować kopię i zmienić kilka aspektów prezentacji wizualnej.

Aby zapoznać się z przykładem, zobacz Tworzenie szablonu dla kontrolki.

Powiązanie szablonu

Być może zauważysz, że zasób szablonu zdefiniowany w poprzedniej sekcji używa rozszerzenia znaczników TemplateBinding. A TemplateBinding jest zoptymalizowaną formą powiązania dla scenariuszy szablonów, analogicznie do powiązania skonstruowanego za pomocą {Binding RelativeSource={RelativeSource TemplatedParent}}polecenia . TemplateBinding jest przydatne w przypadku wiązania części szablonu z właściwościami kontrolki. Na przykład każda kontrolka BorderThickness ma właściwość . TemplateBinding Użyj elementu , aby zarządzać elementem w szablonie, którego dotyczy to ustawienie kontrolki.

ContentControl i ItemsControl

ContentPresenter Jeśli element jest zadeklarowany w elemecie ContentControlControlTemplate , ContentPresenter element automatycznie będzie wiązać się z właściwościami ContentTemplate i Content . Podobnie element ItemsPresenter , który znajduje się w elemecie ItemsControlControlTemplate , automatycznie wiąże się z właściwościami ItemTemplate iItems.

DataTemplates

W tej przykładowej aplikacji istnieje kontrolka ListBox powiązana z listą zdjęć.

<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
         Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>

Obecnie wygląda to ListBox następująco.

ListBox before applying template

Większość kontrolek ma jakiś typ zawartości, a zawartość często pochodzi z danych, z którymi wiążesz. W tym przykładzie dane są listą zdjęć. W WPF należy użyć elementu , DataTemplate aby zdefiniować wizualną reprezentację danych. Zasadniczo to, co należy umieścić w elemecie DataTemplate , określa, jak wyglądają dane w renderowanej aplikacji.

W naszej przykładowej aplikacji każdy obiekt niestandardowy Photo ma Source właściwość typu string, która określa ścieżkę pliku obrazu. Obecnie obiekty zdjęć są wyświetlane jako ścieżki plików.

public class Photo
{
    public Photo(string path)
    {
        Source = path;
    }

    public string Source { get; }

    public override string ToString() => Source;
}
Public Class Photo
    Sub New(ByVal path As String)
        Source = path
    End Sub

    Public ReadOnly Property Source As String

    Public Overrides Function ToString() As String
        Return Source
    End Function
End Class

Aby zdjęcia wyglądały jak obrazy, należy utworzyć DataTemplate jako zasób.

<Window.Resources>
    <!-- .... other resources .... -->

    <!--DataTemplate to display Photos as images
    instead of text strings of Paths-->
    <DataTemplate DataType="{x:Type local:Photo}">
        <Border Margin="3">
            <Image Source="{Binding Source}"/>
        </Border>
    </DataTemplate>
</Window.Resources>

Zwróć uwagę, że DataType właściwość jest podobna do TargetType właściwości Style. Jeśli znajdujesz DataTemplate się w sekcji zasobów, po określeniu DataType właściwości typu i pominiesz x:Keyelement , DataTemplate jest stosowany za każdym razem, gdy pojawi się ten typ. Zawsze masz możliwość przypisania elementu DataTemplate za pomocą elementu x:Key , a następnie ustawić go jako StaticResource właściwości, które przyjmują DataTemplate typy, takie jak ItemTemplate właściwość lub ContentTemplate właściwość.

Zasadniczo w DataTemplate powyższym przykładzie zdefiniowano, że za każdym razem, gdy istnieje Photo obiekt, powinien być wyświetlany jako element Image w obiekcie Border. Dzięki temu DataTemplatenasza aplikacja wygląda teraz następująco.

Photo image

Model tworzenia szablonów danych udostępnia inne funkcje. Jeśli na przykład wyświetlasz dane kolekcji zawierające inne kolekcje przy użyciu HeaderedItemsControl typu, takiego jak Menu lub TreeView, istnieje HierarchicalDataTemplate. Inną funkcją tworzenia szablonów danych jest DataTemplateSelectorfunkcja , która umożliwia wybranie DataTemplate elementu do użycia na podstawie logiki niestandardowej. Aby uzyskać więcej informacji, zobacz Omówienie tworzenia szablonów danych, który zawiera bardziej szczegółowe omówienie różnych funkcji tworzenia szablonów danych.

Wyzwalacze

Wyzwalacz ustawia właściwości lub uruchamia akcje, takie jak animacja, gdy wartość właściwości ulegnie zmianie lub gdy zostanie zgłoszone zdarzenie. Style, ControlTemplatei DataTemplate wszystkie mają Triggers właściwość, która może zawierać zestaw wyzwalaczy. Istnieje kilka typów wyzwalaczy.

WłaściwościTriggers

Obiekt Trigger , który ustawia wartości właściwości lub uruchamia akcje na podstawie wartości właściwości, jest nazywany wyzwalaczem właściwości.

Aby zademonstrować sposób używania wyzwalaczy właściwości, można ustawić każdy ListBoxItem częściowo przezroczysty, chyba że został wybrany. Poniższy styl ustawia Opacity wartość elementu ListBoxItem na 0.5wartość . IsSelected Gdy właściwość ma truewartość , właściwość jest jednak ustawiona Opacity na 1.0wartość .

<Window.Resources>
    <!-- .... other resources .... -->

    <Style TargetType="ListBoxItem">
        <Setter Property="Opacity" Value="0.5" />
        <Setter Property="MaxHeight" Value="75" />
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Trigger.Setters>
                    <Setter Property="Opacity" Value="1.0" />
                </Trigger.Setters>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

W tym przykładzie użyto elementu , Trigger aby ustawić wartość właściwości, ale należy pamiętać, że Trigger klasa ma EnterActions również właściwości i ExitActions , które umożliwiają wyzwalaczowi wykonywanie akcji.

Zwróć uwagę, że MaxHeight właściwość właściwości ListBoxItem jest ustawiona na 75. Na poniższej ilustracji trzeci element jest wybranym elementem.

Styled ListView

EventTriggers i Storyboards

Innym typem wyzwalacza jest EventTriggerwyzwalacz , który uruchamia zestaw akcji na podstawie wystąpienia zdarzenia. Na przykład następujące EventTrigger obiekty określają, że po wprowadzeniu MaxHeightListBoxItemwskaźnika myszy właściwość animuje wartość 900.2 w drugim okresie. Gdy mysz odchodzi od elementu, właściwość powraca do oryginalnej wartości w okresie sekundy 1 . Zwróć uwagę, że nie trzeba określać To wartości animacji MouseLeave . Jest to spowodowane tym, że animacja jest w stanie śledzić oryginalną wartość.

<Style.Triggers>
    <Trigger Property="IsSelected" Value="True">
        <Trigger.Setters>
            <Setter Property="Opacity" Value="1.0" />
        </Trigger.Setters>
    </Trigger>
    <EventTrigger RoutedEvent="Mouse.MouseEnter">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:0.2"
                        Storyboard.TargetProperty="MaxHeight"
                        To="90"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
    <EventTrigger RoutedEvent="Mouse.MouseLeave">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:1"
                        Storyboard.TargetProperty="MaxHeight"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
</Style.Triggers>

Aby uzyskać więcej informacji, zobacz Omówienie scenorysów.

Na poniższej ilustracji mysz wskazuje trzeci element.

Styling sample screenshot

MultiTriggers, DataTriggers i MultiDataTriggers

Oprócz Trigger elementów i EventTriggeristnieją inne typy wyzwalaczy. MultiTrigger umożliwia ustawianie wartości właściwości na podstawie wielu warunków. Używasz wartości DataTrigger i MultiDataTrigger gdy właściwość warunku jest powiązana z danymi.

Stany wizualizacji

Kontrolki są zawsze w określonym stanie. Na przykład gdy mysz porusza się na powierzchni kontrolki, kontrolka jest uważana za wspólną wartość MouseOver. Kontrolka bez określonego stanu jest uważana za w stanie wspólnym Normal . Stany są podzielone na grupy, a wymienione wcześniej stany są częścią grupy CommonStatesstanów . Większość kontrolek ma dwie grupy stanów: CommonStates i FocusStates. Dla każdej grupy stanów stosowanej do kontrolki kontrolka jest zawsze w jednym stanie każdej grupy, na przykład CommonStates.MouseOver i FocusStates.Unfocused. Jednak kontrolka nie może znajdować się w dwóch różnych stanach w tej samej grupie, takich jak CommonStates.Normal i CommonStates.Disabled. Oto tabela stanów, w których większość kontrolek rozpoznaje i używa.

Nazwa wizualizacji Nazwa grupy VisualStateGroup opis
Normalne CommonStates Stan domyślny.
Mouseover CommonStates Wskaźnik myszy jest umieszczony nad kontrolką.
Naciśnięte CommonStates Kontrolka jest naciśnięta.
Disabled CommonStates Kontrolka jest wyłączona.
Ustawiono fokus FocusStates Kontrolka ma fokus.
Unfocused FocusStates Kontrolka nie ma fokusu.

Definiując element System.Windows.VisualStateManager główny szablonu kontrolki, można wyzwalać animacje po wprowadzeniu określonego stanu kontrolki. Wyrażenie VisualStateManager deklaruje, które kombinacje elementów VisualStateGroup i VisualState mają być oglądane. Gdy kontrolka wejdzie w stan obserwowany, zostanie uruchomiona animacja zdefiniowana VisualStateManager przez element .

Na przykład poniższy kod XAML obserwuje CommonStates.MouseOver stan, aby animować kolor wypełnienia elementu o nazwie backgroundElement. Gdy kontrolka powróci do CommonStates.Normal stanu, kolor wypełnienia elementu o nazwie backgroundElement zostanie przywrócony.

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <ColorAnimation Storyboard.TargetName="backgroundElement"
                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                    To="{TemplateBinding Background}"
                                    Duration="0:0:0.3"/>
                </VisualState>
                <VisualState Name="MouseOver">
                    <ColorAnimation Storyboard.TargetName="backgroundElement"
                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                    To="Yellow"
                                    Duration="0:0:0.3"/>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        ...

Aby uzyskać więcej informacji na temat scenorysów, zobacz Storyboards Overview (Omówienie scenorysów).

Udostępnione zasoby i motywy

Typowa aplikacja WPF może mieć wiele zasobów interfejsu użytkownika, które są stosowane w całej aplikacji. Zbiorczo ten zestaw zasobów można uznać za motyw aplikacji. Platforma WPF zapewnia obsługę tworzenia pakietów zasobów interfejsu użytkownika jako motywu przy użyciu słownika zasobów, który jest hermetyzowany jako ResourceDictionary klasa.

Motywy WPF są definiowane przy użyciu mechanizmu stylów i tworzenia szablonów, który WPF uwidacznia do dostosowywania wizualizacji dowolnego elementu.

Zasoby motywu WPF są przechowywane w słownikach zasobów osadzonych. Te słowniki zasobów muszą być osadzone w podpisanym zestawie i mogą być osadzone w tym samym zestawie co sam kod lub w zestawie równoległym. W przypadku biblioteki PresentationFramework.dll zestaw, który zawiera kontrolki WPF, zasoby motywu znajdują się w serii zestawów równoległych.

Motyw staje się ostatnim miejscem do wyszukania podczas wyszukiwania stylu elementu. Zazwyczaj wyszukiwanie rozpocznie się od przejścia w górę drzewa elementów wyszukującego odpowiedni zasób, a następnie zajrzyj do kolekcji zasobów aplikacji i na koniec wykonaj zapytanie dotyczące systemu. Dzięki temu deweloperzy aplikacji mogą ponownie zdefiniować styl dla dowolnego obiektu na poziomie drzewa lub aplikacji przed dotarciem do motywu.

Słowniki zasobów można zdefiniować jako poszczególne pliki, które umożliwiają ponowne użycie motywu w wielu aplikacjach. Można również tworzyć motywy z możliwością zamiany, definiując wiele słowników zasobów, które udostępniają te same typy zasobów, ale z różnymi wartościami. Ponowne zdefiniowanie tych stylów lub innych zasobów na poziomie aplikacji jest zalecanym podejściem do pielęgnacji aplikacji.

Aby udostępnić zestaw zasobów, w tym style i szablony, w aplikacjach, możesz utworzyć plik XAML i zdefiniować element ResourceDictionary zawierający odwołanie do shared.xaml pliku.

<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>

Jest to udostępnianie shared.xamlobiektu , które definiuje element , który zawiera ResourceDictionary zestaw zasobów stylu i pędzla, dzięki czemu kontrolki w aplikacji mają spójny wygląd.

Aby uzyskać więcej informacji, zobacz Scalone słowniki zasobów.

Jeśli tworzysz motyw dla kontrolki niestandardowej, zobacz sekcję Definiowanie zasobów na poziomie motywu w przeglądzie tworzenia kontrolek.

Zobacz też