Delen via


Stijlen en sjablonen in WPF

Windows Presentation Foundation (WPF) styling en templating verwijzen naar een suite met functies waarmee ontwikkelaars en ontwerpers visueel aantrekkelijke effecten en een consistent uiterlijk voor hun product kunnen creëren. Bij het aanpassen van het uiterlijk van een app wilt u een sterk stijl- en sjabloonmodel dat het onderhoud en het delen van het uiterlijk binnen en tussen apps mogelijk maakt. WPF biedt dat model.

Een andere functie van het WPF-stijlmodel is de scheiding van presentatie en logica. Ontwerpers kunnen werken aan het uiterlijk van een app door alleen XAML te gebruiken op hetzelfde moment dat ontwikkelaars aan de programmeerlogica werken met behulp van C# of Visual Basic.

Dit overzicht richt zich op de stijl- en templateaspecten van de app en bespreekt geen gegevensbindingsconcepten. Zie Overzicht van gegevensbindingvoor meer informatie over gegevensbinding.

Het is belangrijk om inzicht te krijgen in resources. Dit zijn de hulpmiddelen waarmee stijlen en sjablonen opnieuw kunnen worden gebruikt. Zie XAML-resourcesvoor meer informatie over resources.

Voorbeeld

De voorbeeldcode in dit overzicht is gebaseerd op een eenvoudige toepassing voor het bladeren door foto's weergegeven in de volgende afbeelding.

Gestijlde ListView

In dit eenvoudige fotovoorbeeld wordt gebruikgemaakt van stijlen en sjablonen om een visueel aantrekkelijke gebruikerservaring te creëren. Het voorbeeld heeft twee TextBlock elementen en een ListBox besturingselement dat is gebonden aan een lijst met afbeeldingen.

Voor het volledige voorbeeld, zie Inleiding tot stijling en templating voorbeeld.

Stijlen

U kunt een Style beschouwen als een handige manier om een set eigenschapswaarden toe te passen op meerdere elementen. U kunt een stijl gebruiken voor elk element dat is afgeleid van FrameworkElement of FrameworkContentElement, zoals een Window of een Button.

De meest voorkomende manier om een stijl te declareren, is als een resource in de sectie Resources in een XAML-bestand. Omdat stijlen resources zijn, voldoen ze aan dezelfde bereikregels die van toepassing zijn op alle resources. Eenvoudig gezegd, waar u een stijl declareert, beïnvloedt waar de stijl kan worden toegepast. Als u bijvoorbeeld de stijl declareert in het hoofdelement van het XAML-bestand van uw app-definitie, kan de stijl overal in uw app worden gebruikt.

De volgende XAML-code declareert bijvoorbeeld twee stijlen voor een TextBlock, een die automatisch wordt toegepast op alle TextBlock elementen en een andere die expliciet moet worden verwezen.

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

Hier volgt een voorbeeld van de stijlen die hierboven zijn gedeclareerd.

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

gestileerde tekstblokken

ControlTemplates

In WPF definieert de ControlTemplate van een besturingselement het uiterlijk van het besturingselement. U kunt de structuur en het uiterlijk van een besturingselement wijzigen door een nieuwe ControlTemplate te definiëren en dit toe te wijzen aan een besturingselement. In veel gevallen bieden sjablonen u voldoende flexibiliteit, zodat u uw eigen aangepaste besturingselementen niet hoeft te schrijven.

Elk control heeft een standaardsjabloon toegewezen aan de eigenschap Control.Template. De sjabloon verbindt de visuele presentatie van het besturingselement met de mogelijkheden van het besturingselement. Omdat u een sjabloon in XAML definieert, kunt u het uiterlijk van het besturingselement wijzigen zonder code te schrijven. Elke sjabloon is ontworpen voor een specifieke controle, zoals een Button.

Meestal declareert u een sjabloon als een resource in de sectie Resources van een XAML-bestand. Net als bij alle resources gelden scope-regels.

Sjablonen voor bedieningselementen zijn veel complexer dan een stijl. Dit komt doordat de besturingselementsjabloon het uiterlijk van het visuele element van het hele besturingselement herschrijft, terwijl een stijl gewoon eigenschapswijzigingen toepast op het bestaande besturingselement. Aangezien de sjabloon van een besturingselement echter wordt toegepast door de eigenschap Control.Template in te stellen, kunt u een stijl gebruiken om een sjabloon te definiëren of in te stellen.

Ontwerpers stellen u in het algemeen in staat om een kopie van een bestaande sjabloon te maken en deze te wijzigen. Selecteer bijvoorbeeld in de WPF-ontwerpfunctie van Visual Studio een CheckBox-besturingselement, klik met de rechtermuisknop en kies vervolgens Sjabloon bewerken>Een kopie maken. Met deze opdracht wordt een stijl gegenereerd waarmee een sjabloon wordt gedefinieerd.

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

Het bewerken van een kopie van een sjabloon is een uitstekende manier om te leren hoe sjablonen werken. In plaats van een nieuwe lege sjabloon te maken, is het eenvoudiger om een kopie te bewerken en enkele aspecten van de visuele presentatie te wijzigen.

Zie bijvoorbeeld Een sjabloon maken voor een besturingselement.

TemplateBinding

Mogelijk hebt u gemerkt dat de sjabloonresource die in de vorige sectie is gedefinieerd, gebruikmaakt van de TemplateBinding Markup Extension. Een TemplateBinding is een geoptimaliseerde vorm van een binding voor sjabloonscenario's, vergelijkbaar met een binding die is samengesteld met {Binding RelativeSource={RelativeSource TemplatedParent}}. TemplateBinding is handig voor het binden van onderdelen van de sjabloon aan eigenschappen van het besturingselement. Elk besturingselement heeft bijvoorbeeld een eigenschap BorderThickness. Gebruik een TemplateBinding om te beheren welk element in de sjabloon wordt beïnvloed door deze besturingselementinstelling.

ContentControl en ItemsControl

Als een ContentPresenter wordt gedeclareerd in de ControlTemplate van een ContentControl, wordt de ContentPresenter automatisch gekoppeld aan de eigenschappen van de ContentTemplate en Content. Op dezelfde manier wordt een ItemsPresenter die zich in de ControlTemplate van een ItemsControl bevindt, automatisch gebonden aan de eigenschappen ItemTemplate en Items.

DataTemplates

In deze voorbeeldapp is er een ListBox controle die is gekoppeld aan een lijst met foto's.

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

Deze ListBox ziet er momenteel als volgt uit.

Keuzelijst voordat u sjabloon toepast

De meeste besturingselementen hebben een bepaald type inhoud en die inhoud komt vaak van gegevens waarmee u verbinding maakt. In dit voorbeeld zijn de gegevens de lijst met foto's. In WPF gebruikt u een DataTemplate om de visuele weergave van gegevens te definiëren. Wat u in een DataTemplate plaatst, bepaalt in feite hoe de gegevens eruit zien in de gerenderde app.

In onze voorbeeld-app heeft elk aangepast Photo-object een Source-eigenschap van het type string, waarmee het bestandspad van de afbeelding wordt gespecificeerd. Momenteel worden de fotoobjecten weergegeven als bestandspaden.

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

Als u wilt dat de foto's worden weergegeven als afbeeldingen, maakt u een DataTemplate als een resource.

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

U ziet dat de eigenschap DataType vergelijkbaar is met de eigenschap TargetType van de Style. Als uw DataTemplate zich in de sectie Resources bevindt, dan wordt DataTemplate toegepast telkens wanneer u de eigenschap DataType voor een type opgeeft en x:Keyweglaat. U hebt altijd de mogelijkheid om de DataTemplate toe te wijzen met een x:Key en deze vervolgens in te stellen als een StaticResource voor eigenschappen die DataTemplate typen hebben, zoals de eigenschap ItemTemplate of de eigenschap ContentTemplate.

In wezen definieert het DataTemplate in het bovenstaande voorbeeld dat wanneer er een Photo object is, het als een Image binnen een Bordermoet worden weergegeven. Met deze DataTemplateziet onze app er nu als volgt uit.

foto

Het sjabloonmodel voor gegevens biedt andere functies. Als u bijvoorbeeld verzamelingsgegevens weergeeft die andere verzamelingen bevatten met behulp van een HeaderedItemsControl type, zoals een Menu of een TreeView, is er de HierarchicalDataTemplate. Een andere functie voor het sjabloneren van gegevens is de DataTemplateSelector, waarmee je een DataTemplate kunt kiezen op basis van aangepaste logica. Voor meer informatie, zie Overzicht van gegevenssjabloneren, dat een uitgebreidere bespreking van de verschillende functies voor gegevenssjabloneren biedt.

Aanleidingen

Met een trigger worden eigenschappen ingesteld of acties gestart, zoals een animatie, wanneer een eigenschapswaarde wordt gewijzigd of wanneer een gebeurtenis wordt gegenereerd. Style, ControlTemplateen DataTemplate hebben allemaal een Triggers eigenschap die een set triggers kan bevatten. Er zijn verschillende soorten triggers.

PropertyTriggers

Een Trigger waarmee eigenschapswaarden worden ingesteld of acties worden gestart op basis van de waarde van een eigenschap, wordt een eigenschapstrigger genoemd.

Als u wilt laten zien hoe u eigenschaptriggers gebruikt, kunt u elke ListBoxItem gedeeltelijk transparant maken, tenzij deze is geselecteerd. Met de volgende stijl wordt de Opacity waarde van een ListBoxItem ingesteld op 0.5. Wanneer de eigenschap IsSelected echter is true, wordt de Opacity ingesteld op 1.0.

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

In dit voorbeeld wordt een Trigger gebruikt om een eigenschapswaarde in te stellen, maar de klasse Trigger heeft ook de eigenschappen EnterActions en ExitActions waarmee een trigger acties kan uitvoeren.

U ziet dat de eigenschap MaxHeight van de ListBoxItem is ingesteld op 75. In de volgende afbeelding is het derde item het geselecteerde item.

Gestylde ListView

EventTriggers en Storyboards

Een ander type trigger is de EventTrigger, waarmee een set acties wordt gestart op basis van het optreden van een gebeurtenis. In de volgende EventTrigger objecten wordt bijvoorbeeld aangegeven dat wanneer de muisaanwijzer de ListBoxItembinnenkomt, de eigenschap MaxHeight een animatie heeft naar een waarde van 90 gedurende een periode van 0.2 seconden. Wanneer de muis van het item afschuift, wordt de eigenschap teruggegaan naar de oorspronkelijke waarde gedurende een periode van 1 seconde. U hoeft geen To waarde op te geven voor de MouseLeave animatie. Dit komt doordat de animatie de oorspronkelijke waarde kan bijhouden.

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

Zie het overzicht van Storyboardsvoor meer informatie.

In de volgende afbeelding wijst de muis naar het derde item.

Schermopname van styling voorbeeld

MultiTriggers, DataTriggers en MultiDataTriggers

Naast Trigger en EventTriggerzijn er andere typen triggers. Met MultiTrigger kunt u de waarde van eigenschappen instellen op basis van meerdere voorwaarden. U gebruikt DataTrigger en MultiDataTrigger wanneer de eigenschap van uw voorwaarde gegevensgebonden is.

Visuele toestanden

Besturingselementen hebben altijd een specifieke status. Wanneer de muis bijvoorbeeld over het oppervlak van een besturingselement beweegt, wordt het besturingselement beschouwd als in een standaardtoestand van MouseOver. Een besturingselement zonder een specifieke toestand wordt beschouwd als in de gemeenschappelijke Normal toestand. Staten worden onderverdeeld in groepen en de eerder genoemde staten maken deel uit van de staatsgroep CommonStates. De meeste besturingselementen hebben twee statusgroepen: CommonStates en FocusStates. Van elke statusgroep die op een besturingselement wordt toegepast, heeft een besturingselement altijd één status van elke groep, zoals CommonStates.MouseOver en FocusStates.Unfocused. Een besturingselement kan zich echter niet in twee verschillende statussen binnen dezelfde groep bevinden, zoals CommonStates.Normal en CommonStates.Disabled. Hier volgt een tabel met statussen die de meeste besturingselementen herkennen en gebruiken.

VisualState-naam VisualStateGroup-naam Beschrijving
Normal CommonStates De standaardstatus.
MouseOver CommonStates De muisaanwijzer bevindt zich boven het besturingselement.
Pressed CommonStates De Control-toets is ingedrukt.
Disabled CommonStates Het besturingselement is uitgeschakeld.
Focused FocusStates Het besturingselement heeft de focus.
Unfocused FocusStates Het besturingselement heeft geen focus.

Door een System.Windows.VisualStateManager te definiëren op het hoofdelement van een besturingselementsjabloon, kunt u animaties activeren wanneer een besturingselement een specifieke status invoert. De VisualStateManager declareert welke combinaties van VisualStateGroup en VisualState te bekijken. Wanneer het controle-element in een bewakingsstatus komt, wordt de animatie die is gedefinieerd door de VisualStateManager gestart.

Met de volgende XAML-code wordt bijvoorbeeld de status van CommonStates.MouseOver gecontroleerd om de vulkleur van het element backgroundElementte animeren. Wanneer het besturingselement terugkeert naar de status CommonStates.Normal, wordt de opvulkleur van het element met de naam backgroundElement hersteld.

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

        ...

Zie Storyboards Overviewvoor meer informatie over storyboards.

Gedeelde hulpmiddelen en thema's

Een typische WPF-app kan meerdere UI-resources hebben die in de app worden toegepast. Gezamenlijk kan deze set resources worden beschouwd als het thema voor de app. WPF biedt ondersteuning voor het verpakken van UI-resources als thema met behulp van een resourcewoordenlijst die is ingekapseld als de ResourceDictionary-klasse.

WPF-thema's worden gedefinieerd met behulp van het stijl- en sjabloonmechanisme dat WPF beschikbaar maakt voor het aanpassen van de visuals van elk element.

WPF-themaresources worden opgeslagen in ingesloten resourcedictionaries. Deze resourcedictionaries moeten worden ingesloten in een ondertekende assembly en kunnen worden ingesloten in dezelfde assembly als de code zelf of in een side-by-side-assembly. Voor PresentationFramework.dllbevindt de assembly die WPF-besturingselementen bevat, zich thema-bronnen in een reeks assembly's naast elkaar.

Het thema wordt de laatste plek om te zoeken bij het zoeken naar de stijl van een element. Normaal gesproken begint de zoekopdracht door binnen de elementenstructuur op zoek te gaan naar een geschikte resource, vervolgens de app-resourceverzameling te raadplegen en ten slotte het systeem te bevragen. Hierdoor kunnen app-ontwikkelaars de stijl voor elk object op structuur- of app-niveau opnieuw definiëren voordat ze het thema bereiken.

U kunt resourcewoordenlijsten definiëren als afzonderlijke bestanden waarmee u een thema in meerdere apps opnieuw kunt gebruiken. U kunt ook wisselbare thema's maken door meerdere resourcewoordenlijsten te definiëren die dezelfde typen resources bieden, maar met verschillende waarden. Het opnieuw definiëren van deze stijlen of andere resources op app-niveau is de aanbevolen benadering voor het aanpassen van de uitstraling van een app.

Als u een set resources, inclusief stijlen en sjablonen, wilt delen in verschillende apps, kunt u een XAML-bestand maken en een ResourceDictionary definiëren die verwijzingen naar een shared.xaml-bestand bevat.

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

Het is het delen van shared.xaml, dat zelf ResourceDictionary definieert die een set van stijl- en kwastresources bevat, waardoor de elementen in een app een consistent uiterlijk krijgen.

Voor meer informatie, zie samengevoegde bronnenwoordenlijsten.

Als u een thema voor uw aangepaste besturingselement maakt, raadpleegt u de Resources definiëren op themaniveau sectie van het Overzicht van het ontwerpen van besturingselementen.

Zie ook