Style i szablony (WPF .NET)
Windows Presentation Foundation (WPF) style i tworzenie szablonów odnoszą się do zestawu funkcji, które pozwalają deweloperom i projektantom tworzyć wizualnie atrakcyjne efekty i spójny wygląd dla swojego 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 wśród nich. WPF zapewnia ten model.
Inną cechą modelu stylów WPF jest rozdzielenie prezentacji i logiki. Projektanci 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żywanie stylów i szablonów. Aby uzyskać więcej informacji na temat zasobów, zobacz Omówienie zasobów XAML.
Ważne
Dokumentacja przewodnika dla komputerów stacjonarnych dla platform .NET 6 i .NET 5 (w tym .NET Core 3.1) jest w budowie.
Przykład
Przykładowy kod przedstawiony w tym omówieniu jest oparty na prostej aplikacji do przeglądania zdjęć pokazanej na poniższej ilustracji.
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.
Kompletny przykład można znaleźć w temacie Introduction to Styling and Templating Sample (Wprowadzenie do stylów i 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żna użyć stylu dla dowolnego elementu, który pochodzi od FrameworkElement lub FrameworkContentElement , na przykład Window lub Button.
Najczęstszym sposobem deklarowania stylu jest 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 prościej, gdzie deklarujesz styl, ma wpływ 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 elementu TextBlock, 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, które są używane.
<StackPanel>
<TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
<TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>
Aby uzyskać więcej informacji, zobacz Tworzenie stylu dla kontrolki.
ControlTemplates
W WPF kontrolka ControlTemplate 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, podczas gdy 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 zdefiniowania lub ustawienia szablonu.
Projektanci zazwyczaj umożliwiają utworzenie kopii istniejącego szablonu i zmodyfikowanie go. Na przykład w projektancie Visual Studio WPF wybierz kontrolkę, a następnie kliknij prawym przyciskiem CheckBox myszy i wybierz pozycję EdytujszablonUtwó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żono, że zasób szablonu zdefiniowany w poprzedniej sekcji używa rozszerzenia znaczników TemplateBinding. A TemplateBinding to zoptymalizowana forma powiązania dla scenariuszy szablonów, analogicznie do powiązania skonstruowanego za pomocą {Binding RelativeSource={RelativeSource TemplatedParent}}elementu . TemplateBinding Jest przydatny w przypadku wiązania części szablonu z właściwościami kontrolki. Na przykład każda kontrolka BorderThickness ma właściwość . Użyj elementu , TemplateBinding aby zarządzać tym elementem w szablonie, którego dotyczy to ustawienie kontrolki.
ContentControl i ItemsControl
ContentPresenter Jeśli element jest zadeklarowany w obiekcie ContentControlControlTemplate , ContentPresenter element automatycznie powiąże się z właściwościami ContentTemplate i Content . Podobnie element ItemsPresenter , który znajduje się w ControlTemplate obiekcie ItemsControl , automatycznie powiąże się z właściwościami ItemTemplate i Items .
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.
Większość kontrolek ma jakiś typ zawartości, a zawartość często pochodzi z danych, z którymi się 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 obiekcie 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 wyświetlane jako 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 element DataTemplate znajduje się w sekcji zasobów, podczas określania DataType właściwości typu i pomijania x:Keyelementu , DataTemplate jest stosowany za każdym razem, gdy pojawi się ten typ. Zawsze masz możliwość przypisania obiektu DataTemplate za pomocą elementu , x:Key a następnie ustawiania 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 on być wyświetlany jako element Image w obiekcie Border. Dzięki temu DataTemplatenasza aplikacja wygląda teraz następująco.
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 DataTemplateSelectorelement , który umożliwia wybranie elementu DataTemplate do użycia na podstawie logiki niestandardowej. Aby uzyskać więcej informacji, zobacz Data Templating Overview (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 zmienia się lub gdy jest zgłaszane 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 zostanie wybrany. Poniższy styl ustawia Opacity wartość typu 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ść obiektu ListBoxItem jest ustawiona na 75wartość . Na poniższej ilustracji trzeci element jest wybranym elementem.
EventTriggers i Storyboards
Innym typem wyzwalacza jest EventTriggerelement , 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ść 90 w ciągu drugiego 0.2 okresu. 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 . Wynika to z tego, że animacja może ś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 wskaźnik myszy wskazuje trzeci element.
MultiTriggers, DataTriggers i MultiDataTriggers
Oprócz elementów Trigger i EventTriggeristnieją inne typy wyzwalaczy. MultiTrigger Umożliwia ustawienie wartości właściwości na podstawie wielu warunków. Używasz DataTrigger właściwości i MultiDataTrigger gdy właściwość warunku jest powiązana z danymi.
Stany wizualne
Kontrolki są zawsze w określonym stanie. Na przykład gdy mysz przesuwa się nad powierzchnią kontrolki, kontrolka jest uważana za w typowym stanie MouseOver. Kontrolka bez określonego stanu jest uważana za stan wspólny 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 | Domyślny stan. |
| 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. Deklaruje VisualStateManager , które kombinacje VisualStateGroup i VisualState które mają być oglądane. Po wprowadzeniu stanu obserwowanego kontrolka zostanie uruchomiona animacja zdefiniowana przez element VisaulStateManager .
Na przykład poniższy kod XAML obserwuje CommonStates.MouseOver stan animowania koloru 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. 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 uwidacznia WPF na potrzeby 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 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 drzewa elementów w poszukiwaniu odpowiedniego zasobu, a następnie wyszukania w kolekcji zasobów aplikacji i na koniec wykonywania zapytań względem 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. Motywy z możliwością zamiany można również tworzyć, definiując wiele słowników zasobów, które udostępniają te same typy zasobów, ale z różnymi wartościami. Przedefiniowanie tych stylów lub innych zasobów na poziomie aplikacji jest zalecanym podejściem do osłaniania aplikacji.
Aby udostępnić zestaw zasobów, w tym style i szablony, w aplikacjach, możesz utworzyć plik XAML i zdefiniować ResourceDictionary odwołanie do shared.xaml pliku.
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
Jest to udostępnianie shared.xamlelementu , który definiuje ResourceDictionary element zawierający zestaw zasobów stylu i pędzla, który umożliwia kontrolkom w aplikacji 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 Tworzenie kontrolek.