Dela via


Anpassade beroendeegenskaper

Här förklarar vi hur du definierar och implementerar dina egna beroendeegenskaper för en Windows Runtime-app med C++, C#eller Visual Basic. Vi listar orsaker till varför apputvecklare och komponentförfattare kanske vill skapa anpassade beroendeegenskaper. Vi beskriver implementeringsstegen för en anpassad beroendeegenskap samt några metodtips som kan förbättra prestanda, användbarhet eller mångsidighet för beroendeegenskapen.

Förutsättningar

Vi förutsätter att du har läst översikten över beroendeegenskaper och att du förstår beroendeegenskaper utifrån en konsument av befintliga beroendeegenskaper. Om du vill följa exemplen i det här avsnittet bör du också förstå XAML och veta hur du skriver en grundläggande Windows Runtime-app med C++, C#eller Visual Basic.

Vad är en beroendeegenskap?

För att stödja formatering, databindning, animeringar och standardvärden för en egenskap bör den implementeras som en beroendeegenskap. Värden för beroendeegenskap lagras inte som fält i klassen, de lagras av xaml-ramverket och refereras med hjälp av en nyckel som hämtas när egenskapen registreras med Windows Runtime-egenskapssystemet genom att anropa metoden DependencyProperty.Register . Beroendeegenskaper kan endast användas av typer som härleds från DependencyObject. Men DependencyObject är ganska högt i klasshierarkin, så de flesta klasser som är avsedda för användargränssnitt och presentationsstöd kan ha stöd för beroendeegenskaper. Mer information om beroendeegenskaper och några av de terminologi och konventioner som används för att beskriva dem i den här dokumentationen finns i Översikt över beroendeegenskaper.

Exempel på beroendeegenskaper i Windows Runtime är: Control.Background, FrameworkElement.Width och TextBox.Text, bland många andra.

Konventionen är att varje beroendeegenskap som exponeras av en klass har en motsvarande public static readonly egenskap av typen DependencyProperty som exponeras i samma klass och tillhandahåller identifieraren för beroendeegenskapen. Identifierarens namn följer den här konventionen: namnet på beroendeegenskapen, med strängen "Egenskap" som läggs till i slutet av namnet. Motsvarande DependencyProperty-identifierare för egenskapen Control.Background är till exempel Control.BackgroundProperty. Identifieraren lagrar informationen om beroendeegenskapen när den registrerades och kan sedan användas för andra åtgärder som involverar beroendeegenskapen, till exempel att anropa SetValue.

Egenskapsomslutare

Beroendeegenskaper har vanligtvis en omslutningsimplementering. Utan omslutningen skulle det enda sättet att hämta eller ange egenskaperna vara att använda metoderna getValue och SetValue för beroendeegenskap och skicka identifieraren till dem som en parameter. Detta är en ganska onaturlig användning för något som till synes är en egenskap. Men med omslutningen kan koden och annan kod som refererar till beroendeegenskapen använda en enkel syntax för objektegenskap som är naturlig för det språk du använder.

Om du implementerar en anpassad beroendeegenskap själv, och vill att den ska vara offentlig och lätt att anropa, definierar du egenskapsomslutningarna också. Egenskapsomslutningarna är också användbara för att rapportera grundläggande information om beroendeegenskapen till reflektions- eller statiska analysprocesser. Mer specifikt är omslutningen där du placerar attribut som ContentPropertyAttribute.

När du ska implementera en egenskap som en beroendeegenskap

När du implementerar en offentlig läs-/skrivegenskap i en klass, så länge klassen härleds från DependencyObject, har du möjlighet att få din egenskap att fungera som en beroendeegenskap. Ibland är den typiska tekniken för att backa upp din egendom med ett privat fält tillräcklig. Det är inte alltid nödvändigt eller lämpligt att definiera din anpassade egenskap som en beroendeegenskap. Valet beror på de scenarier som du har för avsikt att stödja din tillgångar.

Du kan överväga att implementera din egenskap som en beroendeegenskap när du vill att den ska stödja en eller flera av dessa funktioner i Windows Runtime eller Windows Runtime-appar:

  • Ange egenskapen genom en Style
  • Fungerar som en giltig målobjektegenskap för databindning med {Binding}
  • Stöd för animerade värden via en Storyboard
  • Rapportering när värdet för egenskapen har ändrats av:
    • Åtgärder som vidtas av själva egenskapssystemet
    • Miljön
    • Användaråtgärder
    • Läs- och skrivformat

Checklista för att definiera en beroendeegenskap

Att definiera en beroendeegenskap kan ses som en uppsättning begrepp. Dessa begrepp är inte nödvändigtvis processuella steg, eftersom flera begrepp kan hanteras i en enda kodrad i implementeringen. Den här listan ger bara en snabb översikt. Vi förklarar varje begrepp mer detaljerat senare i det här avsnittet, och vi visar exempelkod på flera språk.

  • Registrera egenskapsnamnet med egenskapssystemet (anropa Register), ange en ägartyp och typen av egenskapsvärde.
    • Det finns en obligatorisk parameter för Register som förväntar sig egenskapsmetadata. Ange null för detta, eller om du vill ändra egenskapsbeteende, eller ett metadatabaserat standardvärde som kan återställas genom att anropa ClearValue, anger du en instans av PropertyMetadata.
  • Definiera en DependencyProperty-identifierare som ett public static readonly egenskapsmedlem på ägartypen.
  • Definiera en wrapper-egenskap som följer egenskapsåtkomstmodellen som används i språket du implementerar. Namnet på omslutningsegenskapen ska matcha namnsträngen som du använde i Registrera. Implementera get - och set-åtkomsterna för att ansluta omslutningen till den beroendeegenskap som den omsluter genom att anropa GetValue och SetValue och skicka din egen egenskaps identifierare som en parameter.
  • (Valfritt) Placera attribut som ContentPropertyAttribute på omslutningen.

Anmärkning

Om du definierar en anpassad bifogad egenskap utelämnar du vanligtvis omslutningen. I stället skriver du en annan typ av accessor som en XAML-processor kan använda. Se Anpassade kopplade egenskaper.

Registrera egenskapen

För att din egenskap ska vara en beroendeegenskap måste du registrera egenskapen i ett egenskapsarkiv som underhålls av Windows Runtime-egenskapssystemet. Om du vill registrera egenskapen anropar du metoden Register .

För Microsoft .NET-språk (C# och Microsoft Visual Basic) anropar du Registrera i brödtexten i klassen (i klassen, men utanför eventuella medlemsdefinitioner). Identifieraren tillhandahålls av metodanropet Register som returvärde. Registeranropet görs vanligtvis som ett statiskt konstruktionsanrop eller som en del av initialiseringen av en publik statisk readonly egenskap av typen DependencyProperty i din klass. Den här egenskapen visar identifieraren för din beroendeegenskap. Här är exempel på användning av registeranropet.

Anmärkning

Att registrera beroendeegenskapen som en del av identifieraregenskapsdefinitionen är den typiska implementeringen, men du kan också registrera en beroendeegenskap i klassens statiska konstruktor. Den här metoden kan vara meningsfull om du behöver mer än en kodrad för att initiera beroendeegenskapen.

För C++/CX har du alternativ för hur du delar implementeringen mellan huvudet och kodfilen. Den vanliga uppdelningen är att deklarera själva identifieraren som offentlig statisk egenskap i rubriken, med en get-implementering men ingen uppsättning. Get-implementeringen refererar till ett privat fält, som är en oinitialiserad DependencyProperty-instans. Du kan också deklarera wrappers och get och set implementationer av wrappern. I det här fallet innehåller rubriken endast en minimal implementering. Om omslutningen behöver tillskrivas Windows Runtime, ange detta i huvud också. Placera anropet Registrera i kodfilen i en hjälpfunktion som bara körs när appen initieras första gången. Använd returvärdet av Register för att fylla i de statiska men oinitierade identifierare som du deklarerade i headerfilen, som du ursprungligen angav som nullptr i den globala omfattningen för implementeringsfilen.

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

Anmärkning

För C++/CX-koden är anledningen till att du har ett privat fält och en offentlig skrivskyddad egenskap som visar DependencyProperty så att andra anropare som använder din beroendeegenskap också kan använda API:er för egenskapssystem som kräver att identifieraren är offentlig. Om du håller identifieraren privat kan personer inte använda dessa verktygs-API:er. Exempel på sådana API:er och scenarier är GetValue eller SetValue efter val, ClearValue, GetAnimationBaseValue, SetBinding och Setter.Property. Du kan inte använda ett offentligt fält för detta eftersom Windows Runtime-metadataregler inte tillåter offentliga fält.

Namnkonventioner för beroendeegenskaper

Det finns namngivningskonventioner för beroendeegenskaper. Följ dem under alla förutom exceptionella omständigheter. Själva beroendeegenskapen har ett enkelt namn ("Etikett" i föregående exempel) som anges som den första parametern i Register. Namnet måste vara unikt inom varje registreringstyp, och kravet på unikhet gäller även för alla ärvda medlemmar. Beroendeegenskaper som ärvs via bastyper anses redan vara en del av registreringstypen. namn på ärvda egenskaper kan inte registreras igen.

Varning

Även om det namn som du anger här kan vara valfri strängidentifierare som är giltig i programmering för valfritt språk, vill du vanligtvis kunna ange din beroendeegenskap i XAML också. För att anges i XAML måste egenskapsnamnet du väljer vara ett giltigt XAML-namn. Mer information finns i XAML-översikt.

När du skapar identifieraregenskapen kombinerar du namnet på egenskapen när du registrerade den med suffixet "Property" ("LabelProperty", till exempel). Den här egenskapen är din identifierare för beroendeegenskapen och används som indata för de SetValue - och GetValue-anrop som du gör i dina egna egenskapsomslutningar. Det används också av egenskapssystemet och andra XAML-processorer som {x:Bind}

Implementera omslutningen

Egenskapsomslutningen ska anropa GetValue i get-implementeringen och SetValue i set-implementeringen.

Varning

I undantagsfall bör omslutningsimplementeringarna endast utföra åtgärderna GetValue och SetValue . Annars får du olika beteende när din egenskap anges via XAML jämfört med när den anges via kod. För effektivitet kringgår XAML-parsern omslutningar när beroendeegenskaper anges; och kommunicerar med bakgrundslager via 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);
    }
  }

Egenskapsmetadata för en anpassad beroendeegenskap

När egenskapsmetadata tilldelas till en beroendeegenskap tillämpas samma metadata på den egenskapen för varje instans av egenskapens ägartyp eller dess underklasser. I egenskapsmetadata kan du ange två beteenden:

  • Ett standardvärde som egenskapssystemet tilldelar till alla fall av egenskapen.
  • En statisk återanropsmetod som automatiskt anropas i egenskapssystemet när en ändring av egenskapsvärdet identifieras.

Anropar register med egenskapsmetadata

I föregående exempel på att anropa DependencyProperty.Register skickade vi ett null-värde för parametern propertyMetadata . Om du vill aktivera en beroendeegenskap för att ange ett standardvärde eller använda ett egenskaps ändrat återanrop måste du definiera en PropertyMetadata-instans som tillhandahåller en eller båda av dessa funktioner.

Vanligtvis anger du en PropertyMetadata som en infogad instans inom parametrarna för DependencyProperty.Register.

Anmärkning

Om du definierar en CreateDefaultValueCallback-implementering måste du använda verktygsmetoden PropertyMetadata.Create i stället för att anropa en PropertyMetadata-konstruktor för att definiera PropertyMetadata-instansen .

I nästa exempel ändras tidigare visade DependencyProperty.Register-exempel genom att referera till en PropertyMetadata-instans med ett PropertyChangedCallback-värde . Implementeringen av återanropet "OnLabelChanged" visas senare i det här avsnittet.

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

Standardvärde

Du kan ange ett standardvärde för en beroendeegenskap så att egenskapen alltid returnerar ett visst standardvärde när den tas bort. Det här värdet kan skilja sig från det inbyggda standardvärdet för egenskapens typ.

Om ett standardvärde inte anges är standardvärdet för en beroendeegenskap null för en referenstyp eller standardvärdet för typen för en värdetyp eller språkpri primitiv (till exempel 0 för ett heltal eller en tom sträng för en sträng). Den främsta orsaken till att du upprättar ett standardvärde är att det här värdet återställs när du anropar ClearValue på egenskapen. Det kan vara enklare att upprätta ett standardvärde per egenskap än att upprätta standardvärden i konstruktorer, särskilt för värdetyper. För referenstyper ser du dock till att upprätta ett standardvärde inte skapar ett oavsiktligt singleton-mönster. Mer information finns i Metodtips senare i det här avsnittet

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

Anmärkning

Registrera dig inte med standardvärdet UnsetValue. Om du gör det kommer det att förvirra egendomskonsumenter och kan få oavsiktliga konsekvenser inom egendomssystemet.

CreateDefaultValueCallback

I vissa scenarier definierar du beroendeegenskaper för objekt som används i mer än en användargränssnittstråd. Detta kan vara fallet om du definierar ett dataobjekt som används av flera appar eller en kontroll som du använder i mer än en app. Du kan aktivera utbytet av objektet mellan olika gränssnittstrådar genom att tillhandahålla en CreateDefaultValueCallback-implementering i stället för en standardvärdeinstans, som är kopplad till tråden som registrerade egenskapen. I princip definierar en CreateDefaultValueCallback en fabrik för standardvärden. Värdet som returneras av CreateDefaultValueCallback är alltid associerat med den aktuella UI CreateDefaultValueCallback-tråden som använder objektet.

Om du vill definiera metadata som anger en CreateDefaultValueCallback måste du anropa PropertyMetadata.Create för att returnera en metadatainstans. PropertyMetadata-konstruktorerna har ingen signatur som innehåller parametern CreateDefaultValueCallback .

Det typiska implementeringsmönstret för en CreateDefaultValueCallback är att skapa en ny DependencyObject-klass , ange det specifika egenskapsvärdet för varje egenskap i DependencyObject till den avsedda standardinställningen och sedan returnera den nya klassen som objektreferens via returvärdet för metoden CreateDefaultValueCallback .

Egenskapsändrad återanropsmetod

Du kan definiera en egenskapsändringarad återanropsmetod för att definiera din egenskaps interaktioner med andra beroendeegenskaper, eller för att uppdatera en intern egenskap eller ett tillstånd för objektet när egenskapen ändras. Om återanropet utlöses har egenskapssystemet fastställt att det sker en faktisk ändring av egenskapsvärdet. Eftersom motringningsmetoden är statisk är parametern d för återanropet viktig eftersom den anger vilken instans av klassen som har rapporterat en ändring. En typisk implementering använder egenskapen NewValue för händelsedata och bearbetar det värdet på något sätt, vanligtvis genom att utföra en ändring på objektet som skickas som d. Ytterligare svar på en egenskapsändring är att avvisa värdet som rapporteras av NewValue, återställa OldValue eller ange värdet till en programmatisk begränsning som tillämpas på NewValue.

I nästa exempel visas en PropertyChangedCallback-implementering . Den implementerar den metod som du såg i föregående registerexempel som en del av byggargumenten för PropertyMetadata. Scenariot som hanteras av den här motringningen är att klassen också har en beräknad skrivskyddad egenskap med namnet "HasLabelValue" (implementeringen visas inte). När egenskapen "Etikett" omvärderas anropas den här återanropsmetoden och återanropet gör att det beroende beräknade värdet kan förbli synkroniserat med ändringar i beroendeegenskapen.

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

Egenskapen ändrade beteendet för strukturer och uppräkningar

Om typen av dependencyProperty är en uppräkning eller en struktur kan återanropet anropas även om de interna värdena för strukturen eller uppräkningsvärdet inte ändrades. Detta skiljer sig från en systemprimiter, till exempel en sträng där den bara anropas om värdet ändras. Detta är en bieffekt av box/unbox-operationer på dessa värden som utförs internt. Om du har en PropertyChangedCallback-metod för en egenskap där värdet är en uppräkning eller struktur, måste du jämföra OldValue och NewValue genom att omvandla värdena själv och använda de överlagrade jämförelseoperatorerna som är tillgängliga för de nu gjutna värdena. Eller om ingen sådan operator är tillgänglig (vilket kan vara fallet för en anpassad struktur) kan du behöva jämföra de enskilda värdena. Du skulle vanligtvis välja att inte göra någonting om resultatet är att värdena inte har ändrats.

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

Metodtips

Tänk på följande som metodtips när du definierar din anpassade beroendeegenskap.

DependencyObject och trådhantering

Alla DependencyObject-instanser måste skapas i användargränssnittstråden som är associerad med det aktuella fönstret som visas av en Windows Runtime-app. Även om varje DependencyObject måste skapas i huvudgränssnittstråden kan objekten nås med hjälp av en dispatcher-referens från andra trådar genom att anropa Dispatcher.

Trådaspekterna i DependencyObject är relevanta eftersom det i allmänhet innebär att endast kod som körs i användargränssnittstråden kan ändra eller till och med läsa värdet för en beroendeegenskap. Trådproblem kan vanligtvis undvikas i typisk användargränssnittskod som använder asynkrona mönster och bakgrundstrådar i arbetsytan. Du stöter vanligtvis bara på DependencyObject-relaterade trådproblem om du definierar dina egna DependencyObject-typer och försöker använda dem för datakällor eller andra scenarier där ett DependencyObject inte nödvändigtvis är lämpligt.

Att undvika oavsiktliga singletons

En oavsiktlig singleton kan inträffa om du deklarerar en beroendeegenskap som har en referenstyp, och du anropar en konstruktor för den referenstypen som en del av koden som upprättar din PropertyMetadata. Vad som händer är att alla användningar av beroendeegenskapen bara delar en instans av PropertyMetadata och därmed försöker dela den enda referenstyp som du skapade. Eventuella underegenskaper av den värdetypen som du anger via beroendeegenskapen sprids sedan till andra objekt på ett sätt som du kanske inte har tänkt dig.

Du kan använda klasskonstruktorer för att ange initiala värden för en beroendeegenskap av referenstyp om du vill ha ett värde som inte är null, men tänk på att detta skulle betraktas som ett lokalt värde i samband med översikten över beroendeegenskaper. Det kan vara lämpligare att använda en mall för det här ändamålet, om klassen stöder mallar. Ett annat sätt att undvika ett singleton-mönster, men ändå ge ett användbart standardvärde, är att exponera en statisk egenskap för referenstypen som ger en lämplig standard för värdena för den klassen.

Beroendeegenskaper av samlingstyp

Beroendeegenskaper av samlingstyp har några ytterligare implementeringsproblem att tänka på.

Beroendeegenskaper av samlingstyp är relativt sällsynta i Windows Runtime-API:et. I de flesta fall kan du använda samlingar där objekten är en DependencyObject-underklass , men själva samlingsegenskapen implementeras som en konventionell CLR- eller C++-egenskap. Det beror på att samlingar inte nödvändigtvis passar vissa typiska scenarier där beroendeegenskaper ingår. Till exempel:

  • Du animerar vanligtvis inte en samling.
  • Du förfyller vanligtvis inte objekten i en samling med stilar eller mallar.
  • Även om bindning till samlingar är ett större scenario behöver en samling inte vara en beroendeegenskap för att vara en bindningskälla. För bindningsmål är det mer typiskt att använda underklasser av ItemsControl eller DataTemplate för att stödja samlingsobjekt eller använda mönster för visningsmodell. Mer information om bindning till och från samlingar finns i Databindning på djupet.
  • Meddelanden om samlingsändringar åtgärdas bättre via gränssnitt som INotifyPropertyChanged eller INotifyCollectionChanged, eller genom att härleda samlingstypen från ObservableCollection<T>.

Det finns dock scenarier för beroendeegenskaper av samlingstyp. De kommande tre avsnitten innehåller vägledning om hur du implementerar en beroendeegenskap av samlingstyp.

Initiera samlingen

När du skapar en beroendeegenskap kan du upprätta ett standardvärde med hjälp av metadata för beroendeegenskap. Men var noga med att inte använda en statisk singleton-samling som standardvärde. I stället måste du medvetet ange samlingsvärdet till en unik samling (instans) som en del av klasskonstruktorlogik för ägarklassen för samlingsegenskapen.

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

Ett DependencyProperty och dess PropertyMetadatas standardvärde är en del av den statiska definitionen av DependencyProperty. Genom att ange ett standardvärde för en samling (eller ett annat instansvärde) som standardvärde delas det över alla instanser av klassen i stället för att varje klass har en egen samling, vilket normalt skulle vara önskvärt.

Ändra meddelanden

Att definiera samlingen som en beroendeegenskap tillhandahåller inte automatiskt ändringsnotifiering för objekten i samlingen genom att egenskapssystemet anropar 'PropertyChanged'-återanropsmetoden. Om du vill ha meddelanden för samlingar eller samlingsobjekt – till exempel för ett databindningsscenario – implementerar du gränssnittet INotifyPropertyChanged eller INotifyCollectionChanged . Mer information finns i Databindning på djupet.

Säkerhetsöverväganden för beroendeegenskap

Deklarera beroendeegenskaper som offentliga egenskaper. Deklarera beroendeegenskapsidentifierare som offentlig statisk skrivskyddad medlem. Även om du försöker deklarera andra åtkomstnivåer som tillåts av ett språk (till exempel skyddat), kan en beroendeegenskap alltid nås via identifieraren i kombination med egenskapssystemets API:er. Att deklarera beroendeegenskapsidentifieraren som intern eller privat fungerar inte, eftersom egenskapssystemet då inte kan fungera korrekt.

Omslutande egenskaper är huvudsakligen till för bekvämlighetsskäl, säkerhetsmekanismer som tillämpas på omslutarna kan kringgås genom att anropa GetValue eller SetValue istället. Så håll omslutningsegenskaper offentliga; annars gör du bara din egendom svårare för legitima uppringare att använda utan att ge någon verklig säkerhetsförmån.

Windows Runtime tillhandahåller inte något sätt att registrera en anpassad beroendeegenskap som endast läsbar.

Beroendeegenskaper och klasskonstruktorer

Det finns en allmän princip att klasskonstruktorer inte ska anropa virtuella metoder. Det beror på att konstruktorer kan anropas för att utföra grundläggande initiering av en härledd klasskonstruktor, och om du anger den virtuella metoden via konstruktorn kan det inträffa när objektinstansen som skapas ännu inte har initierats helt. När du härleder från en klass som redan härleds från DependencyObject ska du komma ihåg att själva egenskapssystemet anropar och exponerar virtuella metoder internt som en del av dess tjänster. För att undvika potentiella problem med körningsinitiering ska du inte ange värden för beroendeegenskap i konstruktorer för klasser.

Registrera beroendeegenskaperna för C++/CX-appar

Implementeringen för att registrera en egenskap i C++/CX är svårare än C#, både på grund av uppdelningen i huvud- och implementeringsfilen och även på grund av att initiering i rotomfattningen för implementeringsfilen är en felaktig metod. (Visual C++-komponenttillägg (C++/CX) placerar statisk initieringskod från rotomfånget direkt i DllMain, medan C#-kompilatorer tilldelar statiska initierare till klasser och därmed undviker problem med DllMain-belastningslås .). Bästa praxis här är att deklarera en hjälpfunktion som gör all din registrering av beroendeegenskap för en klass, en funktion per klass. För varje anpassad klass som appen använder måste du referera till den hjälpregistreringsfunktion som exponeras av varje anpassad klass som du vill använda. Anropa varje hjälpregistreringsfunktion en gång som en del av programkonstruktorn (App::App()), före InitializeComponent. Konstruktorn körs bara när appen verkligen refereras för första gången, den körs inte igen om en pausad app till exempel återupptas. Som vi såg i föregående C++-registreringsexempel är nullptr-kontrollen runt varje Register-anrop viktig: det är en försäkring att ingen anropare av funktionen kan registrera egenskapen två gånger. Ett andra registreringsanrop skulle förmodligen krascha din applikation utan en sådan kontroll eftersom egenskapsnamnet skulle vara en dubblett. Du kan se det här implementeringsmönstret i exemplet med XAML-användare och anpassade kontroller om du tittar på koden för C++/CX-versionen av exemplet.