Panoramica delle proprietà di dipendenza

Questo argomento illustra il sistema di proprietà di dipendenza disponibile quando si scrive un'app di Windows Runtime usando C++, C# o Visual Basic insieme alle definizioni XAML per l'interfaccia utente.

Che cos'è una proprietà di dipendenza?

Una proprietà di dipendenza è un tipo specializzato di proprietà. In particolare, si tratta di una proprietà in cui il valore della proprietà viene rilevato e influenzato da un sistema di proprietà dedicato che fa parte di Windows Runtime.

Per supportare una proprietà di dipendenza, l'oggetto che definisce la proprietà deve essere un DependencyObject (in altre parole una classe con la classe base DependencyObject in un punto qualsiasi dell'ereditarietà). Molti dei tipi usati per le definizioni dell'interfaccia utente per un'app UWP con XAML saranno una sottoclasse DependencyObject e supporteranno le proprietà di dipendenza. Tuttavia, qualsiasi tipo proveniente da uno spazio dei nomi di Windows Runtime che non ha "XAML" nel nome non supporterà le proprietà di dipendenza; le proprietà di questi tipi sono proprietà normali che non avranno il comportamento di dipendenza del sistema di proprietà.

Lo scopo delle proprietà di dipendenza è fornire un modo sistemico per calcolare il valore di una proprietà in base ad altri input (altre proprietà, eventi e stati che si verificano all'interno dell'app durante l'esecuzione). Questi altri input possono includere:

  • Input esterno, ad esempio preferenza utente
  • Meccanismi di determinazione delle proprietà JIT, ad esempio data binding, animazioni e storyboard
  • Modelli di modelli a uso multiplo, ad esempio risorse e stili
  • Valori noti tramite relazioni padre-figlio con altri elementi nell'albero degli oggetti

Una proprietà di dipendenza rappresenta o supporta una funzionalità specifica del modello di programmazione per la definizione di un'app di Windows Runtime con XAML per l'interfaccia utente e C#, le estensioni del componente Microsoft Visual Basic o Visual C++ (C++/CX) per il codice. Queste funzionalità sono:

  • Data binding
  • Stili
  • Animazioni con storyboard
  • Comportamento "PropertyChanged" ; È possibile implementare una proprietà di dipendenza per fornire callback che possono propagare modifiche ad altre proprietà di dipendenza
  • Uso di un valore predefinito proveniente dai metadati delle proprietà
  • Utilità generale del sistema di proprietà, ad esempio ClearValue e ricerca dei metadati

Proprietà di dipendenza e proprietà di Windows Runtime

Le proprietà di dipendenza estendono le funzionalità di base delle proprietà di Windows Runtime fornendo un archivio proprietà interno globale che restituisce tutte le proprietà di dipendenza in un'app in fase di esecuzione. Si tratta di un'alternativa al modello standard di supporto di una proprietà con un campo privato privato nella classe property-definition. È possibile considerare questo archivio proprietà interno come un set di identificatori di proprietà e valori esistenti per qualsiasi oggetto specifico (purché si tratti di un DependencyObject). Anziché essere identificata in base al nome, ogni proprietà nell'archivio viene identificata da un'istanza dependencyProperty . Tuttavia, il sistema di proprietà nasconde principalmente questo dettaglio di implementazione: in genere è possibile accedere alle proprietà di dipendenza usando un nome semplice (il nome della proprietà a livello di codice nel linguaggio di codice usato o un nome di attributo durante la scrittura di XAML).

Il tipo di base che fornisce le basi del sistema di proprietà di dipendenza è DependencyObject. DependencyObject definisce metodi che possono accedere alla proprietà di dipendenza e le istanze di una classe derivata DependencyObject supportano internamente il concetto di archivio delle proprietà menzionato in precedenza.

Di seguito è riportato un riepilogo della terminologia usata nella documentazione per discutere delle proprietà di dipendenza:

Termine Descrizione
Proprietà di dipendenza Proprietà esistente in un identificatore DependencyProperty (vedere di seguito). In genere questo identificatore è disponibile come membro statico della classe derivata DependencyObject .
Identificatore di proprietà di dipendenza Valore costante per identificare la proprietà, in genere pubblica e di sola lettura.
Wrapper delle proprietà Implementazioni get e set chiamabili per una proprietà di Windows Runtime. In alternativa, la proiezione specifica del linguaggio della definizione originale. Un'implementazione di get property wrapper chiama GetValue, passando l'identificatore della proprietà di dipendenza pertinente.

Il wrapper di proprietà non è solo pratico per i chiamanti, ma espone anche la proprietà di dipendenza a qualsiasi processo, strumento o proiezione che usa le definizioni di Windows Runtime per le proprietà.

L'esempio seguente definisce una proprietà di dipendenza personalizzata come definita per C# e mostra la relazione dell'identificatore della proprietà di dipendenza con il wrapper della proprietà.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  "Label",
  typeof(string),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);


public string Label
{
    get { return (string)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}

Nota

L'esempio precedente non è destinato all'esempio completo per la creazione di una proprietà di dipendenza personalizzata. È progettato per mostrare i concetti relativi alle proprietà di dipendenza per chiunque preferisca apprendere i concetti tramite il codice. Per una spiegazione più completa di questo esempio, vedere Proprietà di dipendenza personalizzate.

Precedenza del valore della proprietà di dipendenza

Quando si ottiene il valore di una proprietà di dipendenza, si ottiene un valore determinato per tale proprietà tramite uno degli input che partecipano al sistema di proprietà di Windows Runtime. La precedenza del valore della proprietà di dipendenza esiste in modo che il sistema di proprietà di Windows Runtime possa calcolare i valori in modo prevedibile ed è importante avere familiarità anche con l'ordine di precedenza di base. In caso contrario, è possibile che ci si trovi in una situazione in cui si sta tentando di impostare una proprietà a un livello di precedenza, ma qualcos'altro (il sistema, i chiamanti di terze parti, alcuni dei propri codici) lo sta impostando a un altro livello e si otterrà frustrante cercando di capire quale valore della proprietà viene usato e da dove proviene tale valore.

Ad esempio, gli stili e i modelli devono essere un punto di partenza condiviso per stabilire i valori delle proprietà e quindi gli aspetti di un controllo. Tuttavia, in una particolare istanza del controllo è possibile modificarne il valore rispetto al valore comune basato su modelli, ad esempio assegnando a tale controllo un colore di sfondo diverso o una stringa di testo diversa come contenuto. Il sistema di proprietà di Windows Runtime considera i valori locali con precedenza superiore rispetto ai valori forniti da stili e modelli. Ciò consente allo scenario di sovrascrivere i modelli specifici dell'app in modo che i controlli siano utili per il proprio uso nell'interfaccia utente dell'app.

Elenco di precedenza delle proprietà di dipendenza

Di seguito è riportato l'ordine definitivo usato dal sistema di proprietà quando si assegna il valore di runtime per una proprietà di dipendenza. La precedenza più elevata viene elencata per prima. Troverai spiegazioni più dettagliate appena oltre questo elenco.

  1. Valori animati: animazioni attive, animazioni dello stato visivo o animazioni con un comportamento HoldEnd. Per avere un effetto pratico, un'animazione applicata a una proprietà deve avere la precedenza sul valore di base (senzanimazione), anche se tale valore è stato impostato localmente.
  2. Valore locale: un valore locale può essere impostato tramite la praticità del wrapper della proprietà, che equivale anche all'impostazione come attributo o elemento di proprietà in XAML o tramite una chiamata al metodo SetValue usando una proprietà di un'istanza specifica. Se si imposta un valore locale usando un'associazione o una risorsa statica, questi agiscono come se fosse impostato un valore locale e le associazioni o i riferimenti alle risorse verranno cancellati se viene impostato un nuovo valore locale.
  3. Proprietà basate su modelli: un elemento include queste proprietà se è stata creata come parte di un modello (da controlTemplate o DataTemplate).
  4. Setter di stile: valori di un setter all'interno di stili dalle risorse della pagina o dell'applicazione.
  5. Valore predefinito: una proprietà di dipendenza può avere un valore predefinito come parte dei metadati.

Proprietà basato su modelli

Le proprietà modello come elemento di precedenza non si applicano ad alcuna proprietà di un elemento dichiarato direttamente nel markup della pagina XAML. Il concetto di proprietà basata su modelli esiste solo per gli oggetti creati quando Windows Runtime applica un modello XAML a un elemento dell'interfaccia utente e ne definisce gli oggetti visivi.

Tutte le proprietà impostate da un modello di controllo hanno valori di qualche tipo. Questi valori sono quasi simili a un set esteso di valori predefiniti per il controllo e sono spesso associati ai valori che è possibile reimpostare in un secondo momento impostando direttamente i valori delle proprietà. Pertanto, i valori del set di modelli devono essere distinti da un valore locale reale, in modo che qualsiasi nuovo valore locale possa sovrascriverlo.

Nota

In alcuni casi il modello potrebbe eseguire l'override di valori locali anche se il modello non è riuscito a esporre riferimenti all'estensione di markup {TemplateBinding} per le proprietà che devono essere state impostate nelle istanze. Questa operazione viene in genere eseguita solo se la proprietà non è effettivamente progettata per essere impostata sulle istanze, ad esempio se è rilevante solo per gli oggetti visivi e il comportamento del modello e non per la funzione o la logica di runtime prevista del controllo che usa il modello.

Associazioni e precedenza

Le operazioni di associazione hanno la precedenza appropriata per qualsiasi ambito per cui vengono usate. Ad esempio, un {Binding} applicato a un valore locale funge da valore locale e un'estensione di markup {TemplateBinding} per un setter di proprietà viene applicata come setter di stile. Poiché le associazioni devono attendere fino al runtime per ottenere valori dalle origini dati, il processo di determinazione della precedenza del valore della proprietà per qualsiasi proprietà si estende anche in fase di esecuzione.

Non solo le associazioni operano con la stessa precedenza di un valore locale, ma sono realmente un valore locale, dove l'associazione è il segnaposto per un valore posticipato. Se è presente un'associazione per un valore della proprietà e si imposta un valore locale in fase di esecuzione, che sostituisce completamente l'associazione. Analogamente, se chiami SetBinding per definire un'associazione esistente solo in fase di esecuzione, sostituisci qualsiasi valore locale che potresti aver applicato in XAML o con codice eseguito in precedenza.

Animazioni con storyboard e valore di base

Le animazioni con storyboard agiscono su un concetto di valore di base. Il valore di base è il valore determinato dal sistema di proprietà usando la precedenza, ma omettendo l'ultimo passaggio di ricerca di animazioni. Ad esempio, un valore di base potrebbe provenire dal modello di un controllo oppure potrebbe provenire dall'impostazione di un valore locale in un'istanza di un controllo. In entrambi i casi, l'applicazione di un'animazione sovrascriverà questo valore di base e applicherà il valore animato finché l'animazione continua a essere eseguita.

Per una proprietà animata, il valore di base può comunque avere un effetto sul comportamento dell'animazione, se tale animazione non specifica in modo esplicito sia From che To oppure se l'animazione ripristina il valore di base quando viene completata. In questi casi, una volta che un'animazione non è più in esecuzione, il resto della precedenza viene nuovamente usato.

Tuttavia, un'animazione che specifica un oggetto To con un comportamento HoldEnd può eseguire l'override di un valore locale fino a quando l'animazione non viene rimossa, anche quando sembra essere arrestata visivamente. Concettualmente si tratta di un'animazione che viene eseguita per sempre anche se non è presente un'animazione visiva nell'interfaccia utente.

È possibile applicare più animazioni a una singola proprietà. Ognuna di queste animazioni potrebbe essere stata definita per sostituire i valori di base provenienti da punti diversi nella precedenza del valore. Tuttavia, queste animazioni verranno eseguite contemporaneamente in fase di esecuzione e ciò spesso significa che devono combinare i valori perché ogni animazione ha uguale influenza sul valore. Ciò dipende dal modo preciso in cui sono definite le animazioni e dal tipo del valore che viene animato.

Per altre info, vedi Animazioni con storyboard.

Valori predefiniti

La definizione del valore predefinito per una proprietà di dipendenza con un valore PropertyMetadata è illustrata in modo più dettagliato nell'argomento Proprietà di dipendenza personalizzate.

Le proprietà di dipendenza hanno ancora valori predefiniti anche se tali valori predefiniti non sono stati definiti in modo esplicito nei metadati di tale proprietà. A meno che non siano stati modificati dai metadati, i valori predefiniti per le proprietà di dipendenza di Windows Runtime sono in genere uno dei seguenti:

  • Una proprietà che utilizza un oggetto runtime o il tipo Object di base (un tipo riferimento) ha un valore predefinito null. Ad esempio, DataContext è Null fino a quando non viene impostato intenzionalmente o ereditato.
  • Una proprietà che usa un valore di base, ad esempio numeri o un valore booleano (un tipo valore) usa un valore predefinito previsto per tale valore. Ad esempio, 0 per numeri interi e numeri a virgola mobile, false per un valore Boolean.
  • Una proprietà che usa una struttura di Windows Runtime ha un valore predefinito ottenuto chiamando il costruttore predefinito implicito di tale struttura. Questo costruttore usa le impostazioni predefinite per ognuno dei campi valore di base della struttura. Ad esempio, un valore predefinito per un valore Point viene inizializzato con i valori X e Y come 0.
  • Una proprietà che utilizza un'enumerazione ha un valore predefinito del primo membro definito in tale enumerazione. Controllare la presenza di enumerazioni specifiche per verificare qual è il valore predefinito.
  • Una proprietà che usa una stringa (System.String per .NET, Platform::String per C++/CX) ha un valore predefinito di una stringa vuota ("").
  • Le proprietà della raccolta non vengono in genere implementate come proprietà di dipendenza, per motivi illustrati più avanti in questo argomento. Tuttavia, se si implementa una proprietà di raccolta personalizzata e si vuole che sia una proprietà di dipendenza, assicurarsi di evitare un singleton non intenzionale come descritto nella parte finale delle proprietà di dipendenza personalizzate.

Funzionalità offerte da una proprietà di dipendenza

Data binding

Una proprietà di dipendenza può avere il relativo valore impostato applicando un data binding. Il data binding usa la sintassi dell'estensione di markup {Binding} in XAML, l'estensione di markup {x:Bind} o la classe Binding nel codice. Per una proprietà databound, la determinazione del valore finale della proprietà viene posticipata fino al runtime. In quel momento il valore viene ottenuto da un'origine dati. Il ruolo svolto dal sistema di proprietà di dipendenza qui consiste nell'abilitare un comportamento segnaposto per operazioni come il caricamento di XAML quando il valore non è ancora noto e quindi fornire il valore in fase di esecuzione interagendo con il motore di data binding di Windows Runtime.

L'esempio seguente imposta il valore Text per un elemento TextBlock , usando un'associazione in XAML. L'associazione usa un contesto dati ereditato e un'origine dati oggetto. (Nessuno di questi elementi viene visualizzato nell'esempio abbreviato; per un esempio più completo che mostra il contesto e l'origine, vedere Data binding approfondito.

<Canvas>
  <TextBlock Text="{Binding Team.TeamName}"/>
</Canvas>

Puoi anche stabilire associazioni usando codice anziché XAML. Vedere SetBinding.

Nota

Le associazioni come questa vengono considerate come un valore locale ai fini della precedenza del valore della proprietà di dipendenza. Se si imposta un altro valore locale per una proprietà che originariamente contiene un valore binding, si sovrascriverà completamente l'associazione, non solo il valore di runtime dell'associazione. {x:Bind} Le associazioni vengono implementate usando codice generato che imposta un valore locale per la proprietà . Se si imposta un valore locale per una proprietà che usa {x:Bind}, tale valore verrà sostituito alla successiva valutazione dell'associazione, ad esempio quando osserva una modifica della proprietà sull'oggetto di origine.

Origini di associazione, destinazioni di associazione, ruolo di FrameworkElement

Per essere l'origine di un'associazione, non è necessario che una proprietà sia una proprietà di dipendenza; in genere è possibile usare qualsiasi proprietà come origine di associazione, anche se ciò dipende dal linguaggio di programmazione e ognuno ha determinati casi limite. Tuttavia, per essere la destinazione di un'estensione di markup {Binding} o Binding, tale proprietà deve essere una proprietà di dipendenza. {x:Bind} non ha questo requisito perché usa il codice generato per applicare i valori di associazione.

Se si sta creando un'associazione nel codice, si noti che l'API SetBinding è definita solo per FrameworkElement. Tuttavia, è possibile creare una definizione di associazione usando BindingOperations e quindi fare riferimento a qualsiasi proprietà DependencyObject.

Per il codice o XAML, tenere presente che DataContext è una proprietà FrameworkElement. Usando una forma di ereditarietà delle proprietà padre-figlio (in genere stabilita nel markup XAML), il sistema di associazione può risolvere un DataContext esistente in un elemento padre. Questa ereditarietà può valutare anche se l'oggetto figlio (che ha la proprietà di destinazione) non è un FrameworkElement e pertanto non contiene il proprio valore DataContext . Tuttavia, l'elemento padre ereditato deve essere un FrameworkElement per impostare e contenere DataContext. In alternativa, è necessario definire l'associazione in modo che possa funzionare con un valore Null per DataContext.

Collegare l'associazione non è l'unica cosa necessaria per la maggior parte degli scenari di data binding. Affinché un'associazione unidirezionale o bidirezionale sia efficace, la proprietà di origine deve supportare le notifiche di modifica propagate al sistema di associazione e quindi alla destinazione. Per le origini di associazione personalizzate, ciò significa che la proprietà deve essere una proprietà di dipendenza oppure l'oggetto deve supportare INotifyPropertyChanged. Le raccolte devono supportare INotifyCollectionChanged. Alcune classi supportano queste interfacce nelle loro implementazioni in modo che siano utili come classi di base per gli scenari di data binding; un esempio di tale classe è ObservableCollection<T>. Per altre informazioni sul data binding e sul modo in cui il data binding è correlato al sistema di proprietà, vedere Data binding in modo approfondito.

Nota

I tipi elencati qui supportano le origini dati Microsoft .NET. Le origini dati C++/CX usano interfacce diverse per la notifica delle modifiche o il comportamento osservabile, vedere Informazioni approfondite sul data binding.

Stili e modelli

Gli stili e i modelli sono due degli scenari per le proprietà definite come proprietà di dipendenza. Gli stili sono utili per impostare le proprietà che definiscono l'interfaccia utente dell'app. Gli stili vengono definiti come risorse in XAML, come voce in una raccolta Resources o in file XAML separati, ad esempio dizionari risorse tema. Gli stili interagiscono con il sistema di proprietà perché contengono setter per le proprietà. La proprietà più importante è la proprietà Control.Template di un controllo: definisce la maggior parte dell'aspetto visivo e dello stato di visualizzazione per un controllo. Per altre info sugli stili e su alcuni esempi xaml che definiscono uno stile e usano setter, vedi Applicazione di stili ai controlli.

I valori provenienti da stili o modelli sono valori posticipati, simili alle associazioni. In questo modo gli utenti possono riabilitare i controlli dei modelli o ridefinire gli stili. Ecco perché i setter di proprietà negli stili possono agire solo sulle proprietà di dipendenza, non sulle proprietà normali.

Animazioni con storyboard

Puoi animare il valore di una proprietà di dipendenza usando un'animazione con storyboard. Le animazioni con storyboard in Windows Runtime non sono semplicemente decorazioni visive. È più utile considerare le animazioni come una tecnica della macchina a stati che può impostare i valori delle singole proprietà o di tutte le proprietà e di tutti gli oggetti visivi di un controllo e modificare questi valori nel tempo.

Per essere animata, la proprietà di destinazione dell'animazione deve essere una proprietà di dipendenza. Inoltre, per essere animato, il tipo di valore della proprietà di destinazione deve essere supportato da uno dei tipi di animazione derivati dalla sequenza temporale esistenti. I valori di Color, Double e Point possono essere animati usando tecniche di interpolazione o fotogrammi chiave. La maggior parte degli altri valori può essere animata usando fotogrammi chiave Object discreti.

Quando un'animazione viene applicata e in esecuzione, il valore animato opera con una precedenza maggiore rispetto a qualsiasi valore (ad esempio un valore locale) che la proprietà ha in caso contrario. Le animazioni hanno anche un comportamento HoldEnd facoltativo che può causare l'applicazione delle animazioni ai valori delle proprietà anche se l'animazione sembra essere arrestata visivamente.

Il principio della macchina a stati è rappresentato dall'uso di animazioni con storyboard come parte del modello di stato VisualStateManager per i controlli. Per altre info sulle animazioni con storyboard, vedi Animazioni con storyboard. Per altre info su VisualStateManager e sulla definizione degli stati di visualizzazione per i controlli, vedi Animazioni con storyboard per stati di visualizzazione o Modelli di controllo.

Comportamento modificato dalla proprietà

Il comportamento delle proprietà modificate è l'origine della parte "dipendenza" della terminologia delle proprietà di dipendenza. La gestione di valori validi per una proprietà quando un'altra proprietà può influenzare il valore della prima proprietà è un problema di sviluppo difficile in molti framework. Nel sistema di proprietà di Windows Runtime ogni proprietà di dipendenza può specificare un callback richiamato ogni volta che cambia il valore della proprietà. Questo callback può essere usato per notificare o modificare i valori delle proprietà correlate, in modo generalmente sincrono. Molte proprietà di dipendenza esistenti hanno un comportamento modificato dalla proprietà. È anche possibile aggiungere un comportamento di callback simile alle proprietà di dipendenza personalizzate e implementare callback personalizzati modificati dalle proprietà. Per un esempio, vedere Proprietà di dipendenza personalizzate .

Windows 10 introduce il metodo RegisterPropertyChangedCallback. Ciò consente al codice dell'applicazione di registrarsi per le notifiche di modifica quando la proprietà di dipendenza specificata viene modificata in un'istanza di DependencyObject.

Valore predefinito e ClearValue

Una proprietà di dipendenza può avere un valore predefinito definito come parte dei metadati della proprietà. Per una proprietà di dipendenza, il valore predefinito non diventa irrilevante dopo che la proprietà è stata impostata per la prima volta. Il valore predefinito potrebbe essere applicato di nuovo in fase di esecuzione ogni volta che un altro determinante nella precedenza del valore scompare. La precedenza del valore della proprietà di dipendenza viene descritta nella sezione successiva. Ad esempio, potresti rimuovere deliberatamente un valore di stile o un'animazione che si applica a una proprietà, ma vuoi che il valore sia un valore predefinito ragionevole dopo l'operazione. Il valore predefinito della proprietà di dipendenza può fornire questo valore, senza dover impostare specificamente il valore di ogni proprietà come passaggio aggiuntivo.

È possibile impostare deliberatamente una proprietà sul valore predefinito anche dopo averla già impostata con un valore locale. Per reimpostare un valore come predefinito e anche per abilitare altri partecipanti con precedenza che potrebbero eseguire l'override del valore predefinito ma non di un valore locale, chiamare il metodo ClearValue (fare riferimento alla proprietà da cancellare come parametro del metodo). Non si vuole sempre che la proprietà usi letteralmente il valore predefinito, ma la cancellazione del valore locale e il ripristino del valore predefinito potrebbe abilitare un altro elemento con precedenza che si vuole agire ora, ad esempio usando il valore proveniente da un setter di stile in un modello di controllo.

DependencyObject e threading

Tutte le istanze di DependencyObject devono essere create nel thread dell'interfaccia utente associato alla finestra corrente visualizzata da un'app di Windows Runtime. Anche se ogni DependencyObject deve essere creato nel thread principale dell'interfaccia utente, è possibile accedere agli oggetti usando un riferimento dispatcher da altri thread, accedendo alla proprietà Dispatcher. È quindi possibile chiamare metodi come RunAsync nell'oggetto CoreDispatcher ed eseguire il codice all'interno delle regole delle restrizioni dei thread nel thread dell'interfaccia utente.

Gli aspetti del threading di DependencyObject sono rilevanti perché in genere significa che solo il codice eseguito nel thread dell'interfaccia utente può modificare o persino leggere il valore di una proprietà di dipendenza. I problemi di threading possono in genere essere evitati nel codice tipico dell'interfaccia utente che usa correttamente i modelli asincroni e i thread di lavoro in background. In genere si verificano problemi di threading correlati a DependencyObject solo se si definiscono i propri tipi DependencyObject e si tenta di usarli per origini dati o altri scenari in cui dependencyObject non è necessariamente appropriato.

Materiale concettuale