Proprietà Dependency personalizzate

In questo argomento vengono descritte le ragioni per cui sviluppatori di applicazioni Windows Presentation Foundation (WPF) e autori di componenti potrebbero decidere di creare una proprietà di dipendenza personalizzata e vengono illustrati i passaggi dell'implementazione nonché alcune opzioni di implementazione in grado di migliorare le prestazioni, l'usabilità o la versatilità della proprietà.

Nel presente argomento sono contenute le seguenti sezioni.

  • Prerequisiti
  • Definizione della proprietà di dipendenza
  • Esempi di proprietà di dipendenza
  • Quando implementare una proprietà di dipendenza
  • Elenco di controllo per la definizione di una proprietà di dipendenza
  • Proprietà di dipendenza di sola lettura
  • Proprietà di dipendenza di tipo insieme
  • Considerazioni sulla sicurezza della proprietà di dipendenza
  • Proprietà di dipendenza e costruttori di classi
  • Argomenti correlati

Prerequisiti

In questo argomento si presuppone la conoscenza delle proprietà di dipendenza dal punto di vista di un consumer di proprietà di dipendenza esistenti nelle classi WPF, nonché la lettura dell'argomento Cenni preliminari sulle proprietà di dipendenza. Per seguire gli esempi di questo argomento, è necessaria inoltre la comprensione di Extensible Application Markup Language (XAML) oltre che la conoscenza della modalità di scrittura delle applicazioni WPF.

Definizione della proprietà di dipendenza

È possibile consentire a una proprietà che in caso contrario sarebbe una proprietà common language runtime (CLR) il supporto dell'aggiunta di stili, dell'associazione dati, dell'ereditarietà, delle animazioni e di valori predefiniti mediante l'implementazione della proprietà stessa come proprietà di dipendenza. Le proprietà di dipendenza sono proprietà registrate con il sistema di proprietà WPF chiamando il metodo Register (o RegisterReadOnly) e che sono supportate da un campo di identificazione DependencyProperty. Le proprietà di dipendenza possono essere utilizzate solo dai tipi DependencyObject; tuttavia, dal momento che DependencyObject è piuttosto elevato nella gerarchia di classi WPF, la maggior parte delle classi disponibili in WPF è in grado di supportare questo tipo di proprietà. Per ulteriori informazioni sulle proprietà di dipendenza e per la terminologia e le convenzioni utilizzate per descriverle in questo SDK, vedere Cenni preliminari sulle proprietà di dipendenza.

Esempi di proprietà di dipendenza

Tra gli esempi di proprietà di dipendenza implementati nelle classi WPF sono incluse le proprietà Background, Width e Text. Ogni proprietà di dipendenza esposta da una classe dispone di un campo statico pubblico corrispondente del tipo DependencyProperty esposto in quella stessa classe. Si tratta dell'identificatore per la proprietà di dipendenza. L'identificatore viene denominato mediante la seguente convenzione: nome della proprietà di dipendenza seguito dalla stringa Property. Ad esempio, il campo di identificazione DependencyProperty corrispondente per la proprietà Background è BackgroundProperty. L'identificatore archivia le informazioni sulla proprietà di dipendenza al momento della registrazione della proprietà e viene utilizzato in seguito per altre operazioni che coinvolgono la proprietà di dipendenza, ad esempio la chiamata di SetValue.

Come indicato in Cenni preliminari sulle proprietà di dipendenza, tutte le proprietà di dipendenza di WPF (eccetto la maggior parte delle proprietà associate) sono inoltre proprietà CLR a causa dell'implementazione del "wrapper". Pertanto, mediante il codice è possibile ottenere o impostare proprietà di dipendenza chiamando funzioni di accesso CLR che definiscono i wrapper in modo identico alle altre proprietà CLR. I consumer di proprietà di dipendenza stabilite in genere non utilizzano i metodi DependencyObjectGetValue e SetValue, che sono il punto di connessione al sistema di proprietà sottostante. Piuttosto, l'implementazione esistente delle proprietà CLR avrà già chiamato GetValue e SetValue all'interno delle implementazioni dei wrapper get e set della proprietà, utilizzando in modo appropriato il campo dell'identificatore. Se l'implementazione di una proprietà di dipendenza personalizzata viene eseguita in modo autonomo, il wrapper verrà definito in modo simile.

Quando implementare una proprietà di dipendenza

Quando si implementa una proprietà in una classe, se la classe deriva da DependencyObject, è possibile supportare la proprietà con un identificatore DependencyProperty, rendendola pertanto una proprietà di dipendenza. Trasformare una proprietà in una proprietà di dipendenza non sempre è necessario o appropriato e dipende dalle esigenze dello scenario. Talvolta è opportuno utilizzare la tecnica normale che consiste nel supportare la proprietà con un campo privato. Tuttavia, se si desidera che la proprietà supporti una o più delle seguenti funzionalità WPF, è necessario implementarla come proprietà di dipendenza:

  • Si desidera che la proprietà possa essere impostata in uno stile. Per ulteriori informazioni, vedere Applicazione di stili e modelli.

  • Si desidera che la proprietà supporti l'associazione dati. Per le ulteriori informazioni sulle proprietà di dipendenza con associazione dati, vedere Procedura: eseguire l'associazione delle proprietà di due controlli.

  • Si desidera che la proprietà possa essere impostata con un riferimento di risorsa dinamica. Per ulteriori informazioni, vedere Cenni preliminari sulle risorse.

  • Si desidera ereditare un valore di proprietà automaticamente da un elemento padre nella struttura ad albero dell'elemento. In questo caso, effettuare la registrazione con il metodo RegisterAttached, persino nel caso in cui si crei anche un wrapper della proprietà per l'accesso CLR. Per ulteriori informazioni, vedere Ereditarietà del valore della proprietà.

  • Si desidera che la proprietà sia animata. Per ulteriori informazioni, vedere Cenni preliminari sull'animazione.

  • Si desidera che il sistema di proprietà segnali quando il valore precedente della proprietà è stato modificato da azioni intraprese dal sistema di proprietà, dall'ambiente o dall'utente oppure tramite la lettura e l'utilizzo degli stili. Se si utilizzano i metadati della proprietà, la proprietà può specificare un metodo di callback che verrà richiamato ogni volta il sistema di proprietà determina che il valore della proprietà è stato modificato definitivamente. Un concetto correlato è la coercizione del valore di proprietà. Per ulteriori informazioni, vedere Callback e convalida delle proprietà di dipendenza.

  • Si desidera utilizzare convenzioni di metadati definite che sono utilizzate anche dai processi WPF, ad esempio la segnalazione dell'eventualità che la modifica di un valore di proprietà richieda che il sistema di layout ricomponga gli elementi visivi di un elemento. Oppure si desidera utilizzare override di metadati in modo che le classi derivate possano modificare caratteristiche basate sui metadati quali il valore predefinito.

  • Si desidera che le proprietà di un controllo personalizzato ricevano supporto Visual Studio 2008 WPF Designer, ad esempio la modifica della finestra Proprietà. Per ulteriori informazioni, vedere Cenni preliminari sulla modifica di controlli.

Quando si esaminano questi scenari, è necessario considerare inoltre se è possibile realizzare lo scenario eseguendo l'override dei metadati di una proprietà di dipendenza esistente, piuttosto che implementando una proprietà completamente nuova. La validità dell'utilizzo di un override di metadati dipende dallo scenario e dalla vicinanza di quello scenario, in termini di somiglianza, all'implementazione nelle proprietà di dipendenza WPF esistenti e nelle classi. Per ulteriori informazioni sull'override dei metadati nelle proprietà esistenti, vedere Metadati della proprietà di dipendenza.

Elenco di controllo per la definizione di una proprietà di dipendenza

La definizione di una proprietà di dipendenza include quattro concetti distinti. Tali concetti non rappresentano necessariamente passaggi rigidi di una procedura, in quanto alcuni di questi finiscono per essere combinati come singole righe di codice nell'implementazione:

  • (Facoltativo) Creare metadati di proprietà per la proprietà di dipendenza.

  • Registrare il nome della proprietà con il sistema di proprietà, specificando un tipo di proprietario e il tipo del valore di proprietà. Se utilizzati, specificare anche i metadati della proprietà.

  • Definire un identificatore DependencyProperty come campo static public readonly per il tipo di proprietario.

  • Definire una proprietà "wrapper" CLR il cui nome corrisponda al nome della proprietà di dipendenza. Implementare le funzioni di accesso get e set della proprietà "wrapper" CLR da connettere alla proprietà di dipendenza che la supporta.

Registrazione della proprietà con il sistema di proprietà.

Affinché la proprietà sia una proprietà di dipendenza, è necessario registrarla in una tabella gestita dal sistema di proprietà e fornirle un identificatore univoco utilizzato come qualificatore per le successive operazioni del sistema di proprietà. Tali operazioni potrebbero essere operazioni interne oppure le APIs del sistema di proprietà di chiamata del codice. Per registrare la proprietà, chiamare il metodo Register all'interno del corpo della classe (all'interno della classe, ma all'esterno di qualsiasi definizione del membro). Il campo dell'identificatore viene inoltre fornito dalla chiamata al metodo Register, come valore restituito. La ragione per la quale la chiamata a Register viene effettuata all'esterno delle definizioni degli altri membri è che questo valore restituito viene utilizzato per assegnare e creare un campo static public readonly di tipo DependencyProperty come parte della classe. Questo campo diventa l'identificatore per la proprietà di dipendenza.

Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender, 
      new PropertyChangedCallback(OnUriChanged)
  )
);

Convenzioni di denominazione delle proprietà di dipendenza

Esistono convenzioni di denominazione definite relative alle proprietà di dipendenza che è necessario seguire in tutte le circostanze tranne in casi eccezionali.

La proprietà di dipendenza stessa avrà un nome di base, "AquariumGraphic" come in questo esempio, fornito come primo parametro di Register. Tale nome deve essere univoco all'interno di ogni tipo di registrazione. Le proprietà di dipendenza ereditate tramite tipi di base sono considerate già parte del tipo di registrazione; i nomi delle proprietà ereditate non possono essere registrati nuovamente. Tuttavia, esiste una tecnica per aggiungere una classe come proprietario di una proprietà di dipendenza persino quando quella proprietà di dipendenza non è ereditata; per informazioni dettagliate, vedere Metadati della proprietà di dipendenza.

Quando si crea il campo dell'identificatore, denominare questo campo con il nome della proprietà registrata, più il suffisso Property. Questo campo rappresenta l'identificatore per la proprietà di dipendenza e sarà utilizzato in seguito come un input per le chiamate a SetValue e a GetValue nei wrapper, da parte di qualsiasi altro accesso di codice alla proprietà, dal codice personale, da qualsiasi accesso di codice esterno consentito, dal sistema di proprietà e potenzialmente dai processori XAML.

NotaNota

La definizione della proprietà di dipendenza nel corpo della classe costituisce l'implementazione tipica, tuttavia è anche possibile definire una proprietà di dipendenza nel costruttore statico della classe.Questo approccio potrebbe essere utile nel caso in cui siano necessarie più righe di codice per inizializzare la proprietà di dipendenza.

Implementazione del "wrapper"

L'implementazione del wrapper deve chiamare GetValue nell'implementazione get e SetValue nell'implementazione set (la chiamata di registrazione originale e il campo sono mostrati anche in questo caso per chiarezza).

In tutte le circostanze tranne in casi eccezionali, le implementazioni del wrapper devono eseguire solo le azioni GetValue e SetValue, rispettivamente. La ragione è discussa nell'argomento Caricamento XAML e proprietà di dipendenza.

Tutte le proprietà di dipendenza pubbliche esistenti fornite nelle classi WPF utilizzano questo semplice modello di implementazione del wrapper; la maggior parte della complessità della modalità di funzionamento delle proprietà di dipendenza è dovuta a un comportamento intrinseco del sistema di proprietà oppure è implementata tramite altri concetti quali la coercizione o i callback di modifica della proprietà mediante i metadati della proprietà.


Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))
Public Property AquariumGraphic() As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set(ByVal value As Uri)
        SetValue(AquariumGraphicProperty, value)
    End Set
End Property

public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender, 
      new PropertyChangedCallback(OnUriChanged)
  )
);
public Uri AquariumGraphic
{
  get { return (Uri)GetValue(AquariumGraphicProperty); }
  set { SetValue(AquariumGraphicProperty, value); }
}

Anche in questo caso, per convenzione, il nome della proprietà del wrapper deve corrispondere esattamente al nome scelto e fornito come primo parametro della chiamata Register che ha registrato la proprietà. Se la proprietà non segue la convenzione, non necessariamente vengono disabilitati tutti i possibili utilizzi, ma si incontreranno molti problemi rilevanti:

  • Determinati aspetti di stili e modelli non funzioneranno.

  • La maggior parte degli strumenti e delle finestre di progettazione devono basarsi sulle convenzioni di denominazione per serializzare correttamente XAML o per fornire assistenza relativamente all'ambiente della finestra di progettazione a livello di singola proprietà.

  • L'implementazione corrente del caricatore XAML WPF ignora completamente i wrapper e si basa sulla convenzione di denominazione al momento dell'elaborazione dei valori dell'attributo. Per ulteriori informazioni, vedere Caricamento XAML e proprietà di dipendenza.

Metadati della proprietà per una nuova proprietà di dipendenza.

Quando si registra una proprietà di dipendenza, la registrazione tramite il sistema di proprietà crea un oggetto dei metadati che archivia le caratteristiche della proprietà. Molte di queste caratteristiche dispongono di impostazioni predefinite che vengono impostate se la proprietà viene registrata con le semplici firme di Register. Le altre firme di Register consentono di specificare i metadati desiderati quando si registra la proprietà. I metadati più comuni per le proprietà di dipendenza consistono nel fornire a tali proprietà un valore predefinito che viene applicato alle nuove istanze che utilizzano la proprietà.

Se si crea una proprietà di dipendenza che esiste in una classe derivata di FrameworkElement, è possibile utilizzare la classe dei metadati FrameworkPropertyMetadata più specializzata piuttosto che la classe PropertyMetadata di base. Il costruttore per la classe FrameworkPropertyMetadata dispone di molte firme nelle quali è possibile specificare una combinazione delle varie caratteristiche dei metadati. Se si desidera specificare solo il valore predefinito, utilizzare la firma che accetta un singolo parametro di tipo Object. Passare quel parametro di oggetto come valore predefinito specifico del tipo per la proprietà (il valore predefinito fornito deve essere il tipo fornito come parametro propertyType nella chiamata a Register).

Per FrameworkPropertyMetadata, è inoltre possibile specificare i flag di opzione dei metadati per la proprietà. Tali flag vengono convertiti in proprietà discrete nei metadati della proprietà dopo la registrazione e utilizzati per comunicare determinate istruzioni condizionali agli altri processi quali il motore di layout.

Impostazione di flag di metadati appropriati

  • Se la proprietà (o le modifiche nel relativo valore) influisce sull'user interface (UI) e in particolare sul modo in cui il sistema di layout deve ridimensionare o eseguire il rendering dell'elemento in una pagina, impostare uno o più flag tra quelli indicati di seguito: AffectsMeasure, AffectsArrange, AffectsRender.

    • AffectsMeasure indica che una modifica a questa proprietà richiede una modifica al rendering dell'UI nella quale l'oggetto contenitore potrebbe richiedere uno spazio maggiore o minore all'interno dell'elemento padre. Ad esempio, è necessario impostare questo flag in una proprietà "Width".

    • AffectsArrange indica che una modifica a questa proprietà richiede una modifica al rendering dell'UI che in genere non rende necessaria una modifica nello spazio dedicato, ma che tuttavia indica che il posizionamento all'interno dello spazio è stato modificato. Ad esempio, è necessario impostare questo flag in una proprietà "Alignment".

    • AffectsRender indica che si è verificata un'altra modifica che non influirà sul layout e sulla misurazione ma che richiede un altro rendering. Un esempio potrebbe essere una proprietà che modifica un colore di un elemento esistente, ad esempio "Background".

    • Questi flag spesso sono utilizzati come protocollo nei metadati per le implementazioni di override del sistema di proprietà o per i callback del layout. È possibile, ad esempio, che si disponga di un callback di OnPropertyChanged che esegue la chiamata a InvalidateArrange se una qualsiasi proprietà dell'istanza segnala una modifica del valore e presenta un valore true per AffectsArrange nei relativi metadati.

  • Alcune proprietà possono influire sulle caratteristiche di rendering dell'elemento padre contenitore, in modo maggiore rispetto alle modifiche nelle dimensioni necessarie indicate in precedenza. Un esempio è la proprietà MinOrphanLines utilizzata nel modello del documento dinamico, nel quale le modifiche a quella proprietà possono modificare il rendering globale del documento dinamico che contiene il paragrafo. Utilizzare AffectsParentArrange o AffectsParentMeasure per identificare casi simili nelle proprietà utilizzate.

  • Per impostazione predefinita, le proprietà di dipendenza supportano l'associazione dati. È possibile disabilitare intenzionalmente l'associazione dati per i casi in cui non esiste uno scenario realistico per l'associazione dati o nei quali le prestazioni dell'associazione dati per un oggetto di grandi dimensioni viene considerata un problema.

  • Per impostazione predefinita, l'associazione dati Mode per le proprietà di dipendenza utilizza OneWay come valore predefinito. È sempre possibile modificare l'associazione in modo che diventi TwoWay per istanza di associazione; per i dettagli, vedere Procedura: specificare la direzione di associazione. Tuttavia, in qualità di autore della proprietà di dipendenza, è possibile scegliere di fare in modo che la proprietà utilizzi la modalità di associazione TwoWay per impostazione predefinita. Un esempio di una proprietà di dipendenza esistente è MenuItem.IsSubmenuOpen; lo scenario per questa proprietà consiste nel fatto che la logica di impostazione IsSubmenuOpen e la composizione di MenuItem interagiscono con lo stile del tema predefinito. La logica della proprietà IsSubmenuOpen utilizza l'associazione dati a livello nativo per gestire lo stato della proprietà in conformità alle altre proprietà di stato e alle chiamate ai metodi. Un altro esempio di proprietà che esegue l'associazione di TwoWay per impostazione predefinita è TextBox.Text.

  • È anche possibile abilitare l'ereditarietà della proprietà in una proprietà di dipendenza personalizzata impostando il flag Inherits. L'ereditarietà della proprietà è utile per uno scenario nel quale elementi padre ed elementi figlio dispongono di una proprietà in comune ed è inoltre utile che quel particolare valore degli elementi figlio sia impostato sullo stesso valore impostato dall'elemento padre. Una proprietà ereditabile di esempio è DataContext, utilizzata per fare in modo che le operazioni di associazione attivino l'importante scenario Master-Details per la presentazione dei dati. Se si rende ereditabile la proprietà DataContext, tutti gli elementi figlio ereditano anche quel contesto dati. A causa dell'ereditarietà del valore della proprietà, è possibile specificare un contesto dati alla radice della pagina o dell'applicazione e non è necessario specificarlo di nuovo per le associazioni in tutti i possibili elementi figlio. DataContext rappresenta un ottimo esempio di come l'ereditarietà ignori il valore predefinito ma è sempre possibile impostarlo localmente su qualsiasi elemento figlio; per informazioni dettagliate, vedere Procedura: utilizzare il modello Master-Details con dati gerarchici. L'ereditarietà del valore della proprietà può incidere negativamente sulle prestazioni e pertanto deve essere utilizzata sporadicamente; per informazioni dettagliate, vedere Ereditarietà del valore della proprietà.

  • Impostare il flag Journal per indicare se la proprietà di dipendenza deve essere rilevata o utilizzata dai servizi di journaling di esplorazione. Un esempio è rappresentato dalla proprietà SelectedIndex; qualsiasi elemento selezionato in un controllo di selezione deve essere conservato quando si esplora la cronologia del journaling.

Proprietà di dipendenza di sola lettura

È possibile definire una proprietà di dipendenza di sola lettura. Tuttavia, gli scenari in base ai quali è possibile definire la proprietà come proprietà di sola lettura sono piuttosto diversi, allo stesso modo della procedura per registrare queste proprietà con il sistema di proprietà e di quella per esporre l'identificatore. Per ulteriori informazioni, vedere Proprietà di dipendenza di sola lettura.

Proprietà di dipendenza di tipo insieme

Le proprietà di dipendenza di tipo di insieme presentano alcuni problemi di implementazione aggiuntivi che è opportuno considerare. Per informazioni dettagliate, vedere Proprietà di dipendenza di tipo insieme.

Considerazioni sulla sicurezza della proprietà di dipendenza

Le proprietà di dipendenza devono essere dichiarate come proprietà pubbliche. I campi dell'identificatore delle proprietà di dipendenza devono essere dichiarati come campi statici pubblici. Anche se si tenta di dichiarare altri livelli di accesso (ad esempio protetto), è sempre possibile accedere a una proprietà di dipendenza tramite l'identificatore in combinazione con le APIs del sistema di proprietà . In teoria, è possibile persino accedere a un campo dell'identificatore protetto grazie alla segnalazione dei metadati o alle APIs per la determinazione del valore che fanno parte del sistema di proprietà, ad esempio LocalValueEnumerator. Per ulteriori informazioni, vedere Sicurezza della proprietà di dipendenza.

Proprietà di dipendenza e costruttori di classi

Esiste un principio generale nella programmazione del codice gestito (spesso applicato mediante strumenti di analisi del codice quale FxCop) in base al quale i costruttori di classi non devono chiamare metodi virtuali. Il motivo sta nel fatto che i costruttori possono essere chiamati come inizializzazione di base di un costruttore di classe derivato, pertanto l'utilizzo del metodo virtuale tramite il costruttore potrebbe avvenire a uno stato incompleto dell'inizializzazione dell'istanza di oggetto in corso di creazione. Quando si deriva da una qualsiasi classe che deriva già da DependencyObject, è necessario essere consapevoli che il sistema di proprietà stesso chiama ed espone internamente metodi virtuali. Tali metodi virtuali sono parte dei servizi del sistema di proprietà WPF. L'esecuzione dell'override dei metodi consente alle classi derivate di partecipare alla determinazione del valore. Per evitare eventuali problemi con l'inizializzazione del runtime non è necessario impostare valori della proprietà di dipendenza all'interno dei costruttori di classi, a meno che non si esegua un modello del costruttore molto specifico. Per informazioni dettagliate, vedere Modelli di costruttore sicuri per DependencyObject.

Vedere anche

Concetti

Cenni preliminari sulle proprietà di dipendenza

Metadati della proprietà di dipendenza

Cenni preliminari sulla modifica di controlli

Proprietà di dipendenza di tipo insieme

Sicurezza della proprietà di dipendenza

Caricamento XAML e proprietà di dipendenza

Modelli di costruttore sicuri per DependencyObject