Condividi tramite


Proprietà di dipendenza personalizzate (WPF .NET)

Gli sviluppatori di applicazioni e gli autori di componenti di Windows Presentation Foundation (WPF) possono creare proprietà di dipendenza personalizzate per estendere le funzionalità delle relative proprietà. A differenza di una proprietà CLR (Common Language Runtime) , una proprietà di dipendenza aggiunge il supporto per stili, data binding, ereditarietà, animazioni e valori predefiniti. Background, Widthe Text sono esempi di proprietà di dipendenza esistenti nelle classi WPF. Questo articolo descrive come implementare proprietà di dipendenza personalizzate e presenta opzioni per migliorare le prestazioni, l'usabilità e la versatilità.

Prerequisiti

L'articolo presuppone una conoscenza di base delle proprietà di dipendenza e che tu abbia letto "Panoramica delle proprietà di dipendenza". Per seguire gli esempi in questo articolo, è utile se si ha familiarità con Extensible Application Markup Language (XAML) e si sa come scrivere applicazioni WPF.

Identificatore di proprietà di dipendenza

Le proprietà di dipendenza sono proprietà registrate nel sistema di proprietà WPF tramite chiamate Register o RegisterReadOnly. Il metodo Register restituisce un'istanza di DependencyProperty che contiene il nome registrato e le caratteristiche di una proprietà di dipendenza. Si assegnerà l'istanza di DependencyProperty a un campo di sola lettura statico, noto come identificatore di proprietà di dipendenza , che per convenzione è denominato <property name>Property. Ad esempio, il campo identificatore per la proprietà Background è sempre BackgroundProperty.

L'identificatore della proprietà di dipendenza viene usato come campo sottostante per ottenere o impostare i valori delle proprietà, anziché come modello standard di backup di una proprietà con un campo privato. Non solo il sistema di proprietà usa l'identificatore, i processori XAML possono usarlo e il codice (e possibilmente codice esterno) può accedere alle proprietà di dipendenza tramite i relativi identificatori.

Le proprietà di dipendenza possono essere applicate solo alle classi derivate dai tipi DependencyObject. La maggior parte delle classi WPF supporta le proprietà di dipendenza, perché DependencyObject è vicina alla radice della gerarchia di classi WPF. Per altre informazioni sulle proprietà di dipendenza e sulla terminologia e sulle convenzioni usate per descriverle, vedere panoramica delle proprietà di dipendenza .

Wrapper delle proprietà di dipendenza

Le proprietà di dipendenza WPF che non sono proprietà allegate vengono esposte da un wrapper CLR che implementa gli accessori get e set. Usando un wrapper di proprietà, i consumatori di proprietà di dipendenza possono ottenere o impostare i valori delle proprietà di dipendenza, esattamente come farebbero con una proprietà CLR. Le funzioni di accesso get e set interagiscono con il sistema di proprietà sottostante tramite le chiamate DependencyObject.GetValue e DependencyObject.SetValue, passando come parametro l'identificatore della proprietà di dipendenza. I consumatori delle proprietà di dipendenza in genere non chiamano direttamente GetValue o SetValue, ma se implementi una proprietà di dipendenza personalizzata, userai questi metodi nel wrapper.

Quando implementare una proprietà di dipendenza

Quando si implementa una proprietà in una classe che deriva da DependencyObject, si imposta una proprietà di dipendenza tramite il backup della proprietà con un identificatore di DependencyProperty. Il fatto che sia utile creare una proprietà di dipendenza dipende dallo scenario in uso. Anche se usare un campo privato per la tua proprietà è adeguato per alcuni scenari, è consigliabile implementare una proprietà di dipendenza se desideri che la proprietà supporti una o più delle seguenti funzionalità di WPF:

  • Proprietà che sono impostabili all'interno di uno stile. Per altre informazioni, vedere Stili e modelli.

  • Proprietà che supportano il data binding. Per altre informazioni sulle proprietà di dipendenza del data binding, vedere Associare le proprietà di due controlli.

  • Proprietà impostabili tramite riferimenti a risorse dinamiche. Per altre informazioni, vedere risorse XAML.

  • Proprietà che ereditano automaticamente il valore da un elemento padre nell'albero degli elementi. A tale scopo, è necessario eseguire la registrazione usando RegisterAttached, anche se si crea anche un wrapper di proprietà per l'accesso a CLR. Per altre informazioni, vedere ereditarietà del valore della proprietà.

  • Proprietà animabili. Per altre informazioni, vedere Panoramica dell'animazione.

  • Notifica da parte del sistema di proprietà WPF quando viene modificato un valore della proprietà. Le modifiche potrebbero essere dovute alle azioni eseguite dal sistema di proprietà, dall'ambiente, dall'utente o dagli stili. La tua proprietà può specificare un metodo di callback nei metadati delle proprietà, che verrà richiamato ogni volta che il sistema di proprietà determina che il tuo valore di proprietà è stato modificato. Un concetto correlato è la coercizione del valore della proprietà. Per altre informazioni, vedere callback delle proprietà di dipendenza e convalida.

  • Accesso ai metadati delle proprietà di dipendenza, letti dai processi WPF. Ad esempio, è possibile usare i metadati delle proprietà per:

    • Specificare se un valore della proprietà di dipendenza modificato deve causare la ricomposizione visiva degli elementi nell'interfaccia utente per un elemento.

    • Impostare il valore predefinito di una proprietà di dipendenza eseguendo l'override dei metadati nelle classi derivate.

  • Supporto per il designer WPF di Visual Studio, come la modifica delle proprietà di un controllo personalizzato nella finestra Proprietà. Per ulteriori informazioni, vedere la panoramica sulla creazione di controlli.

Per alcuni scenari, l'override dei metadati di una proprietà di dipendenza esistente è un'opzione migliore rispetto all'implementazione di una nuova proprietà di dipendenza. Il fatto che una sovrascrittura dei metadati sia pratica dipende dal tuo scenario e da quanto il tuo scenario somiglia all'implementazione di proprietà di dipendenza e classi WPF esistenti. Per ulteriori informazioni sull'override dei metadati delle proprietà di dipendenza esistenti, vedere .

Lista di controllo per la creazione di una proprietà di dipendenza

Seguire questa procedura per creare una proprietà di dipendenza. Alcuni passaggi possono essere combinati e implementati in una singola riga di codice.

  1. (Facoltativo) Creare i metadati delle proprietà di dipendenza.

  2. Registrare la proprietà di dipendenza con il sistema di proprietà, specificando un nome di proprietà, un tipo di proprietario, il tipo di valore della proprietà e, facoltativamente, i metadati delle proprietà.

  3. Definire un identificatore DependencyProperty come campo public static readonly nel tipo di proprietario. Il nome del campo dell'identificatore è il nome della proprietà con il suffisso Property aggiunto.

  4. Definire una proprietà wrapper CLR con lo stesso nome che quello della proprietà di dipendenza. Nel wrapper CLR, implementare i metodi accessor get e set che si connettono alla proprietà di dipendenza che supporta il wrapper.

Registrazione della proprietà

Affinché la proprietà sia una proprietà di dipendenza, è necessario registrarla nel sistema di proprietà. Per registrare la proprietà, chiamare il metodo Register dall'interno del corpo della classe, ma all'esterno di qualsiasi definizione di membro. Il metodo Register restituisce un identificatore univoco della proprietà di dipendenza che verrà usato quando si chiama l'API del sistema di proprietà. Il motivo per cui la chiamata Register viene effettuata al di fuori delle definizioni dei membri è che si assegna il valore restituito a un campo public static readonly di tipo DependencyProperty. Questo campo, che creerai nella tua classe, è l'identificatore per la tua proprietà di dipendenza. Nell'esempio seguente il primo argomento di Register denomina la proprietà di dipendenza AquariumGraphic.

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

Nota

La definizione della proprietà di dipendenza nel corpo della classe è l'implementazione tipica, ma è anche possibile definire una proprietà di dipendenza nel costruttore statico della classe. Questo approccio può essere utile se sono necessarie più righe di codice per inizializzare la proprietà di dipendenza.

Denominazione delle proprietà di dipendenza

La convenzione di denominazione stabilita per le proprietà di dipendenza è obbligatoria per il normale comportamento del sistema di proprietà. Il nome del campo identificatore creato deve essere il nome registrato della proprietà con il suffisso Property.

Il nome di una proprietà di dipendenza deve essere univoco all'interno della classe di registrazione. Le proprietà di dipendenza ereditate tramite un tipo di base sono già state registrate e non possono essere registrate da un tipo derivato. Tuttavia, è possibile usare una proprietà di dipendenza registrata da un tipo diverso, anche un tipo da cui la classe non eredita, rendendo la classe proprietaria della proprietà di dipendenza. Per ulteriori informazioni su come aggiungere una classe come proprietario, consulta i metadati delle proprietà di dipendenza .

Implementazione di un wrapper di proprietà

Per convenzione, il nome della proprietà wrapper deve essere uguale al primo parametro della chiamata Register, ovvero il nome della proprietà di dipendenza. L'implementazione del wrapper chiamerà GetValue nella funzione di accesso get e SetValue nella funzione di accesso set (per le proprietà di lettura/scrittura). L'esempio seguente mostra un wrapper, successivo alla chiamata di registrazione e alla dichiarazione del campo identificatore. Tutte le proprietà di dipendenza pubbliche nelle classi WPF usano un modello wrapper simile.

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );

// Declare a read-write property wrapper.
public Uri AquariumGraphic
{
    get => (Uri)GetValue(AquariumGraphicProperty);
    set => SetValue(AquariumGraphicProperty, value);
}
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

' Declare a read-write property wrapper.
Public Property AquariumGraphic As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set
        SetValue(AquariumGraphicProperty, Value)
    End Set
End Property

Ad eccezione dei rari casi, l'implementazione del wrapper deve contenere solo codice GetValue e SetValue. Per i motivi alla base di questo, vedere le Implicazioni per le proprietà di dipendenza personalizzate.

Se la proprietà non segue le convenzioni di denominazione stabilite, è possibile che si verifichino questi problemi:

  • Alcuni aspetti degli stili e dei modelli non funzioneranno.

  • La maggior parte degli strumenti e dei designer si basa sulle convenzioni di denominazione per serializzare correttamente XAML e 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 per elaborare i valori degli attributi. Per ulteriori informazioni, vedere il caricamento XAML e le proprietà di dipendenza .

Metadati delle proprietà di dipendenza

Quando si registra una proprietà di dipendenza, il sistema di proprietà crea un oggetto metadati per archiviare le caratteristiche delle proprietà. Gli overload del metodo Register consentono di specificare i metadati delle proprietà durante la registrazione, ad esempio Register(String, Type, Type, PropertyMetadata). Un uso comune dei metadati delle proprietà consiste nell'applicare un valore predefinito personalizzato per le nuove istanze che usano una proprietà di dipendenza. Se non si forniscono metadati delle proprietà, il sistema di proprietà assegnerà valori predefiniti a molte delle caratteristiche delle proprietà di dipendenza.

Se si sta creando una proprietà di dipendenza in una classe derivata da FrameworkElement, è possibile usare la classe di metadati più specializzata FrameworkPropertyMetadata anziché la relativa classe base PropertyMetadata. Diverse firme del costruttore FrameworkPropertyMetadata consentono di specificare differenti combinazioni di caratteristiche dei metadati. Se si vuole solo specificare un valore predefinito, usare FrameworkPropertyMetadata(Object) e passare il valore predefinito al parametro Object. Assicurati che il tipo di valore corrisponda al propertyType specificato nella chiamata Register.

Alcuni overload FrameworkPropertyMetadata consentono di specificare dei flag di opzione dei metadati per la tua proprietà. Il sistema di proprietà converte questi flag in proprietà discrete e i valori dei flag vengono usati dai processi WPF, ad esempio il motore di layout.

Impostazione dei flag di metadati

Quando si impostano i flag di metadati, tenere presente quanto segue:

  • Se il valore di una proprietà (o una sua modifica) influisce su come il sistema di layout rende un elemento dell'interfaccia utente, impostare uno o più dei seguenti flag:

    • AffectsMeasure, che indica che una modifica del valore della proprietà richiede una modifica nel rendering dell'interfaccia utente, in particolare lo spazio occupato da un oggetto all'interno del relativo elemento padre. Ad esempio, impostare questo flag di metadati per una proprietà Width.

    • AffectsArrange, che indica che una modifica del valore della proprietà richiede una modifica nel rendering dell'interfaccia utente, in particolare la posizione di un oggetto all'interno del relativo elemento padre. In genere, l'oggetto non modifica anche le dimensioni. Ad esempio, impostare questo indicatore di metadati per una proprietà Alignment.

    • AffectsRender, che indica che si è verificata una modifica che non influisce sul layout e sulla misura, ma richiede comunque un altro rendering. Ad esempio, imposta questo flag per una proprietà Background o qualsiasi altra proprietà che influisce sul colore di un elemento.

    È anche possibile usare questi flag come input per le implementazioni di override dei callback del sistema di proprietà (o layout). Ad esempio, è possibile usare un callback OnPropertyChanged per chiamare InvalidateArrange quando una proprietà dell'istanza segnala una modifica del valore e ha AffectsArrange impostato nei metadati.

  • Alcune proprietà influiscono sulle caratteristiche di rendering dell'elemento padre in altri modi. Ad esempio, le modifiche apportate alla proprietà MinOrphanLines possono modificare il rendering complessivo di un documento di flusso. Usare AffectsParentArrange o AffectsParentMeasure per segnalare le azioni padre nelle tue proprietà.

  • Per impostazione predefinita, le proprietà di dipendenza supportano il data binding. Tuttavia, è possibile usare IsDataBindingAllowed per disabilitare il data binding quando non esiste uno scenario realistico o in cui le prestazioni del data binding sono problematiche, ad esempio su oggetti di grandi dimensioni.

  • Anche se il data binding predefinito modalità per le proprietà di dipendenza è OneWay, è possibile modificare la modalità di associazione di un'associazione specifica in TwoWay. Per ulteriori informazioni, vedere Orientamento del vincolo. In qualità di autore di proprietà di dipendenza, puoi anche scegliere di impostare l'associazione bidirezionale come modalità predefinita. Un esempio di una proprietà di dipendenza esistente che utilizza l'associazione dati bidirezionale è MenuItem.IsSubmenuOpen, che ha uno stato basato su altre proprietà e chiamate di metodo. Lo scenario per IsSubmenuOpen è che la logica di impostazione e la composizione di MenuIteminteragiscono con lo stile del tema predefinito. TextBox.Text è un'altra proprietà di dipendenza WPF che usa l'associazione bidirezionale per impostazione predefinita.

  • È possibile abilitare l'ereditarietà delle proprietà di una proprietà di dipendenza impostando il flag Inherits. L'ereditarietà delle proprietà è utile per gli scenari in cui gli elementi padre e figlio hanno una proprietà in comune e ha senso che l'elemento figlio erediti il valore padre per la proprietà comune. Un esempio di proprietà ereditabile è DataContext, che supporta operazioni di associazione che usano lo scenario master-detail per la presentazione dei dati. L'ereditarietà del valore della proprietà ti consente di specificare un contesto dati alla radice della pagina o dell'applicazione, eliminando la necessità di specificarlo per le associazioni degli elementi figli. Anche se un valore di proprietà ereditato sovrascrive il valore predefinito, i valori delle proprietà possono essere impostati localmente in qualsiasi elemento figlio. Utilizzare con parsimonia l'ereditarietà dei valori delle proprietà poiché comporta un costo in termini di prestazioni. Per ulteriori informazioni, vedere l'ereditarietà del valore della proprietà .

  • Impostare il flag Journal per indicare che la proprietà di dipendenza deve essere rilevata o usata dai servizi di inserimento nel journal di navigazione. Ad esempio, la proprietà SelectedIndex imposta il flag Journal per consigliare alle applicazioni di mantenere una cronologia di journal degli elementi selezionati.

Proprietà di dipendenza di sola lettura

È possibile definire una proprietà di dipendenza di sola lettura. Uno scenario tipico è una proprietà di dipendenza che archivia lo stato interno. Ad esempio, IsMouseOver è di sola lettura perché il relativo stato deve essere determinato solo dall'input del mouse. Per altre informazioni, vedere proprietà di dipendenza di sola lettura.

Proprietà di dipendenza di tipo raccolta

Le proprietà di dipendenza di tipo raccolta presentano problemi di implementazione aggiuntivi da considerare, ad esempio l'impostazione di un valore predefinito per i tipi di riferimento e il supporto del data binding per gli elementi della raccolta. Per altre informazioni, vedere proprietà di dipendenza di tipo raccolta.

Sicurezza delle proprietà di dipendenza

In genere, dichiari le proprietà di dipendenza come proprietà pubbliche, mentre i campi identificatore DependencyProperty come campi public static readonly. Se si specifica un livello di accesso più restrittivo, ad esempio protected, è comunque possibile accedere a una proprietà di dipendenza tramite il relativo identificatore in combinazione con le API del sistema di proprietà. Anche un campo identificatore protetto è potenzialmente accessibile tramite le API di reportistica dei metadati o determinazione dei valori di WPF, ad esempio LocalValueEnumerator. Per altre informazioni, vedere sicurezza delle proprietà di dipendenza.

Per le proprietà di dipendenza di sola lettura, il valore restituito da RegisterReadOnly è DependencyPropertyKeye, in genere, non si considera DependencyPropertyKey come un membro public della tua classe. Poiché il sistema di proprietà WPF non propaga il DependencyPropertyKey all'esterno del codice, una proprietà di dipendenza di sola lettura ha una migliore sicurezza set rispetto a una proprietà di dipendenza di lettura/scrittura.

Proprietà di dipendenza e costruttori di classi

Esiste un principio generale nella programmazione del codice gestito, spesso applicato dagli strumenti di analisi del codice, che i costruttori di classi non devono chiamare metodi virtuali. Questo perché i costruttori di base possono essere chiamati durante l'inizializzazione di un costruttore di classi derivate e un metodo virtuale chiamato da un costruttore di base può essere eseguito prima di completare l'inizializzazione della classe derivata. Quando si deriva da una classe che deriva già da DependencyObject, 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'override dei metodi consente alle classi derivate di partecipare alla determinazione del valore. Per evitare potenziali problemi con l'inizializzazione di runtime, non è consigliabile impostare i valori delle proprietà di dipendenza all'interno di costruttori di classi, a meno che non si segua un modello di costruttore specifico. Per altre informazioni, vedere Modelli di costruttore sicuri per DependencyObjects.

Vedere anche