Stili e modelli (WPF .NET)

Gli stili e le modelli di Windows Presentation Foundation (WPF) fanno riferimento a una suite di funzionalità che consentono agli sviluppatori e ai progettisti di creare effetti visivamente accattivanti e un aspetto coerente per il prodotto. Quando si personalizza l'aspetto di un'app, si vuole un modello di stili e modelli avanzata che consenta la manutenzione e la condivisione dell'aspetto all'interno e tra le app. WPF fornisce tale modello.

Un'altra funzionalità del modello di stile WPF è la separazione della presentazione e della logica. I progettisti possono lavorare sull'aspetto di un'app usando solo XAML contemporaneamente che gli sviluppatori lavorano sulla logica di programmazione usando C# o Visual Basic.

Questa panoramica è incentrata sugli aspetti relativi allo stile e alla creazione di modelli dell'app e non illustra i concetti di data binding. Per informazioni sul data binding, vedere Cenni preliminari sull'associazione dati.

È importante comprendere le risorse, quali sono gli stili e i modelli da riutilizzare. Per altre informazioni sulle risorse, vedere Panoramica delle risorse XAML.

Importante

La documentazione di Desktop Guide per .NET 7 e .NET 6 è in fase di costruzione.

Esempio

Il codice di esempio fornito in questa panoramica si basa su una semplice applicazione di esplorazione di foto illustrata nella figura seguente.

Styled ListView

Questo esempio usa stili e modelli per creare un'esperienza utente visivamente accattivante. L'esempio include due TextBlock elementi e un ListBox controllo associato a un elenco di immagini.

Per l'esempio completo, vedere Introduzione a un esempio di applicazione di stili e di modelli.

Stili

È possibile considerare un Style oggetto come un modo pratico per applicare un set di valori di proprietà a più elementi. È possibile usare uno stile su qualsiasi elemento che deriva da FrameworkElement o FrameworkContentElement , ad esempio , Window o Button.

Il modo più comune per dichiarare uno stile è una risorsa nella Resources sezione di un file XAML. Poiché gli stili sono risorse, obbediscono alle stesse regole di ambito applicabili a tutte le risorse. Basta, dove dichiari uno stile influisce sulla posizione in cui è possibile applicare lo stile. Ad esempio, se dichiari lo stile nell'elemento radice del file XAML di definizione dell'app, lo stile può essere usato ovunque nella tua app.

Ad esempio, il codice XAML seguente dichiara due stili per un TextBlockoggetto , uno applicato automaticamente a tutti gli TextBlock elementi e un altro a cui è necessario fare riferimento in modo esplicito.

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

Di seguito è riportato un esempio degli stili dichiarati in precedenza.

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

Styled textblocks

Per altre informazioni, vedere Creare uno stile per un controllo .

ControlTemplates

In WPF l'oggetto ControlTemplate di un controllo definisce l'aspetto del controllo . È possibile modificare la struttura e l'aspetto di un controllo definendo un nuovo ControlTemplate controllo e assegnandolo a un controllo . In molti casi, i modelli offrono una flessibilità sufficiente in modo che non sia necessario scrivere controlli personalizzati.

A ogni controllo è assegnato un modello predefinito alla proprietà Control.Template . Il modello connette la presentazione visiva del controllo con le funzionalità del controllo. Poiché definisci un modello in XAML, puoi modificare l'aspetto del controllo senza scrivere codice. Ogni modello è progettato per un controllo specifico, ad esempio .Button

In genere dichiari un modello come risorsa nella Resources sezione di un file XAML. Come per tutte le risorse, si applicano regole di ambito.

I modelli di controllo sono molto più coinvolti rispetto a uno stile. Il motivo è che il modello di controllo riscrive l'aspetto visivo dell'intero controllo, mentre uno stile applica semplicemente le modifiche delle proprietà al controllo esistente. Tuttavia, poiché il modello di un controllo viene applicato impostando la proprietà Control.Template , è possibile utilizzare uno stile per definire o impostare un modello.

Le finestre di progettazione in genere consentono di creare una copia di un modello esistente e modificarla. Ad esempio, nella finestra di progettazione WPF di Visual Studio selezionare un CheckBox controllo e quindi fare clic con il pulsante destro del mouse e scegliere Modifica modello>Crea una copia. Questo comando genera uno stile che definisce un modello.

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

La modifica di una copia di un modello è un ottimo modo per apprendere il funzionamento dei modelli. Anziché creare un nuovo modello vuoto, è più semplice modificare una copia e modificare alcuni aspetti della presentazione visiva.

Per un esempio, vedere Creare un modello per un controllo .

TemplateBinding

È possibile che la risorsa modello definita nella sezione precedente usi l'estensione di markup TemplateBinding. Un TemplateBinding è una forma ottimizzata di un'associazione per gli scenari di modello, analoga a un'associazione costruita con {Binding RelativeSource={RelativeSource TemplatedParent}}. TemplateBinding è utile per l'associazione di parti del modello alle proprietà del controllo. Ad esempio, ogni controllo ha una BorderThickness proprietà . Utilizzare un TemplateBinding oggetto per gestire l'elemento nel modello interessato da questa impostazione di controllo.

ContentControl e ItemsControl

Se un ContentPresenter oggetto viene dichiarato in ControlTemplate di , ContentControll'oggetto ContentPresenter verrà associato automaticamente alle ContentTemplate proprietà e Content . Analogamente, un oggetto ItemsPresenter che si trova in ControlTemplate di un ItemsControl verrà associato automaticamente alle ItemTemplate proprietà e Items .

DataTemplates

In questa app di esempio è presente un ListBox controllo associato a un elenco di foto.

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

L'aspetto ListBox è attualmente simile al seguente.

ListBox before applying template

La maggior parte dei controlli include un tipo di contenuto che spesso deriva da dati che si sceglie di associare. In questo esempio i dati sono l'elenco di foto. In WPF si usa un DataTemplate oggetto per definire la rappresentazione visiva dei dati. Fondamentalmente, ciò che si inserisce in un DataTemplate determina l'aspetto dei dati nell'app sottoposta a rendering.

Nell'app di esempio ogni oggetto personalizzato Photo ha una Source proprietà di tipo stringa che specifica il percorso del file dell'immagine. Gli oggetti foto in questo momento appaiono come percorsi file.

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

Per visualizzare le foto come immagini, si crea un oggetto DataTemplate come risorsa.

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

Si noti che la DataType proprietà è simile alla TargetType proprietà dell'oggetto Style. Se si DataTemplate trova nella sezione resources, quando si specifica la DataType proprietà su un tipo e si omette un x:Keyoggetto , viene DataTemplate applicato ogni volta che viene visualizzato tale tipo. È sempre possibile assegnare l'oggetto DataTemplate con e x:Key quindi impostarlo come oggetto StaticResource per le proprietà che accettano DataTemplate tipi, ad esempio la ItemTemplate proprietà o la ContentTemplate proprietà .

In sostanza, nell'esempio DataTemplate precedente viene definito che ogni volta che è presente un Photo oggetto , deve essere visualizzato come oggetto all'interno di Image un oggetto Border. Con questo DataTemplate, l'app ha ora un aspetto simile al seguente.

Photo image

Il modello di applicazione di modelli di dati fornisce altre funzionalità. Ad esempio, se vengono visualizzati dati di raccolta che contengono altre raccolte usando un HeaderedItemsControl tipo, ad esempio o Menu , TreeViewè presente .HierarchicalDataTemplate Un'altra funzionalità di creazione di modelli di dati è DataTemplateSelector, che consente di scegliere un DataTemplate oggetto da usare in base alla logica personalizzata. Per altre informazioni, vedere Cenni preliminari sui modelli di dati, in cui vengono discusse più dettagliatamente le diverse caratteristiche dei modelli di dati.

Trigger

Un trigger imposta proprietà o avvia azioni, ad esempio un'animazione, quando un valore di proprietà cambia o quando viene generato un evento. Style, ControlTemplatee DataTemplate tutti hanno una Triggers proprietà che può contenere un set di trigger. Esistono diversi tipi di trigger.

PropertyTriggers

Un Trigger oggetto che imposta i valori delle proprietà o avvia azioni in base al valore di una proprietà è denominato trigger di proprietà.

Per illustrare come usare i trigger di proprietà, è possibile rendere ogni ListBoxItem elemento parzialmente trasparente, a meno che non sia selezionato. Lo stile seguente imposta il Opacity valore di un ListBoxItem oggetto su 0.5. Quando la IsSelected proprietà è true, tuttavia, Opacity è impostata su 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>

Questo esempio usa un Trigger oggetto per impostare un valore di proprietà, ma si noti che la Trigger classe ha anche le EnterActions proprietà e ExitActions che consentono a un trigger di eseguire azioni.

Si noti che la MaxHeight proprietà di ListBoxItem è impostata su 75. Nella figura seguente, il terzo elemento è l'elemento selezionato.

Styled ListView

EventTrigger e storyboard

Un altro tipo di trigger è EventTrigger, che avvia un set di azioni in base all'occorrenza di un evento. Ad esempio, gli oggetti seguenti EventTrigger specificano che quando il puntatore del mouse entra in ListBoxItem, la MaxHeight proprietà viene animata su un valore di 90 oltre un 0.2 secondo periodo. Quando il puntatore del mouse viene spostato dall'elemento, la proprietà torna al valore originale in un 1 secondo. Si noti come non è necessario specificare un To valore per l'animazione MouseLeave . L'animazione è infatti in grado di tenere traccia del valore originale.

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

Per altre informazioni, vedere la panoramica degli storyboard.

Nella figura seguente il mouse punta al terzo elemento.

Styling sample screenshot

MultiTrigger, DataTrigger e MultiDataTrigger

Oltre a Trigger e EventTrigger, esistono altri tipi di trigger. MultiTrigger consente di impostare i valori delle proprietà in base a più condizioni. Si usa DataTrigger e MultiDataTrigger quando la proprietà della condizione è associata a dati.

Stati di visualizzazione

I controlli sono sempre in uno stato specifico. Ad esempio, quando il mouse si sposta sulla superficie di un controllo, il controllo viene considerato in uno stato comune di MouseOver. Un controllo senza uno stato specifico viene considerato nello stato comune Normal . Gli stati sono suddivisi in gruppi e gli stati indicati in precedenza fanno parte del gruppo CommonStatesdi stati . La maggior parte dei controlli ha due gruppi di stato: CommonStates e FocusStates. Di ogni gruppo di stato applicato a un controllo, un controllo è sempre in uno stato di ogni gruppo, ad esempio CommonStates.MouseOver e FocusStates.Unfocused. Tuttavia, un controllo non può trovarsi in due stati diversi all'interno dello stesso gruppo, ad esempio CommonStates.Normal e CommonStates.Disabled. Ecco una tabella degli stati che la maggior parte dei controlli riconosce e usa.

Nome VisualState Nome VisualStateGroup Descrizione
Normale CommonStates Lo stato predefinito.
MouseOver CommonStates Il puntatore del mouse è posizionato sul controllo.
Pressed CommonStates Il controllo è premuto.
Disabled CommonStates Il controllo è disabilitato.
Focused FocusStates Il controllo ha lo stato attivo.
Con stato non attivo FocusStates Il controllo non ha lo stato attivo.

Definendo un oggetto System.Windows.VisualStateManager sull'elemento radice di un modello di controllo, puoi attivare animazioni quando un controllo entra in uno stato specifico. VisualStateManager Dichiara le combinazioni di VisualStateGroup e VisualState da controllare. Quando il controllo entra in uno stato di controllo, viene avviata l'animazione VisualStateManager definita da .

Ad esempio, il codice XAML seguente controlla lo CommonStates.MouseOver stato per animare il colore di riempimento dell'elemento denominato backgroundElement. Quando il controllo torna allo CommonStates.Normal stato, viene ripristinato il colore di riempimento dell'elemento denominato backgroundElement .

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

        ...

Per altre informazioni sugli storyboard, vedere Cenni preliminari sugli storyboard.

Risorse condivise e temi

Un'app WPF tipica potrebbe avere più risorse dell'interfaccia utente applicate in tutta l'app. Collettivamente, questo set di risorse può essere considerato il tema per l'app. WPF fornisce supporto per la creazione di pacchetti di risorse dell'interfaccia utente come tema usando un dizionario risorse incapsulato come ResourceDictionary classe .

I temi WPF vengono definiti usando il meccanismo di applicazione di stili e modelli esposto da WPF per personalizzare gli oggetti visivi di qualsiasi elemento.

Le risorse del tema WPF vengono archiviate in dizionari risorse incorporati. Questi dizionari risorse devono essere incorporati all'interno di un assembly firmato e possono essere incorporati nello stesso assembly come codice o in un assembly affiancato. Per PresentationFramework.dll, l'assembly che contiene controlli WPF, le risorse del tema si trovano in una serie di assembly affiancati.

Il tema diventa l'ultimo posto in cui cercare lo stile di un elemento. In genere, la ricerca inizierà passando verso l'alto l'albero degli elementi cercando una risorsa appropriata, quindi cercare nella raccolta di risorse dell'app e infine eseguire una query sul sistema. In questo modo gli sviluppatori di app possono ridefinire lo stile per qualsiasi oggetto a livello di albero o app prima di raggiungere il tema.

È possibile definire dizionari risorse come singoli file che consentono di riutilizzare un tema in più app. È inoltre possibile creare temi scambiabili definendo più dizionari risorse che forniscono gli stessi tipi di risorse ma con valori diversi. La ridefinizione di questi stili o altre risorse a livello di app è l'approccio consigliato per l'interfaccia di un'app.

Per condividere un set di risorse, inclusi gli stili e i modelli, tra le app, puoi creare un file XAML e definire un ResourceDictionary oggetto che include un riferimento a un shared.xaml file.

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

È la condivisione di shared.xaml, che definisce un ResourceDictionary oggetto che contiene un set di risorse di stile e pennello, che consente ai controlli in un'app di avere un aspetto coerente.

Per altre informazioni, vedere Dizionari risorse uniti.

Se si sta creando un tema per il controllo personalizzato, vedere la sezione Definizione delle risorse a livello di tema della panoramica sulla creazione di controlli.

Vedi anche