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à.

Importante

La documentazione di Desktop Guide per .NET 7 e .NET 6 è in fase di costruzione.

Prerequisiti

L'articolo presuppone una conoscenza di base delle proprietà di dipendenza e che si 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 Register o RegisterReadOnly chiamate. Il Register metodo restituisce un'istanza DependencyProperty che contiene il nome registrato e le caratteristiche di una proprietà di dipendenza. Si assegnerà l'istanza 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 Background proprietà è 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 DependencyObject tipi. 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 Cenni preliminari sulle proprietà di dipendenza.

Wrapper delle proprietà di dipendenza

Le proprietà di dipendenza WPF non associate vengono esposte da un wrapper CLR che implementa get e set funzioni di accesso. Usando un wrapper di proprietà, i consumer di proprietà di dipendenza possono ottenere o impostare i valori delle proprietà di dipendenza, esattamente come qualsiasi altra proprietà CLR. Le get funzioni di accesso e set interagiscono con il sistema di proprietà sottostante tramite DependencyObject.GetValue e DependencyObject.SetValue chiamate, passando l'identificatore della proprietà di dipendenza come parametro. I consumer di proprietà di dipendenza in genere non chiamano GetValue o SetValue direttamente, ma se si implementa una proprietà di dipendenza personalizzata, questi metodi verranno usati 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 DependencyProperty identificatore. Il fatto che sia utile creare una proprietà di dipendenza dipende dallo scenario in uso. Anche se il supporto della proprietà con un campo privato è adeguato per alcuni scenari, è consigliabile implementare una proprietà di dipendenza se si vuole che la proprietà supporti una o più delle funzionalità WPF seguenti:

  • 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, vedi 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à dei valori delle proprietà.

  • Proprietà animabili. Per altre informazioni, vedere Cenni preliminari sull'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 proprietà può specificare un metodo di callback nei metadati delle proprietà che verranno richiamati ogni volta che il sistema di proprietà determina che il valore della proprietà è stato modificato. Un concetto correlato è la coercizione del valore di proprietà. Per altre informazioni, vedere Callback e convalida delle proprietà di dipendenza.

  • 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 ricomporre gli oggetti visivi del sistema di layout per un elemento.

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

  • Supporto della finestra di progettazione WPF di Visual Studio, ad esempio la modifica delle proprietà di un controllo personalizzato nella finestra Proprietà . Per altre informazioni, vedere Cenni preliminari 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 un override dei metadati sia pratico dipende dallo scenario e dal modo in cui lo scenario è simile all'implementazione di proprietà e classi di dipendenza WPF esistenti. Per altre informazioni sull'override dei metadati sulle proprietà di dipendenza esistenti, vedere Metadati delle proprietà di dipendenza.

Elenco 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 DependencyProperty identificatore come public static readonly campo 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 della proprietà di dipendenza. Nel wrapper CLR implementare get e set funzioni di accesso che si connettono alla proprietà di dipendenza che esegue il back-end del wrapper.

Registrazione della proprietà

Affinché la proprietà sia una proprietà di dipendenza, è necessario registrarla nel sistema di proprietà. Per registrare la proprietà, chiamare il Register metodo dall'interno del corpo della classe, ma all'esterno di qualsiasi definizione di membro. Il Register metodo 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 Register chiamata viene eseguita al di fuori delle definizioni dei membri è che si assegna il valore restituito a un public static readonly campo di tipo DependencyProperty. Questo campo, che verrà creato nella classe, è l'identificatore per la proprietà di dipendenza. Nell'esempio seguente, il primo argomento dei nomi della proprietà AquariumGraphicdi Register dipendenza .

// 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 potrebbe essere utile nel caso in cui siano 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, aggiungendo la classe come proprietario della proprietà di dipendenza. Per altre informazioni sull'aggiunta di una classe come proprietario, vedere 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 Register chiamata, ovvero il nome della proprietà di dipendenza. L'implementazione del wrapper chiamerà GetValue nella get funzione di accesso e SetValue nella set funzione di accesso (per le proprietà di lettura/scrittura). L'esempio seguente mostra un wrapper, seguendo la dichiarazione di campo di chiamata di registrazione e 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

Tranne in rari casi, l'implementazione del wrapper deve contenere GetValue solo codice e SetValue . Per i motivi di questo problema, vedere 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 progettisti 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 altre informazioni, vedi Proprietà di caricamento e dipendenza XAML.

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 consentono di specificare i metadati delle proprietà durante la Register 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 su una classe derivata da FrameworkElement, è possibile usare la classe FrameworkPropertyMetadata di metadati più specializzata anziché la relativa classe PropertyMetadatadi base . Diverse FrameworkPropertyMetadata firme del costruttore consentono di specificare diverse combinazioni di caratteristiche dei metadati. Se si vuole solo specificare un valore predefinito, usare FrameworkPropertyMetadata(Object) e passare il valore predefinito al Object parametro . Verificare che il tipo di valore corrisponda all'oggetto propertyType specificato nella Register chiamata.

Alcuni FrameworkPropertyMetadata overload consentono di specificare i flag di opzione dei metadati per la 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 della proprietà (o lo modifica) influisce sulla modalità di rendering di un elemento dell'interfaccia utente da parte del sistema di layout, impostare uno o più dei flag seguenti:

    • 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 Width proprietà .

    • 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 flag di metadati per una Alignment proprietà.

    • AffectsRender, che indica che si è verificata una modifica che non influisce sul layout e sulla misura, ma richiede comunque un altro rendering. Ad esempio, impostare questo flag per una Background proprietà 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 OnPropertyChanged callback 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 MinOrphanLines proprietà possono modificare il rendering complessivo di un documento di flusso. Usare AffectsParentArrange o AffectsParentMeasure per segnalare le azioni padre nelle proprie 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 la modalità di associazione dati predefinita per le proprietà di dipendenza è OneWay, è possibile modificare la modalità di associazione di un'associazione specifica in TwoWay. Per altre informazioni, vedere Direzione di associazione. In qualità di autore di proprietà di dipendenza, è anche possibile scegliere di impostare l'associazione bidirezionale come modalità predefinita. Un esempio di una proprietà di dipendenza esistente che usa il data binding bidirezionale è MenuItem.IsSubmenuOpen, che ha uno stato basato su altre proprietà e chiamate al 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à per la proprietà di dipendenza impostando il Inherits flag . 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à consente di specificare un contesto dati nella radice della pagina o dell'applicazione, che consente di risparmiare la necessità di specificarla per le associazioni di elementi figlio. Anche se un valore di proprietà ereditato esegue l'override del valore predefinito, i valori delle proprietà possono essere impostati localmente in qualsiasi elemento figlio. Usare l'ereditarietà del valore della proprietà con moderazione perché ha un costo delle prestazioni. Per altre informazioni, vedere Ereditarietà dei valori delle proprietà.

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

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, si dichiarano le proprietà di dipendenza come proprietà pubbliche e DependencyProperty i campi identificatore come public static readonly campi. 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 dell'identificatore protetto è potenzialmente accessibile tramite le API di creazione di metadati o determinazione dei valori 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 verrà creato DependencyPropertyKey un public membro della classe. Poiché il sistema di proprietà WPF non viene propagato all'esterno DependencyPropertyKey del codice, una proprietà di dipendenza di sola lettura ha una sicurezza migliore 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 internamente i metodi virtuali. 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 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 Cassaforte modelli di costruttore per DependencyObjects.

Vedi anche