Proprietà di dipendenza personalizzate

Questo argomento descrive i motivi per cui gli sviluppatori di applicazioni e gli autori di componenti di Windows Presentation Foundation (WPF) potrebbero voler creare proprietà di dipendenza personalizzate e descrive i passaggi di implementazione, nonché alcune opzioni di implementazione che possono migliorare le prestazioni, l'usabilità o la versatilità della proprietà.

Prerequisiti

In questo argomento si presuppone che le proprietà di dipendenza siano comprensibili dal punto di vista di un consumer di proprietà di dipendenza esistenti nelle classi WPF e che l'argomento Cenni preliminari sulle proprietà di dipendenza sia stato letto. Per seguire gli esempi illustrati in questo argomento, è anche necessario conoscere XAML e saper scrivere applicazioni WPF.

Che cos'è una proprietà di dipendenza?

È possibile abilitare ciò che altrimenti sarebbe una proprietà CLR (Common Language Runtime) per supportare stili, data binding, ereditarietà, animazioni e valori predefiniti implementandolo come proprietà di dipendenza. Le proprietà di dipendenza sono proprietà registrate nel sistema di proprietà WPF chiamando il Register metodo (o RegisterReadOnly) e supportate da un DependencyProperty campo identificatore. Le proprietà di dipendenza possono essere usate solo dai DependencyObject tipi, ma DependencyObject è piuttosto elevata nella gerarchia di classi WPF, quindi la maggior parte delle classi disponibili in WPF può supportare le proprietà di dipendenza. Per altre informazioni sulle proprietà di dipendenza e su alcuni termini e convenzioni usati per descriverli in questo SDK, vedere Cenni preliminari sulle proprietà di dipendenza.

Esempio di proprietà di dipendenza

Esempi di proprietà di dipendenza implementate nelle classi WPF includono la Background proprietà, la Width proprietà e la Text proprietà, tra le altre. Ogni proprietà di dipendenza esposta da una classe ha un campo statico pubblico corrispondente di tipo DependencyProperty esposto nella 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 identificatore corrispondente DependencyProperty per la Background proprietà è BackgroundProperty. L'identificatore archivia le informazioni sulla proprietà di dipendenza durante la registrazione e l'identificatore viene quindi usato in seguito per altre operazioni che coinvolgono la proprietà di dipendenza, ad esempio la chiamata SetValuea .

Come indicato in Cenni preliminari sulle proprietà di dipendenza, tutte le proprietà di dipendenza in WPF (ad eccezione della maggior parte delle proprietà associate) sono anche proprietà CLR a causa dell'implementazione "wrapper". Di conseguenza, dal codice è possibile ottenere o impostare le proprietà di dipendenza chiamando le funzioni di accesso CLR che definiscono i wrapper nello stesso modo in cui si userebbero altre proprietà CLR. Come consumer di proprietà di dipendenza stabilite, in genere non si usano i DependencyObject metodi GetValue e SetValue, che sono il punto di connessione al sistema di proprietà sottostante. Invece, l'implementazione esistente delle proprietà CLR avrà già chiamato GetValue e SetValue all'interno delle get implementazioni e set wrapper della proprietà, usando il campo identificatore in modo appropriato. 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, purché la classe derivi da DependencyObject, è possibile eseguire il backup della proprietà con un DependencyProperty identificatore e quindi impostarla come proprietà di dipendenza. Trasformare una proprietà in una proprietà di dipendenza non sempre è necessario o appropriato e dipende dalle esigenze dello scenario. Talvolta è opportuno usare la tecnica normale che consiste nel supportare la proprietà con un campo privato. È tuttavia necessario implementare la proprietà come proprietà di dipendenza ogni volta che si vuole che la proprietà supporti una o più delle funzionalità WPF seguenti:

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

  • Si vuole che la proprietà supporti il data binding. Per altre informazioni sulle proprietà di dipendenza di data binding, vedere Eseguire l'associazione delle proprietà di due controlli.

  • Si vuole che la proprietà possa essere impostata con un riferimento di risorsa dinamica. Per altre informazioni, vedere Risorse XAML.

  • Si vuole ereditare un valore di proprietà automaticamente da un elemento padre nell'albero degli elementi. In questo caso, eseguire la registrazione con il RegisterAttached metodo , anche se si crea anche un wrapper di proprietà per l'accesso a CLR. Per altre informazioni, vedere Ereditarietà del valore della proprietà.

  • Si vuole che la proprietà sia animata. Per altre informazioni, vedere Panoramica dell'animazione.

  • Si vuole 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'uso degli stili. Se si usano 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 altre informazioni, vedere Callback e convalida delle proprietà di dipendenza.

  • Si vogliono usare convenzioni di metadati stabilite usate anche dai processi WPF, ad esempio per segnalare se la modifica di un valore di proprietà deve richiedere al sistema di layout di ricomporre gli oggetti visivi per un elemento. Oppure si vogliono usare 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 il supporto di Progettazione WPF di Visual Studio, ad esempio la modifica della finestra Proprietà . Per altre informazioni, vedere Cenni preliminari sulla modifica di controlli.

Quando si esaminano questi scenari, è necessario considerare anche se è possibile realizzare lo scenario eseguendo l'override dei metadati di una proprietà di dipendenza esistente, piuttosto che implementando una proprietà completamente nuova. Il fatto che un override dei metadati sia pratico dipende dallo scenario e dal modo in cui lo scenario è simile all'implementazione nelle proprietà e nelle classi di dipendenza WPF esistenti. Per altre informazioni sull'override dei metadati nelle proprietà esistenti, vedere Metadati delle 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. Questi 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 usati, specificare anche i metadati della proprietà.

  • Definire un DependencyProperty identificatore come publicstaticreadonly campo nel tipo di proprietario.

  • Definire una proprietà CLR "wrapper" il cui nome corrisponde al nome della proprietà di dipendenza. Implementare le funzioni di accesso e set le funzioni di get accesso della proprietà CLR "wrapper" per connettersi alla proprietà di dipendenza che lo esegue.

Registrazione della proprietà nel sistema di proprietà

Affinché la proprietà sia una proprietà di dipendenza, è necessario registrarla in una tabella gestita dal sistema di proprietà e assegnarle un identificatore univoco usato come qualificatore per le successive operazioni del sistema di proprietà. Queste operazioni possono essere operazioni interne o API del sistema di proprietà chiamanti codice. Per registrare la proprietà, chiamare il Register metodo all'interno del corpo della classe (all'interno della classe, ma all'esterno di qualsiasi definizione di membro). Il campo dell'identificatore viene fornito anche dalla chiamata al Register metodo, come valore restituito. Il motivo per cui la Register chiamata viene eseguita all'esterno di altre definizioni di membri è che si usa questo valore restituito per assegnare e creare unreadonlypublicstaticcampo di tipo DependencyProperty come parte della classe. Questo campo diventa l'identificatore per la proprietà di dipendenza.

public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender,
      new PropertyChangedCallback(OnUriChanged)
  )
);
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf 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, che viene assegnato 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 delle 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 è l'identificatore per la proprietà di dipendenza e verrà usato in un secondo momento come input per le SetValue chiamate e GetValue verranno effettuate nei wrapper, da qualsiasi altro accesso di codice alla proprietà tramite codice personalizzato, da qualsiasi accesso al codice esterno consentito, dal sistema di proprietà e potenzialmente dai processori XAML.

Nota

La definizione della proprietà di dipendenza nel corpo della classe costituisce l'implementazione tipica, ma è 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 visualizzati anche qui per maggiore chiarezza).

In tutte le circostanze eccezionali, le implementazioni del wrapper devono eseguire solo le GetValue azioni 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 usano questo semplice modello di implementazione wrapper; la maggior parte della complessità del funzionamento delle proprietà di dipendenza è intrinsecamente un comportamento del sistema di proprietà o viene implementata tramite altri concetti, ad esempio la coercizione o il callback delle modifiche delle proprietà tramite metadati delle proprietà.


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

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

Anche in questo caso, per convenzione, il nome della proprietà wrapper deve essere uguale al nome scelto e dato come primo parametro della Register chiamata che ha registrato la proprietà. Se la proprietà non segue la convenzione, non necessariamente vengono disabilitati tutti i possibili usi, ma si riscontreranno vari problemi rilevanti:

  • Determinati aspetti di stili e modelli non funzioneranno.

  • La maggior parte degli strumenti e dei progettisti deve basarsi sulle convenzioni di denominazione per serializzare correttamente XAML o per fornire assistenza all'ambiente di progettazione a livello di proprietà.

  • L'implementazione corrente del caricatore XAML WPF ignora completamente i wrapper e si basa sulla convenzione di denominazione durante l'elaborazione dei valori degli attributi. Per altre 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 hanno valori predefiniti impostati se la proprietà è registrata con le firme semplici di Register. Altre firme di consentono di Register specificare i metadati desiderati durante la registrazione della 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 usano la proprietà.

Se si crea una proprietà di dipendenza presente in una classe derivata di FrameworkElement, è possibile usare la classe FrameworkPropertyMetadata di metadati più specializzata anziché la classe base PropertyMetadata . Il costruttore per la FrameworkPropertyMetadata classe include diverse firme in cui è possibile specificare varie caratteristiche dei metadati in combinazione. Se si vuole specificare solo il valore predefinito, usare la firma che accetta un singolo parametro di tipo Object. Passare il parametro dell'oggetto come valore predefinito specifico del tipo per la proprietà ( il valore predefinito specificato deve essere il tipo specificato come propertyType parametro nella Register chiamata).

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

Impostazione dei flag di metadati appropriati

  • Se la proprietà (o il relativo valore) influisce sull'interfaccia utente e in particolare influisce sul modo in cui il sistema di layout deve ridimensionare o eseguire il rendering dell'elemento in una pagina, impostare uno o più dei flag seguenti: AffectsMeasure, AffectsArrange, AffectsRender.

    • AffectsMeasure indica che una modifica a questa proprietà richiede una modifica al rendering dell'interfaccia utente in cui l'oggetto contenitore potrebbe richiedere più o meno spazio all'interno dell'elemento padre. Ad esempio, è necessario impostare questo flag per una proprietà "Width".

    • AffectsArrange indica che una modifica a questa proprietà richiede una modifica al rendering dell'interfaccia utente che in genere non richiede una modifica nello spazio dedicato, ma indica che il posizionamento all'interno dello spazio è cambiato. Ad esempio, è necessario impostare questo flag per una proprietà "Alignment".

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

    • Questi flag vengono spesso usati come protocollo nei metadati per le implementazioni di override del sistema di proprietà o per i callback del layout. Ad esempio, potrebbe essere presente un OnPropertyChanged callback che chiamerà InvalidateArrange se una proprietà dell'istanza segnala una modifica del valore e ha AffectsArrange come true 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 MinOrphanLines proprietà utilizzata nel modello di documento di flusso, in cui le modifiche apportate a tale proprietà possono modificare il rendering complessivo del documento di flusso che contiene il paragrafo. Usare AffectsParentArrange o AffectsParentMeasure per identificare casi simili nelle proprie proprietà.

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

  • Per impostazione predefinita, il data binding Mode per le proprietà di dipendenza è impostato su OneWay. È sempre possibile modificare l'associazione in modo che sia TwoWay per ogni istanza di associazione. Per informazioni dettagliate, vedere Specificare la direzione dell'associazione. Tuttavia, come autore della proprietà di dipendenza, è possibile scegliere di usare la modalità di associazione della TwoWay proprietà per impostazione predefinita. Un esempio di proprietà di dipendenza esistente è MenuItem.IsSubmenuOpen. Lo scenario per questa proprietà è che la IsSubmenuOpen logica di impostazione e la composizione di MenuItem interagiscono con lo stile del tema predefinito. La logica della IsSubmenuOpen proprietà usa il data binding in modo nativo per mantenere lo stato della proprietà in base ad altre proprietà di stato e chiamate al metodo. Un'altra proprietà di esempio associata per TwoWay impostazione predefinita è TextBox.Text.

  • È anche possibile abilitare l'ereditarietà delle proprietà in una proprietà di dipendenza personalizzata impostando il Inherits flag . L'ereditarietà della proprietà è utile per uno scenario in cui gli elementi padre e gli elementi figlio hanno una proprietà in comune e avrebbe senso per gli elementi figlio avere quel particolare valore di proprietà impostato sullo stesso valore impostato dall'elemento padre. Un esempio di proprietà ereditabile è DataContext, che viene usato per le operazioni di associazione per abilitare lo scenario master-dettagli importante per la presentazione dei dati. Rendendo DataContext ereditabile, anche tutti gli elementi figlio ereditano tale contesto di 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 i binding in tutti i possibili elementi figlio. DataContext è anche un buon esempio per illustrare che l'ereditarietà esegue l'override del valore predefinito, ma può essere sempre impostata localmente su qualsiasi particolare elemento figlio; per informazioni dettagliate, vedere Usare il modello master-dettagli con dati gerarchici. L'ereditarietà del valore della proprietà può incidere negativamente sulle prestazioni e pertanto deve essere usata sporadicamente. Per informazioni dettagliate, vedere Ereditarietà del valore della proprietà.

  • Impostare il Journal flag per indicare se la proprietà di dipendenza deve essere rilevata o usata dai servizi di journaling di navigazione. Un esempio è la SelectedIndex proprietà . Qualsiasi elemento selezionato in un controllo di selezione deve essere salvato in modo permanente quando si esplora la cronologia di inserimento nel journal.

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 altre informazioni, vedere Proprietà di dipendenza di sola lettura.

Proprietà di dipendenza di tipo raccolta

Le proprietà di dipendenza di tipo di raccolta presentano alcuni problemi di implementazione aggiuntivi che è opportuno considerare. Per altri dettagli, vedere Proprietà di dipendenza di tipo raccolta.

Considerazioni sulla sicurezza delle 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 protetti), è sempre possibile accedere a una proprietà di dipendenza tramite l'identificatore in combinazione con le API del sistema di proprietà. Anche un campo identificatore protetto è potenzialmente accessibile a causa di API di segnalazione dei metadati o determinazione del valore che fanno parte del sistema di proprietà, ad esempio LocalValueEnumerator. Per altre informazioni, vedere Sicurezza delle 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'uso 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 qualsiasi classe che deriva già da DependencyObject, è necessario tenere presente che il sistema di proprietà stesso chiama ed espone i metodi virtuali internamente. Questi metodi virtuali fanno 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, è consigliabile evitare di impostare valori della proprietà di dipendenza all'interno dei costruttori di classi, a meno che non si segua un modello del costruttore molto specifico. Per altri dettagli, vedere Modelli di costruttore sicuri per DependencyObject.

Vedi anche