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 DataTemplateControlTemplate 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 FrameworkElement di DrawingContext 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
comepublic
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 eget
dellaset
proprietà . Si noti che leget
funzioni di accesso eset
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 accessoset
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 cheValue
sia maggiore o uguale aMinValue
e minore o uguale aMaxValue
.Definire un metodo per , PropertyChangedCallbackdenominato
OnValueChanged
.OnValueChanged
crea un RoutedPropertyChangedEventArgs<T> oggetto e prepara per generare l'eventoValueChanged
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
comepublic
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 dellaadd
funzione di accesso e RemoveHandler nella dichiarazione dellaremove
funzione di accesso per usare i servizi eventi WPF.Creazione di un metodo virtuale protetto denominato
OnValueChanged
che genera l'eventoValueChanged
.
/// <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
static
readonly
DependencyProperty 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 denominatiSet
PropertyName eGet
PropertyName. Entrambi i metodi devono accettare una classe derivata da DependencyProperty come primo argomento. Il metodoSet
NomeProprietà accetta anche un argomento il cui tipo corrisponde al tipo di dati registrato per la proprietà. Il metodoGet
NomeProprietà deve restituire un valore dello stesso tipo. In assenza del metodoSet
NomeProprietà, la proprietà viene contrassegnata come di sola lettura.Set
PropertyName eGet
PropertyName 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:
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.
Livello dell'applicazione.
Risorse definite dall'oggetto Application .
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
.NET Desktop feedback