Share via


Cenni preliminari sulla modifica di controlli

L'estensibilità del modello di controlli Windows Presentation Foundation (WPF) riduce notevolmente l'esigenza di creare un nuovo controllo. Tuttavia, in certi casi può ancora essere necessario creare un controllo personalizzato. In questo argomento vengono illustrate le funzionalità che riducono al minimo l'esigenza di creare un controllo personalizzato e i diversi modelli di modifica dei controlli in Windows Presentation Foundation (WPF). In questo argomento viene inoltre illustrato come creare un nuovo controllo.

Nel presente argomento sono contenute le seguenti sezioni.

  • Alternative alla scrittura di un nuovo controllo
  • Modelli per la modifica dei controlli
  • Nozioni fondamentali sulla modifica dei controlli
  • Argomenti correlati

Alternative alla scrittura di un nuovo controllo

In precedenza, per ottenere un'esperienza personalizzata da un controllo esistente, le modifiche erano limitate alle proprietà standard del controllo, ad esempio colore di sfondo, larghezza dei bordi e dimensioni del carattere. Per estendere l'aspetto o il comportamento di un controllo oltre tali parametri predefiniti era necessario creare un nuovo controllo, generalmente ereditando da un controllo esistente ed eseguendo l'override del metodo responsabile della creazione del controllo. Sebbene ciò sia ancora possibile, WPF consente di personalizzare i controlli esistenti mediante un modello di contenuto dettagliato, stili, modelli e trigger. Nell'elenco riportato di seguito vengono forniti alcuni esempi di utilizzo di tali funzionalità per la creazione di esperienze coerenti e personalizzate senza la necessità di creare un nuovo controllo.

  • Contenuto dettagliato Molti controlli WPF standard supportano il contenuto dettagliato. La proprietà di contenuto di Button, ad esempio, è di tipo Object. In teoria, pertanto, in Button è possibile visualizzare qualsiasi tipo di oggetto. Per visualizzare testo e immagini in un pulsante, è possibile aggiungere un'immagine e un oggetto TextBlock a StackPanel e assegnare StackPanel alla proprietà Content. Poiché i controlli sono in grado di visualizzare elementi visivi e dati arbitrari di WPF, vi è una minore esigenza di creare un nuovo controllo o di modificarne uno esistente per supportare una visualizzazione complessa. Per ulteriori informazioni sul modello di contenuto per Button e su altri modelli di contenuto in WPF, vedere Modello di contenuto WPF.

  • Stili Style è un insieme di valori che rappresentano le proprietà di un controllo. Utilizzando gli stili, è possibile creare una rappresentazione riutilizzabile dell'aspetto e del comportamento di un controllo desiderato senza scrivere un nuovo controllo. Si supponga ad esempio che si desideri applicare a tutti i controlli TextBlock il carattere Arial di colore rosso con una dimensione pari a 14. È possibile creare uno stile come risorsa e impostare di conseguenza le proprietà appropriate. Ogni oggetto TextBlock aggiunto all'applicazione avrà quindi lo stesso aspetto.

  • Modelli di dati DataTemplate consente di personalizzare la modalità di visualizzazione dei dati in un controllo. È ad esempio possibile utilizzare DataTemplate per specificare la modalità di visualizzazione dei dati in ListBox. Per un esempio, vedere Cenni preliminari sui modelli di dati. Oltre alla personalizzazione dell'aspetto dei dati, in DataTemplate possono essere inclusi elementi di interfaccia utente. Tale caratteristica aumenta la flessibilità nella personalizzazione dell'interfaccia utente stessa. Mediante DataTemplate, ad esempio, è possibile creare un oggetto ComboBox in cui ogni elemento contiene una casella di controllo.

  • Modelli di controllo. Per definire la struttura e l'aspetto di molti controlli WPF, viene utilizzato ControlTemplate, che separa l'aspetto di un controllo dalla relativa funzionalità. La ridefinizione di ControlTemplate consente di modificare radicalmente l'aspetto di un controllo. Si supponga, ad esempio, di voler utilizzare un controllo con l'aspetto di un semaforo. Il controllo dispone di una funzionalità e un'interfaccia utente semplici. È costituito da tre cerchi, che possono essere illuminati soltanto uno alla volta. RadioButton consente di fare in modo che venga selezionato un solo elemento alla volta, ma l'aspetto predefinito di RadioButton non ha nulla a che fare con le luci di un semaforo. Poiché per definire l'aspetto di RadioButton viene utilizzato un modello di controllo, è possibile ridefinire facilmente ControlTemplate in base ai requisiti del controllo e utilizzare pulsanti di opzione per realizzare il semaforo.

    NotaNota

    Anche se RadioButton può utilizzare DataTemplate, in questo esempio DataTemplate non è sufficiente.DataTemplate definisce l'aspetto del contenuto di un controllo.Nel caso di RadioButton, il contenuto è costituito da qualsiasi elemento visualizzato a destra del cerchio che indica la selezione di RadioButton.Nell'esempio del semaforo, il pulsante di opzione dovrà essere costituito semplicemente da un cerchio in grado di "illuminarsi". La notevole differenza tra il requisito relativo all'aspetto del semaforo e l'aspetto predefinito di RadioButton rende necessaria la ridefinizione di ControlTemplate.In genere, per la definizione del contenuto (o dei dati) di un controllo si utilizza DataTemplate, mentre per la definizione della struttura di un controllo si utilizza ControlTemplate.

  • Trigger Trigger consente di modificare in modo dinamico l'aspetto e il comportamento di un controllo senza creare un nuovo controllo. Si supponga, ad esempio, di avere più controlli ListBox nell'applicazione e che gli elementi selezionati di ciascun oggetto ListBox debbano essere visualizzati in grassetto e in rosso. La soluzione più ovvia sarebbe quella di creare una classe che eredita da ListBox e di eseguire l'override del metodo OnSelectionChanged per modificare l'aspetto dell'elemento selezionato. Tuttavia, un approccio migliore consiste nell'aggiunta di un trigger a uno stile di un oggetto ListBoxItem che modifichi l'aspetto dell'elemento selezionato. Un trigger consente di modificare i valori delle proprietà o di eseguire specifiche operazioni in base al valore di una proprietà. EventTrigger consente di intraprendere determinate azioni quando si verifica un evento.

Per ulteriori informazioni su stili, modelli e trigger, vedere Applicazione di stili e modelli.

In genere, se il controllo rispecchia la funzionalità di un controllo esistente ma si desidera che abbia un aspetto diverso, valutare innanzitutto la possibilità di utilizzare uno dei metodi descritti in questa sezione per modificare l'aspetto del controllo esistente.

Modelli per la modifica dei controlli

Modello di contenuto dettagliato, stili, modelli e trigger riducono al minimo la necessità di creare un nuovo controllo. Se, tuttavia, è necessario creare un nuovo controllo, è importante conoscere i diversi modelli di creazione dei controlli in WPF. In WPF sono disponibili tre modelli generali per la creazione di un controllo, ognuno dei quali offre un insieme di funzionalità e un livello di flessibilità differenti. Le classi base dei tre modelli sono: UserControl, Control e FrameworkElement.

Derivazione dall'oggetto UserControl

Il modo più semplice per creare un controllo in WPF consiste nel farlo derivare da UserControl. Nella compilazione di un controllo che eredita da UserControl, è possibile aggiungere componenti esistenti a UserControl, denominare i componenti e fare riferimento a gestori eventi nel codice Extensible Application Markup Language (XAML). Sarà quindi possibile fare riferimento agli elementi denominati e definire i gestori eventi nel codice. Questo modello di sviluppo è molto simile al modello utilizzato per lo sviluppo di applicazioni in WPF.

Se compilato correttamente, UserControl può sfruttare i vantaggi del contenuto dettagliato, degli stili e dei trigger. Se, tuttavia, il controllo eredita da UserControl, gli utenti di tale controllo non saranno in grado di utilizzare DataTemplate o ControlTemplate per personalizzarne l'aspetto. Per creare un controllo personalizzato che supporti i modelli, è necessario derivare dalla classe Control o da una delle relative classi derivate (diverse da UserControl).

Vantaggi della derivazione dall'oggetto UserControl

Si consideri di utilizzare la derivazione da UserControl in presenza di tutte le condizioni seguenti:

  • Si desidera compilare il controllo in modo analogo alla compilazione di un'applicazione.

  • Il controllo è costituito esclusivamente da componenti esistenti.

  • Non è necessario supportare una personalizzazione complessa.

Derivazione dall'oggetto Control

La derivazione dalla classe Control è il modello utilizzato dalla maggior parte dei controlli WPF esistenti. Quando si crea un controllo che eredita dalla classe Control, è possibile definirne l'aspetto mediante modelli. In tal modo, la logica operativa viene separata dalla rappresentazione visiva. È inoltre possibile ottenere la separazione tra logica e interfaccia utente mediante l'uso di comandi e associazioni anziché di eventi, evitando laddove possibile riferimenti agli elementi di ControlTemplate. Se la logica e l'interfaccia utente del controllo sono separate in modo appropriato, l'utente del controllo sarà in grado di ridefinire l'oggetto ControlTemplate del controllo per personalizzarne l'aspetto. Sebbene la compilazione di un oggetto Control personalizzato non sia semplice come la compilazione di UserControl, un oggetto Control personalizzato offre la massima flessibilità.

Vantaggi della derivazione dall'oggetto Control

Si consideri di utilizzare la derivazione da Control anziché la classe UserControl in presenza di una qualsiasi delle condizioni seguenti:

  • Si desidera che l'aspetto del controllo sia personalizzabile tramite ControlTemplate.

  • Si desidera che il controllo supporti temi diversi.

Derivazione dall'oggetto FrameworkElement

I controlli che derivano da UserControl o Control si basano sulla composizione di elementi esistenti. Per molti scenari si tratta di una soluzione accettabile, poiché qualsiasi oggetto che eredita da FrameworkElement può essere incluso in ControlTemplate. A volte, tuttavia, le funzionalità della composizione semplice di elementi non sono sufficienti per definire l'aspetto di un controllo. Per questi scenari, basare un componente su FrameworkElement è la scelta giusta.

Esistono due metodi standard per compilare componenti basati su FrameworkElement: rendering diretto e composizione di elementi personalizzati. Il rendering diretto comporta l'esecuzione dell'override del metodo OnRender di FrameworkElement e la specifica di operazioni DrawingContext che definiscono in modo esplicito gli elementi visivi del componente. Questo è il metodo utilizzato da Image e Border. La composizione di elementi personalizzati comporta l'utilizzo di oggetti di tipo Visual per comporre l'aspetto del componente. Per un esempio, vedere Utilizzo degli oggetti DrawingVisual. Track è un esempio di controllo di WPF che utilizza la composizione di elementi personalizzati. È inoltre possibile combinare il rendering diretto e la composizione di elementi personalizzati nello stesso controllo.

Vantaggi della derivazione dall'oggetto FrameworkElement

Si consideri di utilizzare la derivazione da FrameworkElement in presenza di una qualsiasi delle condizioni seguenti:

  • Si desidera disporre di un controllo più preciso sull'aspetto del controllo oltre a quello offerto dalla semplice composizione di elementi.

  • Si desidera definire l'aspetto del controllo definendo una logica di rendering personalizzata.

  • Si desidera comporre gli elementi esistenti in modalità nuove che vanno oltre le possibilità offerte da UserControl e Control.

Nozioni fondamentali sulla modifica dei controlli

Come illustrato in precedenza, tra le funzionalità più efficaci di WPF vi è la possibilità di andare oltre l'impostazione delle proprietà di base di un controllo per modificarne l'aspetto e il comportamento senza dover necessariamente creare un controllo personalizzato. Le funzionalità di applicazione di stili, associazione dati e trigger sono possibili grazie al sistema di proprietà WPF e al sistema di eventi WPF. Nelle sezioni seguenti vengono descritte alcune procedure che è necessario seguire, indipendentemente dal modello utilizzato per creare il controllo personalizzato, in modo che gli utenti del controllo personalizzato siano in grado di utilizzare tali funzionalità come se si trattasse di un controllo incluso in WPF. 

Utilizzare proprietà di dipendenza

Con le proprietà di dipendenza è possibile eseguire le operazioni indicate di seguito:

  • Impostare la proprietà in uno stile.

  • Associare la proprietà a un'origine dati.

  • Utilizzare una risorsa dinamica come valore della proprietà.

  • Animare la proprietà.

Perché una proprietà del controllo supporti una di tali funzionalità, è necessario implementarla come proprietà di dipendenza. Nell'esempio seguente viene definita una proprietà di dipendenza denominata Value mediante le operazioni seguenti:

  • Definizione di un identificatore DependencyProperty denominato ValueProperty come campo static public readonly.

  • Registrazione del nome della proprietà nel sistema di proprietà mediante una chiamata a DependencyProperty.Register per specificare gli elementi seguenti:

    • Nome della proprietà.

    • Tipo della proprietà.

    • Tipo proprietario della proprietà.

    • Metadati della proprietà. I metadati contengono il valore predefinito della proprietà, CoerceValueCallback e PropertyChangedCallback.

  • Definizione di una proprietà del wrapper CLR denominata Value, ovvero lo stesso nome utilizzato per la registrazione della proprietà di dipendenza, mediante l'implementazione delle funzioni di accesso get e set della proprietà. Si noti che le funzioni di accesso get e set sono unicamente in grado di chiamare rispettivamente GetValue e SetValue. È consigliabile che le funzioni di accesso delle proprietà di dipendenza non contengano logica aggiuntiva poiché i client e WPF possono ignorare le funzioni di accesso e chiamare direttamente GetValue e SetValue. Ad esempio, quando una proprietà è associata a un'origine dati, la funzione di accesso set della proprietà non viene chiamata. Anziché aggiungere logica aggiuntiva alle funzioni di accesso get e set, utilizzare i delegati ValidateValueCallback, CoerceValueCallback e PropertyChangedCallback per rispondere o controllare il valore in caso di modifica. Per ulteriori informazioni su tali callback, vedere Callback e convalida delle proprietà di dipendenza.

  • Definire un metodo per CoerceValueCallback denominato CoerceValue. CoerceValue garantisce che Value sia maggiore o uguale a MinValue e minore o uguale a MaxValue.

  • Definire un metodo per PropertyChangedCallback denominato OnValueChanged. OnValueChanged crea un oggetto RoutedPropertyChangedEventArgs<T> e prepara la generazione dell'evento indirizzato ValueChanged. Gli eventi indirizzati verranno illustrati nella sezione successiva.

        ''' <summary>
        ''' Identifies the Value dependency property.
        ''' </summary>
        Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))

        ''' <summary>
        ''' Gets or sets the value assigned to the control.
        ''' </summary>
        Public Property Value() As Decimal
            Get
                Return CDec(GetValue(ValueProperty))
            End Get
            Set(ByVal value As Decimal)
                SetValue(ValueProperty, value)
            End Set
        End Property

        Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
            Dim newValue As Decimal = CDec(value)
            Dim control As NumericUpDown = CType(element, NumericUpDown)

            newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))

            Return newValue
        End Function

        Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
            Dim control As NumericUpDown = CType(obj, NumericUpDown)

            Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
            control.OnValueChanged(e)
        End Sub
/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value", typeof(decimal), typeof(NumericUpDown),
        new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
                                      new CoerceValueCallback(CoerceValue)));

/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{          
    get { return (decimal)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

private static object CoerceValue(DependencyObject element, object value)
{
    decimal newValue = (decimal)value;
    NumericUpDown control = (NumericUpDown)element;

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));

    return newValue;
}

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    NumericUpDown control = (NumericUpDown)obj;         

    RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
        (decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
    control.OnValueChanged(e);
}

Per ulteriori informazioni, vedere Proprietà Dependency personalizzate.

Utilizzo di eventi indirizzati

Analogamente alle proprietà di dipendenza, che estendono il concetto di proprietà CLR con funzionalità aggiuntive, gli eventi indirizzati estendono il concetto di eventi CLR standard. Quando si crea un nuovo controllo WPF, è inoltre consigliabile implementare l'evento come evento indirizzato, in quanto gli eventi indirizzati supportano il comportamento seguente:

  • Possibilità di gestire eventi in un elemento padre di più controlli. Se si tratta di un evento di bubbling, un solo elemento padre nella struttura ad albero dell'elemento è in grado di sottoscrivere l'evento. Gli autori dell'applicazione potranno quindi utilizzare un solo gestore per rispondere all'evento di più controlli. Se, ad esempio, il controllo fa parte di ciascun elemento di ListBox in quanto incluso in DataTemplate, lo sviluppatore di applicazioni potrà definire il gestore eventi per l'evento del controllo in ListBox. Il gestore eventi viene chiamato ogni volta che l'evento si verifica in uno dei controlli.

  • Possibilità di utilizzare eventi indirizzati in EventSetter, che consente agli sviluppatori di applicazioni di specificare il gestore di un evento all'interno di uno stile.

  • Possibilità di utilizzare eventi indirizzati in EventTrigger, che consente l'animazione di proprietà mediante XAML. Per ulteriori informazioni, vedere Cenni preliminari sull'animazione.

Nell'esempio seguente viene definito un evento indirizzato effettuando le operazioni seguenti:

  • Definizione di un identificatore RoutedEvent denominato ValueChangedEvent come campo static public readonly.

  • Registrazione di un evento indirizzato mediante la chiamata al metodo EventManager.RegisterRoutedEvent. Nell'esempio vengono specificate le informazioni indicate di seguito nella chiamata a RegisterRoutedEvent:

    • Il nome dell'evento è ValueChanged.

    • La strategia di routing è Bubble: vale a dire che viene chiamato innanzitutto un gestore eventi nell'origine (l'oggetto che genera l'evento); vengono quindi chiamati in successione i gestori eventi negli elementi padre dell'origine, a partire dal gestore eventi nell'elemento padre più vicino.

    • Il tipo del gestore eventi è RoutedPropertyChangedEventHandler<T>, costruito con un tipo Decimal.

    • Il tipo proprietario dell'evento è NumericUpDown.

  • Dichiarazione di un evento pubblico denominato ValueChanged e inclusione di dichiarazioni delle funzioni di accesso agli eventi. Nell'esempio viene chiamato l'oggetto AddHandler nella dichiarazione della funzione di accesso add e RemoveHandler nella dichiarazione della funzione di accesso remove per utilizzare i servizi eventi di WPF.

  • Creazione di un metodo virtuale protetto denominato OnValueChanged che genera l'evento ValueChanged.

        ''' <summary>
        ''' Identifies the ValueChanged routed event.
        ''' </summary>
        Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))

        ''' <summary>
        ''' Occurs when the Value property changes.
        ''' </summary>
        Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
            AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
                MyBase.AddHandler(ValueChangedEvent, value)
            End AddHandler
            RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
                MyBase.RemoveHandler(ValueChangedEvent, value)
            End RemoveHandler
            RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
            End RaiseEvent
        End Event

        ''' <summary>
        ''' Raises the ValueChanged event.
        ''' </summary>
        ''' <param name="args">Arguments associated with the ValueChanged event.</param>
        Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
            MyBase.RaiseEvent(args)
        End Sub
/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
    "ValueChanged", RoutingStrategy.Bubble, 
    typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

/// <summary>
/// Occurs when the Value property changes.
/// </summary>
public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
{
    add { AddHandler(ValueChangedEvent, value); }
    remove { RemoveHandler(ValueChangedEvent, value); }
}

/// <summary>
/// Raises the ValueChanged event.
/// </summary>
/// <param name="args">Arguments associated with the ValueChanged event.</param>
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
{
    RaiseEvent(args);
}

Per ulteriori informazioni, vedere Cenni preliminari sugli eventi indirizzati e Procedura: creare un evento indirizzato personalizzato.

Utilizzare l'associazione

Per separare l'interfaccia utente del controllo dalla relativa logica, è possibile utilizzare l'associazione dati. Tale operazione risulta particolarmente importante se si definisce l'aspetto del controllo mediante ControlTemplate. L'utilizzo dell'associazione dati consente di eliminare la necessità di fare riferimento a parti specifiche dell'interfaccia utente dal codice. È consigliabile non fare riferimento a elementi presenti in ControlTemplate poiché quando il codice fa riferimento a elementi presenti in ControlTemplate e ControlTemplate viene modificato, l'elemento a cui si fa riferimento deve essere incluso nel nuovo oggetto ControlTemplate.

Nell'esempio riportato di seguito viene aggiornato l'oggetto TextBlock del controllo NumericUpDown mediante l'assegnazione di un nome e l'uso del nome nel codice per fare riferimento alla casella di testo.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
  <TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
        Private Sub UpdateTextBlock()
            valueText.Text = Value.ToString()
        End Sub
private void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}

Negli esempi riportati di seguito viene utilizzata l'associazione per ottenere lo stesso risultato.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">

    <!--Bind the TextBlock to the Value property-->
    <TextBlock 
        Width="60" TextAlignment="Right" Padding="5"
        Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                       AncestorType={x:Type local:NumericUpDown}}, 
                       Path=Value}"/>

</Border>

Per ulteriori informazioni sull'associazione dati, vedere Cenni preliminari sull'associazione dati.

Progettare le finestre di progettazione

Per ottenere supporto per i controlli WPF personalizzati in WPF Designer per Visual Studio (ad esempio, la modifica delle proprietà con la finestra Proprietà), attenersi alle linee guida riportate di seguito. Per ulteriori informazioni sullo sviluppo per WPF Designer, vedere WPF Designer.

Proprietà di dipendenza

Implementare le funzioni di accesso CLR get e set come descritto in precedenza in "Utilizzare proprietà di dipendenza". Nelle finestre di progettazione è possibile utilizzare il wrapper per rilevare la presenza di una proprietà di dipendenza, ma, come accade per WPF e per i client del controllo, non è necessario chiamare le funzioni di accesso quando si ottiene o si imposta la proprietà.

Proprietà associate

È necessario implementare le proprietà associate sui controlli personalizzati attenendosi alle linee guida riportate di seguito:

  • Disporre di un oggetto DependencyProperty static public readonly nel formato NomeProprietàProperty creato utilizzando il metodo RegisterAttached. Il nome della proprietà passato a RegisterAttached deve corrispondere a NomeProprietà.

  • Implementare una coppia di metodi CLR publicstatic denominati SetNomeProprietà e GetNomeProprietà. Entrambi i metodi devono accettare una classe derivata da DependencyProperty come primo argomento. Il metodo SetNomeProprietà accetta anche un argomento il cui tipo corrisponde al tipo di dati registrato per la proprietà. Il metodo GetNomeProprietà deve restituire un valore dello stesso tipo. In assenza del metodo SetNomeProprietà, la proprietà viene contrassegnata come di sola lettura.

  • SetNomeProprietà e GetNomeProprietà devono essere indirizzati rispettivamente ai metodi GetValue e SetValue sull'oggetto di dipendenza di destinazione in modo diretto. Le finestre di progettazione consentono di accedere alla proprietà associata tramite chiamate rivolte al wrapper del metodo oppure tramite chiamata diretta all'oggetto di dipendenza di destinazione.

Per ulteriori informazioni sulle proprietà associate, vedere Cenni preliminari sulle proprietà associate.

Definire e utilizzare risorse condivise

È possibile includere il controllo nello stesso assembly dell'applicazione o è possibile inserire il controllo in un package di un assembly separato che può essere utilizzato in più applicazioni. Nella maggior parte dei casi, le informazioni riportate in questo argomento si applicano indipendentemente dal metodo utilizzato. È importante tuttavia notare una differenza. Quando si inserisce un controllo nello stesso assembly di un'applicazione, è possibile aggiungere risorse globali al file App.xaml. Il file App.xaml non è invece disponibile per gli assembly contenenti solo controlli che non dispongono di un oggetto Application associato.

Quando un'applicazione cerca una risorsa, la ricerca viene effettuata a tre livelli nell'ordine seguente:

  1. Livello dell'elemento.

    Il sistema inizia dall'elemento che fa riferimento alla risorsa, quindi esegue la ricerca delle risorse dell'elemento padre logico continuando fino a raggiungere l'elemento radice.

  2. Livello dell'applicazione.

    Risorse definite dall'oggetto Application.

  3. Livello del tema.

    I dizionari a livello tema vengono archiviati in una sottocartella denominata Themes. I file nella cartella Themes corrispondono ai temi. Potrebbero ad esempio essere presenti Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml e così via. È inoltre possibile disporre di un file denominato generic.xaml. Quando il sistema cerca una risorsa a livello tema, la ricerca viene effettuata prima nel file del tema specifico e poi nel file generic.xaml.

Quando il controllo si trova in un assembly separato dall'applicazione, è necessario inserire le risorse globali a livello elemento o a livello tema. Entrambi i metodi presentano dei vantaggi.

Definizione di risorse al livello elemento

È possibile definire le risorse condivise a livello elemento creando un dizionario risorse personalizzato e unendolo al dizionario risorse del controllo. Quando si utilizza questo metodo, è possibile assegnare un nome qualsiasi al file di risorse e posizionare il file nella stessa cartella dei controlli. Le risorse a livello elemento possono inoltre utilizzare semplici stringhe come chiavi. Nell'esempio seguente viene creato un file di risorse di LinearGradientBrush denominato Dictionary1.xaml.

<ResourceDictionary 
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
  <LinearGradientBrush 
    x:Key="myBrush"  
    StartPoint="0,0" EndPoint="1,1">
    <GradientStop Color="Red" Offset="0.25" />
    <GradientStop Color="Blue" Offset="0.75" />
  </LinearGradientBrush>

</ResourceDictionary>

Dopo aver definito il dizionario, è necessario unirlo al dizionario risorse del controllo. A tale scopo è possibile utilizzare XAML o il codice.

Nell'esempio seguente viene unito un dizionario risorse mediante XAML.

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

Questo approccio comporta tuttavia la creazione di un oggetto ResourceDictionary ogni volta che vi si fa riferimento. Ad esempio, se si dispone di 10 controlli personalizzati nella libreria e si uniscono i dizionari delle risorse condivise di ogni controllo mediante XAML, si creano 10 oggetti ResourceDictionary identici. È possibile evitare questo problema creando una classe statica che unisce le risorse nel codice e restituisce l'oggetto ResourceDictionary risultante.

Nell'esempio seguente viene creata una classe che restituisce un oggetto ResourceDictionary condiviso.

internal static class SharedDictionaryManager
{
    internal static ResourceDictionary SharedDictionary
    {
        get
        {
            if (_sharedDictionary == null)
            {
                System.Uri resourceLocater =
                    new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml", 
                                    System.UriKind.Relative);

                _sharedDictionary = 
                    (ResourceDictionary)Application.LoadComponent(resourceLocater);
            }

            return _sharedDictionary;
        }
    }

    private static ResourceDictionary _sharedDictionary;
}

Nell'esempio seguente viene unita la risorsa condivisa alle risorse di un controllo personalizzato nel costruttore del controllo prima che venga chiamato InitilizeComponent. Poiché SharedDictionaryManager.SharedDictionary è una proprietà statica, l'oggetto ResourceDictionary viene creato solo una volta. Poiché il dizionario risorse è stato unito prima che venisse chiamato InitializeComponent, le risorse sono disponibili al controllo nel file XAML.

public NumericUpDown()
{
    this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
    InitializeComponent();

}

Definizione di risorse al livello tema

WPF consente di creare risorse per diversi temi di Windows. Come autore dei controlli, è possibile definire una risorsa per un tema specifico in modo da modificare l'aspetto del controllo a seconda del tema utilizzato. Ad esempio, l'aspetto di un oggetto Button nel tema Windows Classico, ovvero il tema predefinito per Windows 2000, differisce da un oggetto Button nel tema Windows Luna, ovvero il tema predefinito per Windows XP, poiché l'oggetto Button utilizza un oggetto ControlTemplate diverso per ogni tema.

Le risorse specifiche di un tema vengono inserite in un dizionario risorse con un nome file specifico. Questi file devono risiedere in una cartella denominata Themes che è una sottocartella della cartella contenente il controllo. Nella tabella seguente sono elencati i file del dizionario risorse e il tema associato a ogni file:

Nome del file del dizionario risorse

Tema di Windows

Classic.xaml

Aspetto classico di Windows 9x/2000 in Windows XP

Luna.NormalColor.xaml

Tema blu predefinito in Windows XP

Luna.Homestead.xaml

Tema verde oliva in Windows XP

Luna.Metallic.xaml

Tema argento in Windows XP

Royale.NormalColor.xaml

Tema predefinito in Windows XP Media Center Edition

Aero.NormalColor.xaml

Tema predefinito in Windows Vista

Non è necessario definire una risorsa per ogni tema. Se una risorsa non è definita per un tema specifico, il controllo verifica Classic.xaml per tale risorsa. Se la risorsa non è definita nel file che corrisponde al tema corrente o in Classic.xaml, il controllo utilizza la risorsa generica, che si trova in un file del dizionario risorse denominato generic.xaml. Il file generic.xaml è posto nella stessa cartella dei file dei dizionari risorse specifici del tema. Sebbene generic.xaml non corrisponda a un tema di Windows specifico, è ancora un dizionario a livello tema.

L'esempio di controllo personalizzato NumericUpDown con supporto per temi e animazione interfaccia utente contiene due dizionari risorse per li controllo NumericUpDown: uno in generic.xaml e uno in Luna.NormalColor.xaml. È possibile eseguire l'applicazione e passare dal tema argento in Windows XP a un altro tema per vedere la differenza tra i due modelli del controllo. Se si utilizza Windows Vista, è possibile rinominare Luna.NormalColor.xaml in Aero.NormalColor.xaml e passare da un tema all'altro, ad esempio Windows Classic e il tema predefinito per Windows Vista.

Quando si inserisce un oggetto ControlTemplate in uno dei file dei dizionari risorse specifici del tema, è necessario creare un costruttore statico per il controllo e chiamare il metodo OverrideMetadata(Type, PropertyMetadata) su DefaultStyleKey, come illustrato nell'esempio seguente.

        Shared Sub New()
            DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
        End Sub
static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}

Definizione delle chiavi e relativo riferimento per le risorse del tema

Quando si definisce una risorsa a livello elemento, è possibile assegnare una stringa come chiave e accedere alla risorsa tramite la stringa. Quando si definisce una risorsa a livello tema, è necessario utilizzare un oggetto ComponentResourceKey come chiave. Nell'esempio seguente viene definita una risorsa nel file generic.xaml.

<LinearGradientBrush 
     x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter}, 
                                  ResourceId=MyEllipseBrush}"  
                                  StartPoint="0,0" EndPoint="1,0">
    <GradientStop Color="Blue" Offset="0" />
    <GradientStop Color="Red" Offset="0.5" />
    <GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>

Nell'esempio seguente viene fatto riferimento alla risorsa specificando l'oggetto ComponentResourceKey come chiave.

<RepeatButton 
    Grid.Column="1" Grid.Row="0"
    Background="{StaticResource {ComponentResourceKey 
                        TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                        ResourceId=ButtonBrush}}">
    Up
</RepeatButton>
<RepeatButton 
    Grid.Column="1" Grid.Row="1"
    Background="{StaticResource {ComponentResourceKey 
                    TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                    ResourceId=ButtonBrush}}">
    Down
 </RepeatButton>

Specifica del percorso delle risorse del tema

Per trovare le risorse per un controllo, è necessario indicare all'applicazione host che l'assembly contiene risorse specifiche del controllo. È possibile eseguire questa operazione aggiungendo l'oggetto ThemeInfoAttribute all'assembly che contiene il controllo. L'oggetto ThemeInfoAttribute contiene una proprietà GenericDictionaryLocation che specifica il percorso delle risorse generiche e una proprietà ThemeDictionaryLocation che specifica il percorso delle risorse specifiche del tema.

Nell'esempio seguente vengono impostate le proprietà GenericDictionaryLocation e ThemeDictionaryLocation su SourceAssembly, per specificare che le risorse generiche e quelle specifiche del tema risiedono nello stesso assembly del controllo.

<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, 
           ResourceDictionaryLocation.SourceAssembly)]

Vedere anche

Concetti

URI di tipo pack in WPF

Altre risorse

WPF Designer

Personalizzazione dei controlli