Condividi tramite


Proprietà di dipendenza personalizzate

In questo articolo spieghiamo come definire e implementare le proprie proprietà di dipendenza per un'app di Windows Runtime scritta in C++, C# o Visual Basic. Vengono elencati i motivi per cui gli sviluppatori di app e gli autori di componenti potrebbero voler creare proprietà di dipendenza personalizzate. Vengono descritti i passaggi di implementazione per una proprietà di dipendenza personalizzata, nonché alcune procedure consigliate che possono migliorare le prestazioni, l'usabilità o la versatilità della proprietà di dipendenza.

Prerequisiti

Si assume che si sia letto Panoramica delle proprietà di dipendenza e che si comprendano le proprietà di dipendenza dal punto di vista di un utente di proprietà di dipendenza esistenti Per seguire gli esempi in questo argomento, è necessario avere familiarità con XAML ed essere in grado di scrivere un'app Windows Runtime di base usando C++, C# o Visual Basic.

Che cos'è una proprietà di dipendenza?

Per supportare stili, data binding, animazioni e valori predefiniti per una proprietà, è necessario implementarli come proprietà di dipendenza. I valori delle proprietà di dipendenza non vengono archiviati come campi nella classe , vengono archiviati dal framework xaml e fanno riferimento usando una chiave, che viene recuperata quando la proprietà viene registrata nel sistema di proprietà di Windows Runtime chiamando il metodo DependencyProperty.Register. Le proprietà di dipendenza possono essere usate solo dai tipi derivati da DependencyObject. Ma DependencyObject è tuttavia piuttosto elevato nella gerarchia di classi, quindi la maggior parte delle classi destinate all'interfaccia utente e al supporto della presentazione può supportare le proprietà di dipendenza. Per altre informazioni sulle proprietà di dipendenza e per la terminologia e le convenzioni usate per descriverle in questa documentazione, vedere Panoramica sulle proprietà di dipendenza.

Esempi di proprietà di dipendenza in Windows Runtime sono: Control.Background, FrameworkElement.Width e TextBox.Text, tra gli altri.

La convenzione è che ogni proprietà di dipendenza esposta da una classe dispone di una proprietà public static readonly corrispondente di tipo DependencyProperty esposta sulla stessa classe, che fornisce l'identificatore per la proprietà di dipendenza. Il nome dell'identificatore segue questa convenzione: il nome della proprietà di dipendenza, con la stringa "Property" aggiunta alla fine del nome. Ad esempio, l'identificatore DependencyProperty corrispondente per la proprietà Control.Background è Control.BackgroundProperty. L'identificatore archivia le informazioni sulla proprietà di dipendenza così come è stata registrata e può quindi essere utilizzato per altre operazioni che coinvolgono la proprietà di dipendenza, ad esempio la chiamata a SetValue.

Wrapper delle proprietà

Le proprietà di dipendenza in genere hanno un'implementazione wrapper. Senza il wrapper, l'unico modo per ottenere o impostare le proprietà consiste nell'usare i metodi dell'utilità delle proprietà di dipendenza GetValue e SetValue e per passarvi l'identificatore come parametro. Si tratta di un utilizzo piuttosto innaturale per qualcosa che è apparentemente una proprietà. Con il wrapper, tuttavia, il codice e qualsiasi altro codice che fa riferimento alla proprietà di dipendenza può usare una sintassi semplice di proprietà oggetto naturale per il linguaggio in uso.

Se si implementa manualmente una proprietà di dipendenza personalizzata e si vuole che sia pubblica e facile da chiamare, definire anche i wrapper delle proprietà. I wrapper delle proprietà sono utili anche per segnalare informazioni di base sulla proprietà di dipendenza ai processi di reflection o analisi statica. In particolare, il wrapper consente di inserire attributi come ContentPropertyAttribute.

Quando implementare una proprietà come proprietà di dipendenza

Ogni volta che si implementa una proprietà di lettura/scrittura pubblica in una classe, purché la classe derivi da DependencyObject, è possibile fare in modo che la proprietà funzioni come proprietà di dipendenza. Talvolta è opportuno usare la tecnica normale che consiste nel supportare la proprietà con un campo privato. La definizione della proprietà personalizzata come proprietà di dipendenza non è sempre necessaria o appropriata. La scelta dipenderà dagli scenari che si intende supportare per la proprietà.

Si potrebbe prendere in considerazione l'implementazione della proprietà come proprietà di dipendenza quando si vuole che supporti una o più di queste funzionalità di Windows Runtime o delle app di Windows Runtime:

  • Impostazione della proprietà tramite uno stile
  • Funge da proprietà di destinazione valida per il data binding con {Binding}
  • Supporto di valori animati tramite uno Storyboard
  • Segnalazione quando il valore della proprietà è stato modificato da:
    • Azioni eseguite dal sistema di proprietà stesso
    • Ambiente
    • Azioni utente
    • Stili di lettura e scrittura

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

La definizione di una proprietà di dipendenza può essere considerata come un set di concetti. Questi concetti non sono necessariamente passaggi procedurali, perché diversi concetti possono essere affrontati in una singola riga di codice nell'implementazione. Questo elenco offre solo una rapida panoramica. Ogni concetto verrà illustrato in modo più dettagliato più avanti in questo argomento e verrà illustrato il codice di esempio in diversi linguaggi.

  • Registrare il nome della proprietà con il sistema di proprietà (richiamare Register), specificando un tipo di proprietario e il tipo del valore di proprietà.
    • Esiste un parametro obbligatorio per Register che prevede i metadati delle proprietà. Specificare null per questa proprietà o se si desidera un comportamento modificato dalla proprietà o un valore predefinito basato su metadati che può essere ripristinato chiamando ClearValue, specificare un'istanza di PropertyMetadata.
  • Definire un identificatore DependencyProperty come membro di proprietà readonly statico public nel tipo proprietario.
  • Definire una proprietà wrapper, seguendo il modello di funzione di accesso alle proprietà usato nel linguaggio che si sta implementando. Il nome della proprietà wrapper deve corrispondere alla stringa nome usata in Register. Implementare le funzioni di accesso get e set per connettere il wrapper alla proprietà di dipendenza di cui esegue il wrapping, chiamando GetValue e SetValue e passando l'identificatore della proprietà come parametro.
  • (Facoltativo) Inserire attributi come ContentPropertyAttribute nel wrapper.

Nota

Se si definisce una proprietà associata personalizzata, in genere si omette il wrapper. Si scrive invece uno stile di funzione di accesso diverso che un processore XAML può usare. Vedere Proprietà associate personalizzate 

Registrazione della proprietà

Affinché la proprietà sia una proprietà di dipendenza, si deve registrare la proprietà in un archivio proprietà gestito dal sistema di proprietà di Windows Runtime. Per registrare la proprietà, chiamare il metodo Register.

Per i linguaggi Microsoft .NET (C# e Microsoft Visual Basic) si chiama Register all'interno del corpo della classe (all'interno della classe, ma all'esterno di qualsiasi definizione di membro). L'identificatore viene fornito dalla chiamata al metodo Register, come valore restituito. La chiamata Register viene in genere eseguita come costruttore statico o come parte dell'inizializzazione di una proprietà statica pubblica di sola lettura di tipo DependencyProperty come parte della classe. Questa proprietà espone l'identificatore per la proprietà di dipendenza. Ecco alcuni esempi della chiamata Register.

Nota

La registrazione della proprietà di dipendenza come parte della definizione della proprietà dell'identificatore è l'implementazione tipica, ma è anche possibile registrare una proprietà di dipendenza nel costruttore statico della classe. Questo approccio può essere utile nel caso in cui siano necessarie più righe di codice per inizializzare la proprietà di dipendenza.

Per C++/CX sono disponibili opzioni per la suddivisione dell'implementazione tra l'intestazione e il file di codice. La suddivisione tipica consiste nel dichiarare l'identificatore stesso come proprietà statica pubblica nell'intestazione, con un'implementazione get ma senza set. L'implementazione get fa riferimento a un campo privato, che è un'istanza DependencyProperty non inizializzata. È anche possibile dichiarare i wrapper e le implementazioni get e set del wrapper. In questo caso l'intestazione include un'implementazione minima. Se il wrapper richiede l'attribuzione di Windows Runtime, anche l'attributo nell'intestazione. Inserire la chiamata Register nel file di codice, all'interno di una funzione helper che viene eseguita solo quando l'app inizializza la prima volta. Usare il valore restituito di Register per riempire gli identificatori statici ma non inizializzati dichiarati nell'intestazione, inizialmente impostato su nullptr nell'ambito radice del file di implementazione.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);
Public Shared ReadOnly LabelProperty As DependencyProperty = 
    DependencyProperty.Register("Label", 
      GetType(String), 
      GetType(ImageWithLabelControl), 
      New PropertyMetadata(Nothing))
// ImageWithLabelControl.idl
namespace ImageWithLabelControlApp
{
    runtimeclass ImageWithLabelControl : Windows.UI.Xaml.Controls.Control
    {
        ImageWithLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

// ImageWithLabelControl.h
...
struct ImageWithLabelControl : ImageWithLabelControlT<ImageWithLabelControl>
{
...
public:
    static Windows::UI::Xaml::DependencyProperty LabelProperty()
    {
        return m_labelProperty;
    }

private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
...
};

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
...
//.h file
//using namespace Windows::UI::Xaml::Controls;
//using namespace Windows::UI::Xaml::Interop;
//using namespace Windows::UI::Xaml;
//using namespace Platform;

public ref class ImageWithLabelControl sealed : public Control
{
private:
    static DependencyProperty^ _LabelProperty;
...
public:
    static void RegisterDependencyProperties();
    static property DependencyProperty^ LabelProperty
    {
        DependencyProperty^ get() {return _LabelProperty;}
    }
...
};

//.cpp file
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml.Interop;

DependencyProperty^ ImageWithLabelControl::_LabelProperty = nullptr;

// This function is called from the App constructor in App.xaml.cpp
// to register the properties
void ImageWithLabelControl::RegisterDependencyProperties()
{ 
    if (_LabelProperty == nullptr)
    { 
        _LabelProperty = DependencyProperty::Register(
          "Label", Platform::String::typeid, ImageWithLabelControl::typeid, nullptr);
    } 
}

Nota

Per il codice C++/CX, il motivo per cui si dispone di un campo privato e di una proprietà di sola lettura pubblica che espone DependencyProperty è in modo che altri chiamanti che usano la proprietà di dipendenza possano usare anche API dell'utilità di sistema delle proprietà che richiedono che l'identificatore sia pubblico. Se si mantiene l'identificatore privato, gli utenti non possono usare queste API di utilità. Esempi di tali API e scenari includono GetValue o SetValue selezionando, ClearValue, GetAnimationBaseValue, SetBinding e Setter.Property. Non è possibile usare un campo pubblico perché le regole dei metadati di Windows Runtime non consentono campi pubblici.

Convenzioni di denominazione delle proprietà di dipendenza

Esistono convenzioni di denominazione per le proprietà di dipendenza. seguirle in tutte le circostanze, tranne che in circostanze eccezionali. La proprietà di dipendenza ha un nome di base ("Label" nell'esempio precedente) assegnato come primo parametro di Register. Il nome deve essere univoco all'interno di ogni tipo di registrazione e il requisito di univocità si applica anche a tutti i membri ereditati. 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.

Avviso

Anche se il nome specificato qui può essere qualsiasi identificatore di stringa valido nella programmazione per il linguaggio preferito, in genere vuoi essere in grado di impostare anche la proprietà di dipendenza in XAML. Per essere impostato in XAML, il nome della proprietà scelto deve essere un nome XAML valido. Per maggiori informazioni, vedere Panoramica di XAML.

Quando si crea la proprietà dell'identificatore, combinare il nome della proprietà durante la registrazione con il suffisso "Property" ("LabelProperty", ad esempio). Questa proprietà è l'identificatore per la proprietà di dipendenza e viene usata come input per le chiamate SetValue e GetValue effettuate nei wrapper delle proprietà. Viene usato anche dal sistema di proprietà e da altri processori XAML, ad esempio {x:Bind}

Implementazione del wrapper

Il wrapper della proprietà deve chiamare GetValue nell'implementazione get e SetValue nell'implementazione set.

Avviso

In tutte le circostanze eccezionali, le implementazioni del wrapper devono eseguire solo le operazioni GetValue e SetValue. In caso contrario, si otterrà un comportamento diverso quando la proprietà viene impostata tramite XAML rispetto a quando viene impostata tramite codice. Per un'efficienza, il parser XAML ignora i wrapper quando si impostano le proprietà di dipendenza; e comunica con il negozio di backup tramite SetValue.

public String Label
{
    get { return (String)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}
Public Property Label() As String
    Get
        Return DirectCast(GetValue(LabelProperty), String) 
    End Get 
    Set(ByVal value As String)
        SetValue(LabelProperty, value)
    End Set
End Property
// ImageWithLabelControl.h
...
winrt::hstring Label()
{
    return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}

void Label(winrt::hstring const& value)
{
    SetValue(m_labelProperty, winrt::box_value(value));
}
...
//using namespace Platform;
public:
...
  property String^ Label
  {
    String^ get() {
      return (String^)GetValue(LabelProperty);
    }
    void set(String^ value) {
      SetValue(LabelProperty, value);
    }
  }

Metadati della proprietà per una proprietà di dipendenza personalizzata

Quando i metadati della proprietà vengono assegnati a una proprietà di dipendenza, gli stessi metadati vengono applicati a tale proprietà per ogni istanza del tipo di proprietario della proprietà o delle relative sottoclassi. Nei metadati delle proprietà è possibile specificare due comportamenti:

  • Valore predefinito assegnato dal sistema di proprietà a tutti i case della proprietà.
  • Metodo di callback statico richiamato automaticamente all'interno del sistema di proprietà ogni volta che viene rilevata una modifica del valore della proprietà.

Chiamata di Register con i metadati delle proprietà

Negli esempi precedenti di chiamata a DependencyProperty.Register è stato passato un valore Null per il parametro propertyMetadata. Per abilitare una proprietà di dipendenza per fornire un valore predefinito o usare un callback con modifica della proprietà, è necessario definire un'istanza PropertyMetadata che fornisce una o entrambe queste funzionalità.

In genere si specifica PropertyMetadata come istanza creata inline, all'interno dei parametri per DependencyProperty.Register.

Nota

Se si definisce un'implementazione CreateDefaultValueCallback è necessario utilizzare il metodo PropertyMetadata.Create anziché chiamare un costruttore PropertyMetadata per definire l'istanza PropertyMetadata.

Nell'esempio seguente vengono modificati gli esempi di DependencyProperty.Register facendo riferimento all'istanza PropertyMetadata con un valore PropertyChangedCallback. L'implementazione del callback "OnLabelChanged" verrà illustrata più avanti in questa sezione.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null,new PropertyChangedCallback(OnLabelChanged))
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(
        Nothing, new PropertyChangedCallback(AddressOf OnLabelChanged)))
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr, Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
DependencyProperty^ ImageWithLabelControl::_LabelProperty =
    DependencyProperty::Register("Label",
    Platform::String::typeid,
    ImageWithLabelControl::typeid,
    ref new PropertyMetadata(nullptr,
      ref new PropertyChangedCallback(&ImageWithLabelControl::OnLabelChanged))
    );

Valore predefinito

È possibile specificare un valore predefinito per una proprietà di dipendenza in modo che la proprietà restituisca sempre un valore predefinito specifico quando non è impostata. Questo valore può essere diverso dal valore predefinito intrinseco per il tipo di tale proprietà.

Se non viene specificato un valore predefinito, il valore predefinito per una proprietà di dipendenza è Null per un tipo riferimento o l'impostazione predefinita del tipo per un tipo di valore o una primitiva della lingua, ad esempio 0 per un numero intero o una stringa vuota per una stringa. Il motivo principale per stabilire un valore predefinito è che questo valore viene ripristinato quando si chiama ClearValue sulla proprietà. La definizione di un valore predefinito per ogni proprietà potrebbe risultare più utile rispetto alla definizione dei valori predefiniti nei costruttori, in particolare per i tipi valore. Tuttavia, per i tipi di riferimento, assicurarsi che la definizione di un valore predefinito non crei un modello singleton non intenzionale. Per ulteriori informazioni, vedere Best practices più avanti in questa sezione.

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...

Nota

Non eseguire la registrazione con un valore predefinito UnsetValue. In tal caso, confonderà i consumer di proprietà e avrà conseguenze impreviste all'interno del sistema di proprietà.

CreateDefaultValueCallback

In alcuni scenari si definiscono le proprietà di dipendenza per gli oggetti usati in più thread dell'interfaccia utente. Questo può essere il caso se si definisce un oggetto dati usato da più app o un controllo usato in più app. È possibile abilitare lo scambio dell'oggetto tra thread dell'interfaccia utente diversi fornendo un'implementazione CreateDefaultValueCallback anziché un'istanza di valore predefinita, collegata al thread che ha registrato la proprietà. Fondamentalmente un CreateDefaultValueCallback definisce una factory per i valori predefiniti. Il valore restituito da CreateDefaultValueCallback è sempre associato al thread CreateDefaultValueCallback dell'interfaccia utente corrente che usa l'oggetto.

Per definire i metadati che specificano un oggetto CreateDefaultValueCallback, è necessario chiamare PropertyMetadata.Create per restituire un'istanza di metadati; i costruttori PropertyMetadata non hanno una firma che include un parametro CreateDefaultValueCallback.

Il modello di implementazione tipico per un oggetto CreateDefaultValueCallback consiste nel creare una nuova classe DependencyObject, impostare il valore della proprietà specifico di ogni proprietà di DependencyObject sul valore predefinito previsto e quindi restituire la nuova classe come riferimento Object tramite il valore restituito del metodo CreateDefaultValueCallback.

Metodo di callback modificato dalla proprietà

È possibile definire un metodo di callback modificato dalla proprietà per definire le interazioni della proprietà con altre proprietà di dipendenza o per aggiornare una proprietà o uno stato interno dell'oggetto ogni volta che la proprietà cambia. Se viene richiamato il callback, il sistema di proprietà ha determinato che è presente una modifica effettiva del valore della proprietà. Poiché il metodo di callback è statico, il parametro d del callback è importante perché indica quale istanza della classe ha segnalato una modifica. Un'implementazione tipica usa la proprietà NewValue ei dati dell'evento ed elabora tale valore in qualche modo, in genere eseguendo altre modifiche sull'oggetto passato come d. Le risposte aggiuntive a una modifica della proprietà riguardano il rifiuto del valore segnalato da NewValue, il ripristino di OldValue, o l'impostazione del valore su un vincolo programmatico applicato a NewValue.

Questo esempio seguente illustra un'implementazione di PropertyChangedCallback. Implementa il metodo a cui si è fatto riferimento negli esempi Register precedenti, come parte degli argomenti di costruzione per PropertyMetadata. Lo scenario risolto da questo callback è che la classe ha anche una proprietà di sola lettura calcolata denominata "HasLabelValue" (implementazione non visualizzata). Ogni volta che viene rivalutata la proprietà "Label", questo metodo di callback viene richiamato e il callback consente al valore calcolato dipendente di rimanere sincronizzato con le modifiche apportate alla proprietà di dipendenza.

private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    ImageWithLabelControl iwlc = d as ImageWithLabelControl; //null checks omitted
    String s = e.NewValue as String; //null checks omitted
    if (s == String.Empty)
    {
        iwlc.HasLabelValue = false;
    } else {
        iwlc.HasLabelValue = true;
    }
}
    Private Shared Sub OnLabelChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim iwlc As ImageWithLabelControl = CType(d, ImageWithLabelControl) ' null checks omitted
        Dim s As String = CType(e.NewValue,String) ' null checks omitted
        If s Is String.Empty Then
            iwlc.HasLabelValue = False
        Else
            iwlc.HasLabelValue = True
        End If
    End Sub
void ImageWithLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto iwlc{ d.as<ImageWithLabelControlApp::ImageWithLabelControl>() };
    auto s{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
    iwlc.HasLabelValue(s.size() != 0);
}
static void OnLabelChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    ImageWithLabelControl^ iwlc = (ImageWithLabelControl^)d;
    Platform::String^ s = (Platform::String^)(e->NewValue);
    if (s->IsEmpty()) {
        iwlc->HasLabelValue=false;
    }
}

Comportamento modificato delle proprietà per strutture ed enumerazioni

Se il tipo di DependencyProperty è un'enumerazione o una struttura, il callback può essere richiamato anche se i valori interni della struttura o il valore di enumerazione non è stato modificato. Ciò è diverso da una primitiva di sistema, ad esempio una stringa in cui viene richiamata solo se il valore è stato modificato. Si tratta di un effetto collaterale delle operazioni box e unbox su questi valori eseguiti internamente. Se si dispone di un metodo PropertyChangedCallback per una proprietà in cui il valore è un'enumerazione o una struttura, è necessario confrontare OldValue e NewValue eseguendo il cast dei valori manualmente e usando gli operatori di confronto di overload disponibili per i valori di cast. In alternativa, se non è disponibile alcun operatore di questo tipo (che potrebbe essere il caso di una struttura personalizzata), potrebbe essere necessario confrontare i singoli valori. In genere si sceglie di non eseguire alcuna operazione se il risultato è che i valori non sono stati modificati.

private static void OnVisibilityValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    if ((Visibility)e.NewValue != (Visibility)e.OldValue)
    {
        //value really changed, invoke your changed logic here
    } // else this was invoked because of boxing, do nothing
}
Private Shared Sub OnVisibilityValueChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    If CType(e.NewValue,Visibility) != CType(e.OldValue,Visibility) Then
        '  value really changed, invoke your changed logic here
    End If
    '  else this was invoked because of boxing, do nothing
End Sub
static void OnVisibilityValueChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto oldVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.OldValue()) };
    auto newVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.NewValue()) };

    if (newVisibility != oldVisibility)
    {
        // The value really changed; invoke your property-changed logic here.
    }
    // Otherwise, OnVisibilityValueChanged was invoked because of boxing; do nothing.
}
static void OnVisibilityValueChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    if ((Visibility)e->NewValue != (Visibility)e->OldValue)
    {
        //value really changed, invoke your changed logic here
    } 
    // else this was invoked because of boxing, do nothing
    }
}

Procedure consigliate

Tenere presenti le considerazioni seguenti come procedure consigliate quando si definisce la proprietà di dipendenza personalizzata.

DependencyObject e threading

Tutte le istanze 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, chiamando Dispatcher.

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.

Evitare singleton non intenzionali

Un singleton non intenzionale può verificarsi se si dichiara una proprietà di dipendenza che accetta un tipo riferimento e si chiama un costruttore per tale tipo di riferimento come parte del codice che stabilisce PropertyMetadata. Ciò che accade è che tutti gli utilizzi della proprietà di dipendenza condividono una sola istanza di PropertyMetadata e quindi tentano di condividere il singolo tipo di riferimento costruito. Tutte le sottoproprietà del tipo di valore impostato tramite la proprietà di dipendenza vengono propagate ad altri oggetti in modi che potrebbero non essere previsti.

È possibile usare i costruttori di classi per impostare i valori iniziali per una proprietà di dipendenza di tipo riferimento se si desidera un valore non Null, ma tenere presente che questo valore verrebbe considerato un valore locale ai fini della panoramica delle proprietà di dipendenza. Potrebbe essere più appropriato usare un modello a questo scopo, se la classe supporta i modelli. Un altro modo per evitare un modello singleton, ma fornire comunque un valore predefinito utile, consiste nell'esporre una proprietà statica sul tipo di riferimento che fornisce un valore predefinito appropriato per i valori di tale classe.

Proprietà di dipendenza di tipo raccolta

Le proprietà di dipendenza di tipo di raccolta presentano alcuni problemi di implementazione aggiuntivi che è opportuno considerare.

Le proprietà di dipendenza di tipo raccolta sono relativamente rare nell'API di Windows Runtime. Nella maggior parte dei casi, è possibile usare raccolte in cui gli elementi sono una sottoclasse DependencyObject, ma la proprietà della raccolta stessa viene implementata come una proprietà CLR o C++ convenzionale. Ciò è dovuto al fatto che le raccolte non soddisfano necessariamente alcuni scenari tipici in cui sono coinvolte le proprietà di dipendenza. Ad esempio:

  • In genere non si anima una raccolta.
  • In genere non si prepopolano gli elementi in una raccolta con stili o un modello.
  • Anche se l'associazione alle raccolte è uno scenario principale, non è necessario che una raccolta sia una proprietà di dipendenza come origine di associazione. Per le destinazioni di associazione, è più tipico usare sottoclassi di ItemsControl o DataTemplate per supportare gli elementi della raccolta o per usare modelli di modello di visualizzazione. Per altre informazioni sull'associazione da e verso le raccolte, vedere Informazioni approfondite sul data binding.
  • Le notifiche per le modifiche alla raccolta vengono gestite meglio tramite interfacce come INotifyPropertyChanged o INotifyCollectionChanged, o derivando il tipo di raccolta da ObservableCollection<T>.

Esistono tuttavia scenari per le proprietà di dipendenza di tipo raccolta. Le tre sezioni successive forniscono alcune indicazioni su come implementare una proprietà di dipendenza di tipo raccolta.

Inizializzazione della raccolta

Quando si crea una proprietà di dipendenza, è possibile stabilire un valore predefinito tramite i metadati delle proprietà di dipendenza. Prestare tuttavia attenzione a non usare una raccolta statica singleton come valore predefinito. Al contrario, è necessario impostare intenzionalmente il valore dell'insieme su un insieme univoco (istanza) come parte della logica del costruttore di classe per la classe owner della proprietà dell'insieme.

// WARNING - DO NOT DO THIS
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(new List<object>())
);

// DO THIS Instead
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);

public ImageWithLabelControl()
{
    // Need to initialize in constructor instead
    Items = new List<object>();
}

DependencyProperty e il valore predefinito di PropertyMetadata fanno parte della definizione statica di DependencyProperty. Fornendo un valore di raccolta predefinito (o un altro valore di istanza) come valore predefinito, verrà condiviso in tutte le istanze della classe anziché in ogni classe con una propria raccolta, come in genere si desidera.

Notifiche relative a modifiche

La definizione della raccolta come proprietà di dipendenza non fornisce automaticamente notifiche di modifica per gli elementi della raccolta in virtù del sistema di proprietà che richiama il metodo di callback "PropertyChanged". Se si vogliono ricevere notifiche per raccolte o elementi di raccolta, ad esempio per uno scenario di data binding, implementare l'interfaccia INotifyPropertyChanged o INotifyCollectionChanged . Per altre informazioni, vedere Informazioni approfondite sul data binding.

Considerazioni sulla sicurezza delle proprietà di dipendenza

Dichiarare le proprietà di dipendenza come proprietà pubbliche. Dichiarare gli identificatori di proprietà di dipendenza come membri statici di sola lettura pubblici. Anche se si tenta di dichiarare altri livelli di accesso permessi da una lingua (ad esempio protetto), è sempre possibile accedere a una proprietà di dipendenza tramite l'identificatore in combinazione con le API del sistema di proprietà. La dichiarazione dell'identificatore della proprietà di dipendenza come interna o privata non funzionerà perché il sistema di proprietà non può funzionare correttamente.

Le proprietà wrapper sono davvero utili, i meccanismi di sicurezza applicati ai wrapper possono essere ignorati chiamando invece GetValue o SetValue. Mantenere quindi pubbliche le proprietà del wrapper; in caso contrario, rendi la tua proprietà più difficile per i chiamanti legittimi da usare senza fornire alcun vantaggio di sicurezza reale.

Windows Runtime non consente di registrare una proprietà di dipendenza personalizzata come di sola lettura.

Proprietà di dipendenza e costruttori di classi

Esiste un principio generale che i costruttori di classi non devono chiamare metodi virtuali. Il motivo sta nel fatto che i costruttori possono essere chiamati per realizzare inizializzazione di base di un costruttore di classe derivato, pertanto l'uso del metodo virtuale tramite il costruttore potrebbe avvenire quando l'istanza di oggetto in corso di creazione non è ancora inizializzato completamente. Quando si deriva da una qualsiasi classe che deriva già da DependencyObject, ricordare che il sistema di proprietà stesso chiama ed espone internamente metodi virtuali come parte de suoi servizi. Per evitare potenziali problemi con l'inizializzazione in fase di esecuzione, non impostare i valori delle proprietà di dipendenza all'interno di costruttori di classi.

Registrazione delle proprietà di dipendenza per le app C++/CX

L'implementazione per la registrazione di una proprietà in C++/CX è più complessa rispetto a C#, sia a causa della separazione in un file di intestazione che di implementazione e anche perché l'inizializzazione nell'ambito radice del file di implementazione è una procedura non valida. (Le estensioni del componente Visual C++ (C++/CX) inserisce il codice dell'inizializzatore statico direttamente nell'ambito radice DllMain, mentre i compilatori C# assegnano gli inizializzatori statici alle classi e quindi evitano problemi di blocco del carico DllMain). La procedura consigliata consiste nel dichiarare una funzione helper che esegue tutte le registrazioni delle proprietà di dipendenza per una classe, una funzione per classe. Quindi, per ogni classe personalizzata usata dall'app, dovrai fare riferimento alla funzione di registrazione helper esposta da ogni classe personalizzata che vuoi usare. Chiamare ogni funzione di registrazione helper una volta come parte del Costruttore Application (App::App()), prima di InitializeComponent. Questo costruttore viene eseguito solo quando viene effettivamente fatto riferimento all'app per la prima volta, non verrà eseguito di nuovo se un'app sospesa riprende, ad esempio. Inoltre, come illustrato nell'esempio di registrazione C++ precedente, il controllo nullptr intorno a ogni chiamata Register è importante: è importante che nessun chiamante della funzione possa registrare la proprietà due volte. Una seconda chiamata di registrazione probabilmente arresta in modo anomalo l'app senza un controllo di questo tipo perché il nome della proprietà sarebbe un duplicato. È possibile visualizzare questo modello di implementazione nell'esempio di controlli XAML utente e personalizzati se si esamina il codice per la versione C++/CX dell'esempio.