Panoramica della creazione di controlli

L'estendibilità del modello di controllo Windows Presentation Foundation (WPF) riduce notevolmente la necessità di creare un nuovo controllo. In alcuni casi può tuttavia essere ancora necessario creare un controllo personalizzato. Questo argomento illustra le funzionalità che riducono al minimo la necessità di creare un controllo personalizzato e i diversi modelli di creazione di controlli in Windows Presentation Foundation (WPF). Viene anche illustrato come creare un nuovo controllo.

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, spessore del bordo e dimensioni del carattere. Per estendere l'aspetto o il comportamento di un controllo oltre questi parametri predefiniti, era necessario creare un nuovo controllo, in genere ereditando da un controllo esistente ed eseguendo l'override del metodo responsabile del disegno del controllo. Anche se questa è ancora un'opzione, WPF consente di personalizzare i controlli esistenti usando la relativa funzionalità avanzata di con modalità tenda l, stili, modelli e trigger. L'elenco seguente fornisce alcuni esempi relativi a come usare queste funzionalità per creare esperienze coerenti e personalizzate senza dover creare un nuovo controllo.

  • Contenuto avanzato. Molti dei controlli WPF standard supportano contenuti avanzati. Ad esempio, la proprietà del contenuto di un Button oggetto è di tipo Object, quindi teoricamente qualsiasi elemento può essere visualizzato in un oggetto Button. Per fare in modo che un pulsante visualizzi un'immagine e un testo, puoi aggiungere un'immagine e un TextBlock oggetto a e StackPanel assegnarlo StackPanel alla Content proprietà . Poiché i controlli possono visualizzare elementi visivi WPF e dati arbitrari, è meno necessario creare un nuovo controllo o modificare un controllo esistente per supportare una visualizzazione complessa. Per altre informazioni sul con modalità tenda l per Button e altri con modalità tenda ls in WPF, vedere Modello di contenuto WPF.

  • Stili. Un Style oggetto è una raccolta di valori che rappresentano le proprietà di un controllo . Usando gli stili, è possibile creare una rappresentazione riutilizzabile dell'aspetto e del comportamento desiderati di un controllo, senza scriverne uno nuovo. Si supponga, ad esempio, che tutti i controlli abbiano un tipo di TextBlock carattere rosso, Arial con dimensioni del carattere pari a 14. È possibile creare uno stile come risorsa e impostare le proprietà appropriate di conseguenza. TextBlock Ogni elemento aggiunto all'applicazione avrà quindi lo stesso aspetto.

  • Modelli di dati. Un DataTemplate consente di personalizzare la modalità di visualizzazione dei dati in un controllo . Ad esempio, un DataTemplate oggetto può essere usato per specificare la modalità di visualizzazione dei dati in un oggetto ListBox. Per un esempio, vedere Cenni preliminari sui modelli di dati. Oltre a personalizzare l'aspetto dei dati, un DataTemplate può includere elementi dell'interfaccia utente, che offre una notevole flessibilità nelle interfacce utente personalizzate. Ad esempio, usando un DataTemplateoggetto , è possibile creare un oggetto ComboBox in cui ogni elemento contiene una casella di controllo.

  • Modelli di controllo. Molti controlli in WPF usano un ControlTemplate oggetto per definire la struttura e l'aspetto del controllo, che separa l'aspetto di un controllo dalla funzionalità del controllo. È possibile modificare drasticamente l'aspetto di un controllo definendone ControlTemplate. Si supponga, ad esempio, di voler creare un controllo con l'aspetto di un semaforo. Il controllo ha una funzionalità e un'interfaccia utente semplici. È costituito da tre cerchi, che possono essere illuminati soltanto uno alla volta. Dopo alcuni riflessi, si potrebbe rendersi conto che un offre RadioButton la funzionalità di un solo oggetto selezionato alla volta, ma l'aspetto predefinito dell'oggetto RadioButton non assomiglia alle luci su un semaforo. RadioButton Poiché usa un modello di controllo per definirne l'aspetto, è facile ridefinire per ControlTemplate soddisfare i requisiti del controllo e usare i pulsanti di opzione per rendere la luce di arresto.

    Nota

    Anche se un RadioButton oggetto può usare , DataTemplateun oggetto DataTemplate non è sufficiente in questo esempio. Definisce DataTemplate l'aspetto del contenuto di un controllo . Nel caso di , RadioButtonil contenuto appare a destra del cerchio che indica se RadioButton è selezionato . Nell'esempio del semaforo, il pulsante di opzione deve essere solo un cerchio che può "accendere". Poiché il requisito di aspetto per il semaforo è così diverso dall'aspetto predefinito di RadioButton, è necessario ridefinire l'oggetto ControlTemplate. In generale viene usato un DataTemplate oggetto per definire il contenuto (o i dati) di un controllo e viene ControlTemplate usato per definire la struttura di un controllo.

  • Triggers (Trigger). Un Trigger oggetto consente di modificare dinamicamente l'aspetto e il comportamento di un controllo senza creare un nuovo controllo. Si supponga, ad esempio, di avere più ListBox controlli nell'applicazione e di volere che gli elementi in ognuno ListBox siano in grassetto e rosso quando vengono selezionati. Il primo istinto potrebbe essere quello di creare una classe che eredita da ListBox ed esegue l'override del OnSelectionChanged metodo per modificare l'aspetto dell'elemento selezionato, ma un approccio migliore consiste nell'aggiungere un trigger a uno stile di un ListBoxItem oggetto che modifica l'aspetto dell'elemento selezionato. Un trigger consente di modificare i valori delle proprietà o di eseguire determinate azioni in base al valore di una proprietà. Un EventTrigger consente di eseguire azioni quando si verifica un evento.

Per altre 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 vuole che abbia un aspetto diverso, valutare innanzitutto la possibilità di usare uno dei metodi descritti in questa sezione per modificare l'aspetto del controllo esistente.

Modelli per la modifica dei controlli

Il modello di contenuto avanzato, gli stili, i modelli e i trigger riducono al minimo la necessità di creare un nuovo controllo. Tuttavia, se è necessario creare un nuovo controllo, è importante comprendere i diversi modelli di creazione di controlli in WPF. WPF offre tre modelli generali per la creazione di un controllo, ognuno dei quali offre un set diverso di funzionalità e livello di flessibilità. Le classi di base per i tre modelli sono UserControl, Controle FrameworkElement.

Derivazione da UserControl

Il modo più semplice per creare un controllo in WPF consiste nel derivare da UserControl. Quando si compila un controllo che eredita da UserControl, si aggiungono componenti esistenti a UserControl, denominare i componenti e i gestori eventi di riferimento in XAML. È quindi possibile fare riferimento agli elementi denominati e definire i gestori eventi nel codice. Questo modello di sviluppo è molto simile al modello usato per lo sviluppo di applicazioni in WPF.

Se compilata correttamente, un UserControl oggetto può sfruttare i vantaggi di contenuti, stili e trigger avanzati. Tuttavia, se il controllo eredita da UserControl, gli utenti che usano il controllo non potranno usare o DataTemplate ControlTemplate per personalizzarne l'aspetto. È necessario derivare dalla classe o da Control una delle relative classi derivate (diverse UserControlda ) per creare un controllo personalizzato che supporti i modelli.

Vantaggi della derivazione da UserControl

Prendere in considerazione la derivazione da UserControl se si applicano tutte le condizioni seguenti:

  • Si vuole creare il controllo in modo analogo a come si crea un'applicazione.

  • Il controllo è costituito esclusivamente da componenti esistenti.

  • Non è necessario supportare una personalizzazione complessa.

Derivazione da Control

La derivazione dalla Control classe è il modello usato dalla maggior parte dei controlli WPF esistenti. Quando si crea un controllo che eredita dalla Control classe , è possibile definirne l'aspetto usando i modelli. In tal modo, la logica operativa viene separata dalla rappresentazione visiva. È anche possibile garantire il disaccoppiamento dell'interfaccia utente e della logica usando comandi e associazioni anziché eventi ed evitando di fare riferimento agli elementi in quanto ControlTemplate possibile. Se l'interfaccia utente e la logica del controllo sono correttamente disaccoppiate, un utente del controllo può ridefinire il controllo per personalizzarne l'aspetto ControlTemplate . Anche se la creazione di un oggetto personalizzato Control non è semplice come la creazione di un oggetto UserControl, un oggetto personalizzato Control offre la massima flessibilità.

Vantaggi della derivazione da Control

Prendere in considerazione la derivazione da Control anziché usare la UserControl classe se si applica una delle seguenti condizioni:

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

  • Si vuole che il controllo supporti temi diversi.

Derivazione da FrameworkElement

Controlli che derivano da UserControl o Control si basano sulla composizione di elementi esistenti. Per molti scenari, si tratta di una soluzione accettabile, perché qualsiasi oggetto che eredita da FrameworkElement può trovarsi in un oggetto ControlTemplate. A volte, tuttavia, le funzionalità di 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 la compilazione FrameworkElementdi componenti basati su : rendering diretto e composizione di elementi personalizzati. Il rendering diretto comporta l'override del OnRender metodo di e la fornitura DrawingContext di FrameworkElement operazioni che definiscono in modo esplicito gli oggetti visivi del componente. Questo è il metodo usato da Image e Border. La composizione di elementi personalizzati prevede l'uso di oggetti di tipo Visual per comporre l'aspetto del componente. Per un esempio, vedere Uso degli oggetti DrawingVisual. Track è un esempio di un controllo in WPF che usa la composizione di elementi personalizzati. È anche possibile combinare il rendering diretto e la composizione personalizzata di elementi nello stesso controllo.

Vantaggi della derivazione da FrameworkElement

Prendere in considerazione la derivazione da FrameworkElement se si applica una delle seguenti condizioni:

  • Si vuole controllare l'aspetto del controllo in modo più preciso rispetto a quanto possibile con la composizione semplice di elementi.

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

  • Si vogliono comporre elementi esistenti in modi nuovi che vanno oltre ciò che è possibile con UserControl e Control.

Nozioni di base sulla creazione di controlli

Come illustrato in precedenza, una delle funzionalità più potenti di WPF è la possibilità di andare oltre l'impostazione delle proprietà di base di un controllo per modificarne l'aspetto e il comportamento, ma non è ancora necessario creare un controllo personalizzato. Le funzionalità di applicazione di stili, data binding e trigger sono rese possibili dal sistema di proprietà WPF e dal sistema di eventi WPF. Le sezioni seguenti descrivono alcune procedure da seguire, indipendentemente dal modello usato per creare il controllo personalizzato, in modo che gli utenti del controllo personalizzato possano usare queste funzionalità esattamente come per un controllo incluso in WPF.

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

  • Usare una risorsa dinamica come valore della proprietà.

  • Animare la proprietà.

Se si vuole che una proprietà del controllo supporti queste funzionalità, è necessario implementarla come proprietà di dipendenza. L'esempio seguente definisce una proprietà di dipendenza denominata Value mediante le operazioni seguenti:

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

  • Registrare il nome della proprietà con il sistema di proprietà chiamando DependencyProperty.Registerper specificare quanto segue:

    • Nome della proprietà.

    • Tipo della proprietà.

    • Tipo proprietario della proprietà.

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

  • Definire una proprietà wrapper CLR denominata Value, ovvero lo stesso nome usato per registrare la proprietà di dipendenza, implementando le funzioni di accesso e set della get proprietà . Si noti che le get funzioni di accesso e set chiamano GetValue solo e SetValue rispettivamente. È consigliabile che le funzioni di accesso delle proprietà di dipendenza non contengano logica aggiuntiva perché i client e WPF possono ignorare le funzioni di accesso e chiamare GetValue e SetValue direttamente. 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, usare i ValidateValueCallbackdelegati , CoerceValueCallbacke PropertyChangedCallback per rispondere o controllare il valore quando cambia. Per altre informazioni su questi callback, vedere Callback e convalida delle proprietà di dipendenza.

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

  • Definire un metodo per , PropertyChangedCallbackdenominato OnValueChanged. OnValueChanged crea un RoutedPropertyChangedEventArgs<T> oggetto e prepara per generare l'evento ValueChanged indirizzato. Gli eventi indirizzati sono illustrati nella sezione successiva.

/// <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);
}
''' <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

Per altre informazioni, vedere Proprietà Dependency personalizzate.

Usare gli eventi indirizzati

Proprio come le proprietà di dipendenza estendono la nozione di proprietà CLR con funzionalità aggiuntive, gli eventi indirizzati estendono la nozione di eventi CLR standard. Quando si crea un nuovo controllo WPF, è anche consigliabile implementare l'evento come evento indirizzato perché un evento indirizzato supporta il comportamento seguente:

  • Gli eventi possono essere gestiti in un elemento padre di più controlli. Nel caso di un evento di bubbling, un singolo elemento padre nell'albero degli elementi può sottoscrivere l'evento. Gli autori dell'applicazione possono quindi usare un solo gestore per rispondere all'evento di più controlli. Ad esempio, se il controllo fa parte di ogni elemento di un ListBox oggetto (perché è incluso in un DataTemplate), lo sviluppatore dell'applicazione può 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.

  • Gli eventi indirizzati possono essere usati in un EventSetteroggetto , che consente agli sviluppatori di applicazioni di specificare il gestore di un evento all'interno di uno stile.

  • Gli eventi indirizzati possono essere usati in un EventTriggeroggetto , utile per animare le proprietà tramite XAML. Per altre informazioni, vedere Panoramica dell'animazione.

L'esempio seguente definisce un evento indirizzato mediante le operazioni seguenti:

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

  • Registrare l'evento indirizzato chiamando il EventManager.RegisterRoutedEvent metodo . Nell'esempio vengono specificate le informazioni seguenti quando chiama RegisterRoutedEvent:

    • Il nome dell'evento è ValueChanged.

    • La strategia di routing è Bubble, il che significa che un gestore eventi sull'origine (l'oggetto che genera l'evento) viene chiamato per primo e quindi i gestori eventi negli elementi padre dell'origine vengono chiamati in successione, a partire dal gestore eventi sull'elemento padre più vicino.

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

    • Il tipo proprietario dell'evento è NumericUpDown.

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

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

/// <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);
}
''' <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

Per altre informazioni, vedere Cenni preliminari sugli eventi indirizzati e Creare un evento indirizzato personalizzato.

Usare il binding

Per separare l'interfaccia utente del controllo dalla relativa logica, è possibile usare il data binding. Ciò è particolarmente importante se si definisce l'aspetto del controllo usando un oggetto ControlTemplate. Se si usa il data binding, potrebbe non essere più necessario fare riferimento a parti specifiche dell'interfaccia utente dal codice. È consigliabile evitare di fare riferimento agli elementi presenti in ControlTemplate perché quando il codice fa riferimento a elementi presenti in ControlTemplate e viene ControlTemplate modificato, l'elemento a cui si fa riferimento deve essere incluso nel nuovo ControlTemplate.

Nell'esempio seguente viene aggiornato l'oggetto TextBlock del NumericUpDown controllo, assegnando un nome e facendo riferimento alla casella di testo in base al nome nel codice.

<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 void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}
Private Sub UpdateTextBlock()
    valueText.Text = Value.ToString()
End Sub

L'esempio seguente usa il binding 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 altre informazioni sul data binding, vedere Cenni preliminari sull'associazione dati.

Progettare le finestre di progettazione

Per ricevere il supporto per i controlli WPF personalizzati in Wpf Designer per Visual Studio (ad esempio, la modifica delle proprietà con il Finestra Proprietà), seguire queste linee guida. Per altre informazioni sullo sviluppo per la finestra di progettazione WPF, vedere Progettare XAML in Visual Studio.

Proprietà di dipendenza

Assicurarsi di implementare CLR get e set le funzioni di accesso come descritto in precedenza, in "Use Dependency Properties". I progettisti possono usare il wrapper per rilevare la presenza di una proprietà di dipendenza, ma, come WPF e i client del controllo, non sono necessari per chiamare le funzioni di accesso durante il recupero o l'impostazione della proprietà.

Proprietà associate

Per implementare le proprietà associate nei controlli personalizzati, attenersi alle linee guida seguenti:

  • Disporre di un public DependencyProperty readonly static oggetto del formato PropertyNameProperty che stava creando utilizzando il RegisterAttached metodo . Il nome della proprietà passato a deve corrispondere a RegisterAttached PropertyName.

  • Implementare una coppia di public static metodi CLR denominati SetPropertyName e GetPropertyName. 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.

  • SetPropertyName e GetPropertyName devono essere indirizzati direttamente ai GetValue metodi e SetValue nell'oggetto dipendenza di destinazione, rispettivamente. Le finestre di progettazione possono accedere alla proprietà associata eseguendo una chiamata tramite il wrapper del metodo oppure una chiamata diretta all'oggetto dipendenza di destinazione.

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

Definire e usare risorse condivise

È possibile includere il controllo nello stesso assembly dell'applicazione oppure inserirlo in un pacchetto in un assembly separato che può essere usato in più applicazioni. Nella maggior parte dei casi, le informazioni di questo argomento si applicano indipendentemente dal metodo usato. È importante tuttavia notare una differenza. Quando si inserisce un controllo nello stesso assembly di un'applicazione, è possibile aggiungere risorse globali al file App.xaml. Tuttavia, un assembly che contiene solo i controlli non dispone di un Application oggetto associato, quindi un file App.xaml non è disponibile.

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 di 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. Può esserci anche un file denominato generic.xaml. Quando il sistema cerca una risorsa a livello di tema, la ricerca viene effettuata prima nel file specifico del tema e poi nel file generic.xaml.

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

Definizione delle risorse a livello di elemento

È possibile definire le risorse condivise a livello di elemento creando un dizionario risorse personalizzato e unendolo con il dizionario risorse del controllo. Quando si usa questo metodo, è possibile assegnare un nome qualsiasi al file di risorse, che può trovarsi nella stessa cartella dei controlli. Le risorse a livello di elemento possono anche usare semplici stringhe come chiavi. L'esempio seguente crea un LinearGradientBrush file di risorse denominato Dictionary1.xaml.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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>

Una volta definito il dizionario, è necessario unirlo al dizionario risorse del controllo. Puoi farlo usando XAML o il codice.

L'esempio seguente unisce un dizionario risorse usando XAML.

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

Lo svantaggio di questo approccio è che viene creato un ResourceDictionary oggetto ogni volta che vi si fa riferimento. Ad esempio, se hai 10 controlli personalizzati nella libreria e unisci i dizionari risorse condivise per ogni controllo usando XAML, crei 10 oggetti identici ResourceDictionary . È possibile evitare questo problema creando una classe statica che unisce le risorse nel codice e restituisce l'oggetto risultante ResourceDictionary.

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

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

L'esempio seguente unisce la risorsa condivisa con le risorse di un controllo personalizzato nel costruttore del controllo prima di chiamare InitializeComponent. SharedDictionaryManager.SharedDictionary Poiché è una proprietà statica, viene ResourceDictionary creato una sola volta. Poiché il dizionario risorse è stato unito prima InitializeComponent della chiamata, le risorse sono disponibili per il controllo nel relativo file XAML.

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

Definizione delle risorse a livello di tema

WPF consente di creare risorse per diversi temi di Windows. L'autore di un controllo può definire una risorsa per un tema specifico in modo da modificare l'aspetto del controllo a seconda del tema in uso. Ad esempio, l'aspetto di un Button oggetto nel tema classico di Windows (il tema predefinito per Windows 2000) differisce da un Button nel tema Luna di Windows (il tema predefinito per Windows XP) perché Button usa un tema diverso ControlTemplate per ogni tema.

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

Nome 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 la cerca in Classic.xaml. Se la risorsa non è definita nel file che corrisponde al tema corrente o in Classic.xaml, il controllo usa la risorsa generica, che si trova in un file del dizionario risorse denominato generic.xaml. Il file generic.xaml si trova nella stessa cartella dei file del dizionario risorse specifico del tema. Anche se generic.xaml non corrisponde a un tema di Windows specifico, è comunque un dizionario a livello di tema.

L'esempio di controllo personalizzato C# o Visual Basic NumericUpDown con il tema e l'automazione interfaccia utente contiene due dizionari di risorse per il NumericUpDown controllo: uno è in generic.xaml e l'altro è in Luna.NormalColor.xaml.

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

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
    DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
Definizione delle chiavi e relativo riferimento per le risorse del tema

Quando si definisce una risorsa a livello di elemento, è possibile assegnare una stringa come chiave e accedere alla risorsa tramite la stringa. Quando si definisce una risorsa a livello di tema, è necessario usare come ComponentResourceKey chiave . L'esempio seguente definisce una risorsa in 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 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>
Indicazione del percorso delle risorse del tema

Per trovare le risorse per un controllo, l'applicazione host deve sapere che l'assembly contiene risorse specifiche del controllo. A tale scopo, è possibile aggiungere l'oggetto ThemeInfoAttribute all'assembly che contiene il controllo . Ha ThemeInfoAttribute una GenericDictionaryLocation proprietà che specifica la posizione delle risorse generiche e una ThemeDictionaryLocation proprietà che specifica la posizione delle risorse specifiche del tema.

Nell'esempio seguente le GenericDictionaryLocation proprietà e ThemeDictionaryLocation vengono impostate su SourceAssemblyper specificare che le risorse generiche e specifiche del tema si trovano nello stesso assembly del controllo .

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

Vedi anche