Sdílet prostřednictvím


Vlastní vlastnosti závislostí

Tady vysvětlujeme, jak definovat a implementovat vlastní vlastnosti závislostí pro aplikaci windows Runtime pomocí C++, C# nebo Visual Basicu. Uvádíme důvody, proč můžou vývojáři aplikací a autoři komponent chtít vytvářet vlastní vlastnosti závislostí. Popisujeme kroky implementace vlastní vlastnosti závislostí a také některé osvědčené postupy, které můžou zlepšit výkon, použitelnost nebo všestrannost vlastnosti závislosti.

Požadavky

Předpokládáme, že jste si přečetli přehled vlastností závislosti a porozuměli jste vlastnostem závislostí z pohledu příjemce existujících vlastností závislosti. Pokud chcete postupovat podle příkladů v tomto tématu, měli byste také porozumět jazyku XAML a vědět, jak psát základní aplikaci prostředí Windows Runtime pomocí C++, C# nebo Visual Basicu.

Co je vlastnost závislosti?

Pokud chcete podporovat styly, datové vazby, animace a výchozí hodnoty vlastnosti, měla by být implementována jako vlastnost závislosti. Hodnoty vlastnosti závislosti nejsou uloženy jako pole ve třídě, nýbrž jsou uloženy v rámci XAML a jsou odkazovány pomocí klíče, který se načte, když je vlastnost zaregistrována v systému vlastností Windows Runtime. To se provádí voláním metody DependencyProperty.Register. Vlastnosti závislostí lze používat pouze pomocí typů odvozených z Objektu DependencyObject. Ale DependencyObject je v hierarchii tříd poměrně vysoký, takže většina tříd, které jsou určeny pro podporu uživatelského rozhraní a prezentace, může podporovat vlastnosti závislostí. Další informace o vlastnostech závislostí a některých terminologiích a konvencích používaných k jejich popisu v této dokumentaci najdete v přehledu vlastností závislostí.

Příklady vlastností závislostí v prostředí Windows Runtime jsou mimo jiné: Control.Background, FrameworkElement.Width a TextBox.Text.

Konvence je, že každá vlastnost závislostí vystavená třídou má odpovídající veřejnou statickou Readonly vlastnost typu DependencyProperty, která je vystavena ve stejné třídě a poskytuje identifikátor pro vlastnost závislosti. Název identifikátoru se řídí touto konvencí: název vlastnosti závislosti s řetězcem "Property" přidaným na konec názvu. Například odpovídající Identifikátor DependencyProperty pro Control.Background vlastnost je Control.BackgroundProperty. Identifikátor ukládá informace o vlastnosti závislosti, jak byla zaregistrována, a lze ji použít pro jiné operace zahrnující vlastnost závislosti, jako je volání SetValue.

Obaly vlastností

Vlastnosti závislostí obvykle mají implementaci obálky. Bez obálky by jediným způsobem, jak získat nebo nastavit vlastnosti, bylo použít metody vlastností závislosti GetValue a SetValue a předat jim identifikátor jako parametr. Jedná se spíše o nepřirozené použití něčeho, co je zdánlivě vlastností. Pomocí obálky ale váš kód a jakýkoli jiný kód, který odkazuje na vlastnost závislosti, může použít přímou syntaxi vlastností objektu, která je přirozená pro jazyk, který používáte.

Pokud implementujete vlastní vlastnost závislosti a chcete, aby byla veřejná a snadno volatelná, definujte také obaly vlastností. Obálky vlastností jsou také užitečné pro poskytování základních informací o vlastnosti závislosti pro reflexi nebo statickou analýzu. Obálka je konkrétně místo, kde umístíte atributy, jako je ContentPropertyAttribute.

Kdy implementovat vlastnost jako vlastnost závislosti

Kdykoli implementujete veřejnou vlastnost pro čtení a zápis třídy, pokud vaše třída je odvozena z DependencyObject, máte možnost, aby vaše vlastnost fungovala jako vlastnost závislosti. Někdy je typická technika zálohování vašeho majetku s privátním polem adekvátní. Definování vlastní vlastnosti jako vlastnosti závislosti není vždy nutné ani vhodné. Volba bude záviset na scénářích, které chcete, aby váš parametr podporoval.

Vlastnost můžete zvážit jako vlastnost závislosti, pokud chcete, aby podporovala jednu nebo více z těchto funkcí prostředí Windows Runtime nebo aplikací prostředí Windows Runtime:

  • Nastavení vlastnosti pomocí stylu
  • Působí jako platná cílová vlastnost pro datové propojení s {Binding}
  • Podpora animovaných hodnot prostřednictvím Storyboardu
  • Oznámení o změně hodnoty vlastnosti:
    • Akce prováděné samotným systémem vlastností
    • Prostředí
    • Akce uživatelů
    • Styly čtení a psaní

Kontrolní seznam pro definování vlastnosti závislosti

Definování vlastnosti závislosti lze považovat za sadu konceptů. Tyto koncepty nejsou nutně procedurálními kroky, protože několik konceptů lze řešit v jednom řádku kódu v implementaci. Tento seznam poskytuje jen rychlý přehled. Jednotlivé koncepty podrobněji vysvětlíme dále v tomto tématu a ukážeme vám ukázkový kód v několika jazycích.

  • Zaregistrujte název vlastnosti v systému vlastností (volat Register), zadejte typ vlastníka a typ hodnoty vlastnosti.
    • Pro Register je povinný parametr, který očekává metadata vlastností. Zadejte hodnotu null pro toto nebo pokud chcete změnit chování vlastnosti nebo výchozí hodnotu založenou na metadatech, kterou lze obnovit voláním ClearValue, zadejte instanci PropertyMetadata.
  • Definujte identifikátor DependencyProperty jako veřejný člen vlastnosti jen pro čtení u typu vlastníka.
  • Definujte zapouzdřující vlastnost podle modelu přístupu k vlastnostem, který se používá v jazyce, který implementujete. Název vlastnosti obálky by se měl shodovat s řetězcem name, který jste použili v Register. Implementujte přístupové objekty get a set pro připojení obálky s vlastností závislosti, kterou zabalí, voláním GetValue a SetValue a předáním identifikátoru vlastní vlastnosti jako parametru.
  • (Volitelné) Umístěte atributy, jako je ContentPropertyAttribute na obálku.

Poznámka:

Pokud definujete vlastní připojenou vlastnost, obvykle vynecháte obálku. Místo toho napíšete jiný styl přístupového objektu, který může použít procesor XAML. Viz Vlastní připojené vlastnosti.

Registrace vlastnosti

Aby vaše vlastnost byla vlastností závislosti, musíte ji zaregistrovat do úložiště vlastností spravovaného systémem vlastností prostředí Windows Runtime. Chcete-li zaregistrovat vlastnost, volejte metodu Register .

Pro jazyky Microsoft .NET (C# a Microsoft Visual Basic) voláte Register v těle třídy (uvnitř třídy, ale mimo definice členů). Identifikátor je poskytován voláním metody Register jako návratová hodnota. Volání Register je obvykle provedeno jako statický konstruktor nebo jako součást inicializace veřejné statické readonly vlastnost typu DependencyProperty jako součást vaší třídy. Tato vlastnost zveřejňuje identifikátor vlastnosti závislosti. Tady jsou příklady volání Register .

Poznámka:

Registrace vlastnosti závislosti jako součást definice vlastnosti identifikátoru je typická implementace, ale můžete také zaregistrovat vlastnost závislosti ve statickém konstruktoru třídy. Tento přístup může dávat smysl, pokud k inicializaci vlastnosti závislosti potřebujete více než jeden řádek kódu.

Pro C++/CX máte možnosti rozdělení implementace mezi hlavičku a soubor kódu. Typickým rozdělením je deklarovat samotný identifikátor jako veřejná statická vlastnost v hlavičce s implementací get , ale bez sady. Implementace get odkazuje na privátní pole, což je neinicializovaná instance DependencyProperty . Můžete také deklarovat zabalovače a get a set implementace zabalovače. V tomto případě hlavička obsahuje minimální implementaci. Pokud obálka vyžaduje atribuci prostředí Windows Runtime, přidejte ji také do hlavičky. Volání Register vložte do pomocné funkce v souboru kódu, která se spustí jen při první inicializaci aplikace. Pomocí návratové hodnoty Register vyplňte statické, ale neinicializované identifikátory, které jste deklarovali v hlavičce, které jste původně nastavili na nullptr v kořenovém oboru souboru implementace.

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

Poznámka:

Pro kód C++/CX je důvodem, proč máte soukromé pole a veřejnou vlastnost jen pro čtení, která zobrazí Vlastnost DependencyProperty , aby ostatní volající, kteří používají vaši vlastnost závislosti, mohli také použít rozhraní API nástroje pro systém vlastností, které vyžadují, aby identifikátor byl veřejný. Pokud identifikátor zachováte jako soukromý, uživatelé nebudou moct tato rozhraní API nástroje používat. Mezi příklady takových rozhraní API a scénářů patří GetValue nebo SetValue podle výběru, ClearValue, GetAnimationBaseValue, SetBinding a Setter.Property. Pro toto pole nelze použít veřejné pole, protože pravidla metadat prostředí Windows Runtime neumožňují veřejná pole.

Konvence názvů vlastnosti závislosti

Existují zásady vytváření názvů pro vlastnosti závislostí; sledujte je za všech výjimečných okolností. Samotná vlastnost závislosti má základní název ("Label" v předchozím příkladu), který je uveden jako první parametr register. Název musí být jedinečný v rámci každého typu registrace a požadavek na jedinečnost platí také pro všechny zděděné členy. Vlastnosti závislostí zděděné prostřednictvím základních typů jsou považovány za součást již zaregistrovaného typu; názvy zděděných vlastností nelze znovu zaregistrovat.

Výstraha

I když zde zadaný název může být libovolný identifikátor řetězce, který je platný při programování pro váš jazyk podle vašeho výběru, obvykle chcete mít možnost nastavit vlastnost závislosti také v XAML. Aby byl vytvořen v XAML, název vlastnosti, kterou zvolíte, musí být platný název XAML. Další informace najdete v přehledu XAML.

Při vytváření vlastnosti identifikátoru zkombinujte název vlastnosti při registraci s příponou "Property" (Například LabelProperty). Tato vlastnost je vaším identifikátorem vlastnosti závislosti a používá se jako vstup pro volání SetValue a GetValue , která provedete v obálkách vlastní vlastnosti. Používá ho také systém vlastností a další procesory XAML, například {x:Bind}.

Implementace obálky

Zabalovač vlastnosti by měl volat GetValue v get implementaci a SetValue v set implementaci.

Výstraha

Za všech výjimečných okolností by vaše implementace obálky měly provádět pouze operace GetValue a SetValue . V opačném případě získáte jiné chování, když je vlastnost nastavena prostřednictvím XAML a kdy je nastavena prostřednictvím kódu. Z důvodu efektivity analyzátor XAML obchází obálky při nastavování vlastností závislosti; a komunikuje s záložním úložištěm přes 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);
    }
  }

Metadata vlastností pro vlastní vlastnost závislosti

Pokud jsou metadata vlastností přiřazena k vlastnosti závislosti, stejná metadata se použijí na tuto vlastnost pro každou instanci typu vlastníka vlastnosti nebo jeho podtřídy. V metadatech vlastností můžete zadat dvě chování:

  • Výchozí hodnota, kterou systém vlastností přiřadí všem případům vlastnosti.
  • Statická metoda zpětného volání, která se automaticky vyvolá v systému vlastností při zjištění změny hodnoty vlastnosti.

Volání registru s metadaty vlastností

V předchozích příkladech volání DependencyProperty.Register jsme předali hodnotu null parametru propertyMetadata . Chcete-li povolit závislou vlastnost, aby poskytla výchozí hodnotu nebo použila zpětné volání při změně vlastnosti, musíte definovat instanci PropertyMetadata, která poskytuje jednu nebo obě tyto schopnosti.

Obvykle zadáte PropertyMetadata jako vytvořenou inline instanci v parametrech DependencyProperty.Register.

Poznámka:

Pokud implementujete CreateDefaultValueCallback, musíte použít nástrojovou metodu PropertyMetadata.Create místo volání konstruktoru PropertyMetadata pro definování instance PropertyMetadata.

Tento další příklad upraví dříve zobrazené příklady DependencyProperty.Register odkazováním na instanci PropertyMetadata s hodnotou PropertyChangedCallback . Implementace zpětného volání OnLabelChanged se zobrazí dále v této části.

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

Výchozí hodnota

Můžete zadat výchozí hodnotu vlastnosti závislosti tak, aby vlastnost vždy vrátila konkrétní výchozí hodnotu, pokud není nastavena. Tato hodnota se může lišit od vlastní výchozí hodnoty pro typ této vlastnosti.

Pokud není zadána výchozí hodnota, výchozí hodnota vlastnosti závislosti má hodnotu null pro typ odkazu nebo výchozí typ pro typ hodnoty nebo primitiv jazyka (například 0 pro celé číslo nebo prázdný řetězec pro řetězec). Hlavním důvodem pro vytvoření výchozí hodnoty je, že tato hodnota je obnovena při volání ClearValue pro vlastnost. Vytvoření výchozí hodnoty na základě vlastností může být pohodlnější než vytvoření výchozích hodnot v konstruktorech, zejména pro typy hodnot. U referenčních typů se ale ujistěte, že nastavení výchozí hodnoty nevytvoří neúmyslný vzor jedináčka. Další informace najdete v části Osvědčené postupy dále v tomto tématu.

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

Poznámka:

Nezaregistrujte se s výchozí hodnotou UnsetValue. Pokud to uděláte, zmátí to uživatele vlastností a bude mít nezamýšlené důsledky v rámci systému vlastností.

CreateDefaultValueCallback

V některých scénářích definujete vlastnosti závislostí pro objekty, které se používají ve více vláknech uživatelského rozhraní. To může být případ, kdy definujete datový objekt, který používá více aplikací, nebo ovládací prvek, který používáte ve více než jedné aplikaci. Můžete povolit výměnu objektu mezi různými vlákny uživatelského rozhraní poskytnutím CreateDefaultValueCallback implementace namísto výchozí instance hodnoty, která je svázána s vláknem, které zaregistroval vlastnost. V podstatě CreateDefaultValueCallback určuje továrnu pro výchozí hodnoty. Hodnota vrácená CreateDefaultValueCallback je vždy přidružena k aktuálnímu vláknu uživatelského rozhraní CreateDefaultValueCallback, které používá objekt.

Chcete-li definovat metadata, která určuje CreateDefaultValueCallback, musíte volat PropertyMetadata.Create pro vrácení instance metadat; Konstruktory PropertyMetadata nemají podpis, který obsahuje CreateDefaultValueCallback parametr.

Typickým způsobem implementace pro CreateDefaultValueCallback je vytvořit novou třídu DependencyObject, nastavit specifickou hodnotu pro každou vlastnost DependencyObject na zamýšlenou výchozí hodnotu a pak vrátit novou třídu jako objektový odkaz návratovou hodnotou metody CreateDefaultValueCallback.

Metoda zpětného volání při změně vlastnosti

Můžete definovat metodu zpětného volání změněnou vlastností, která definuje interakce vlastnosti s jinými vlastnostmi závislosti, nebo aktualizovat interní vlastnost nebo stav objektu při každé změně vlastnosti. Pokud je vyvoláno vaše zpětné volání, systém vlastností zjistil, že došlo k účinné změně hodnoty vlastnosti. Vzhledem k tomu, že metoda zpětného volání je statická, je důležitý parametr d zpětného volání, protože vám řekne, která instance třídy oznámila změnu. Typická implementace používá vlastnost NewValue dat události a zpracovává tuto hodnotu nějakým způsobem, obvykle prováděním nějaké další změny na objektu předaném jako d. Další odpovědi na změnu vlastnosti jsou odmítnout hodnotu hlášenou NewValue, obnovit OldValue nebo nastavit hodnotu na programové omezení použité na NewValue.

Tento další příklad ukazuje PropertyChangedCallback implementace. Implementuje metodu, na kterou jste viděli odkazované v předchozích příkladech Register , jako součást stavebních argumentů pro PropertyMetadata. Scénář vyřešený tímto zpětným voláním spočívá v tom, že třída má také počítanou vlastnost jen pro čtení s názvem "HasLabelValue" (implementace není zobrazena). Kdykoli se vlastnost Label znovu vyhodnotí, vyvolá se tato metoda zpětného volání a zpětné volání umožní závislé počítané hodnotě zůstat v synchronizaci se změnami vlastnosti závislosti.

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

Změna chování vlastností pro struktury a výčty

Pokud je typem DependencyProperty výčet nebo struktura, zpětné volání může být vyvoláno i v případě, že se vnitřní hodnoty struktury nebo hodnota výčtu nezměnila. Liší se od primitivního systému, jako je například řetězec, ve kterém se vyvolá pouze v případě, že se hodnota změnila. Jedná se o vedlejší účinek operací boxování a unboxování těchto hodnot, které se provádějí interně. Pokud máte PropertyChangedCallback metodu pro vlastnost, kde je vaše hodnota výčtem nebo strukturou, musíte porovnat OldValue a NewValue přetypováním hodnot sami a pomocí přetížených relačních operátorů, které jsou k dispozici pro hodnoty, které jste nyní přetypovali. Nebo pokud není k dispozici žádný takový operátor (což může být případ vlastní struktury), možná budete muset porovnat jednotlivé hodnoty. Obvykle byste se rozhodli nic udělat, pokud je výsledkem to, že se hodnoty nezměnily.

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

Osvědčené postupy

Při definování vlastní vlastnosti závislosti mějte na paměti následující aspekty jako osvědčené postupy.

DependencyObject a vlákna

Všechny instance DependencyObject musí být vytvořeny ve vlákně uživatelského rozhraní, které je přidruženo k aktuálnímu okně zobrazenému aplikací Windows Runtime. I když musí být každý DependencyObject vytvořen v hlavním vlákně uživatelského rozhraní, lze k objektům přistupovat pomocí odkazu dispečera z jiných vláken volání Dispatcher.

Aspekty threadingu DependencyObject jsou relevantní, protože obecně znamená, že pouze kód spuštěný ve vlákně uživatelského rozhraní může změnit nebo dokonce číst hodnotu vlastnosti závislosti. Problémy s vlákny se obvykle dají vyhnout v typickém kódu uživatelského rozhraní, který správně využívá asynchronní vzory a pracovní vlákna na pozadí. K problémům s vlákny souvisejícím s DependencyObject obvykle dochází, pokud definujete vlastní typy DependencyObject a pokusíte se je použít jako zdroje dat nebo v jiných scénářích, kdy DependencyObject nemusí být nutně vhodný.

Vyhněte se neúmyslným singletonům

Neúmyslný singleton může nastat, pokud deklarujete vlastnost závislosti, která přijímá referenční typ, a voláte konstruktor pro tento referenční typ jako součást kódu, který stanovuje PropertyMetadata. Co se stane je, že všechna použití vlastnosti závislosti sdílejí pouze jednu instanci PropertyMetadata, a proto se pokouší sdílet jediný referenční typ, který jste vytvořili. Jakékoli dílčí výhody tohoto typu hodnoty, které jste nastavili prostřednictvím vlastnosti závislosti, se pak rozšíří na jiné objekty způsobem, který jste možná nezamýšleli.

Konstruktory třídy můžete použít k nastavení počátečních hodnot pro vlastnost závislostí typu odkaz, pokud chcete hodnotu, která není null, ale mějte na paměti, že by to bylo považováno za místní hodnotu pro účely přehledu vlastností závislostí. Pokud vaše třída podporuje šablony, může být vhodnější pro tento účel použít šablonu. Dalším způsobem, jak se vyhnout jednoduchému vzoru, ale přesto poskytnout užitečné výchozí nastavení, je zveřejnit statickou vlastnost pro typ odkazu, která poskytuje vhodné výchozí hodnoty pro hodnoty této třídy.

Vlastnosti závislostí typu kolekcí

Vlastnosti závislostí typu kolekce mají některé další problémy s implementací, které je potřeba zvážit.

Vlastnosti závislostí typu kolekce jsou relativně vzácné v rozhraní API prostředí Windows Runtime. Ve většině případů můžete použít kolekce, ve kterých jsou položky podtřídou DependencyObject , ale samotná vlastnost kolekce je implementována jako konvenční CLR nebo C++ vlastnost. Důvodem je to, že kolekce nemusí nutně vyhovovat některým typickým scénářům, ve kterých jsou zahrnuté vlastnosti závislostí. Například:

  • Neobvykle animujete kolekci.
  • Položky v kolekci obvykle nevyplníte styly nebo šablonou.
  • I když je vazba na kolekce hlavním scénářem, kolekce nemusí být vlastnost závislosti, aby mohla sloužit jako zdroj vazby. U cílů vazby je obvyklejší použít podtřídy ItemsControl nebo DataTemplate pro podporu položek kolekce nebo použití vzorů modelu zobrazení. Další informace o vazbách k kolekcím a z kolekcí najdete v tématu Hloubková datová vazba.
  • Oznámení o změnách kolekce jsou lépe řešena prostřednictvím rozhraní, jako je INotifyPropertyChanged nebo INotifyCollectionChanged, nebo odvozením typu kolekce z ObservableCollection<T>.

Nicméně existují scénáře pro vlastnosti závislostí typu kolekce. Další tři části obsahují pokyny k implementaci vlastnosti závislosti typu kolekce.

Inicializace kolekce

Při vytváření vlastnosti závislosti můžete vytvořit výchozí hodnotu pomocí metadat vlastností závislostí. Dávejte ale pozor, abyste jako výchozí hodnotu nepoužíli statickou kolekci singleton. Místo toho je nutné záměrně nastavit hodnotu kolekce na jedinečnou kolekci (instance) jako součást logiky konstruktoru třídy pro třídu vlastníka vlastnosti kolekce.

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

A DependencyProperty a výchozí hodnota jeho PropertyMetadata jsou součástí statické definice DependencyProperty. Když jako výchozí hodnotu poskytnete výchozí hodnotu kolekce (nebo jiné instance), bude sdílena mezi všemi instancemi vaší třídy místo každé třídy, která má vlastní kolekci, jak by bylo obvykle žádoucí.

Změna oznámení

Určení kolekce jako vlastnosti závislosti automaticky neposkytuje oznámení o změnách položek v kolekci, protože systém vlastností nevyvolá metodu zpětného volání "PropertyChanged". Pokud chcete oznámení o kolekcích nebo položkách kolekce, například pro scénář vazby dat, musíte implementovat rozhraní INotifyPropertyChanged nebo INotifyCollectionChanged. Další informace naleznete v tématu , Datové vazby podrobně,.

Aspekty zabezpečení závislých vlastností

Deklarujte vlastnosti závislosti jako veřejné vlastnosti. Deklarujte identifikátory vlastností závislosti jako veřejné statické členy jen pro čtení . I když se pokusíte deklarovat jiné úrovně přístupu povolené jazykem (například chráněným), je vlastnost závislosti vždy přístupná prostřednictvím identifikátoru v kombinaci s rozhraními API systému vlastností. Deklarování identifikátoru vlastnosti závislosti jako interní nebo soukromé nebude fungovat, protože systém vlastností nemůže správně fungovat.

Vlastnosti obálky jsou skutečně pro usnadnění, mechanismy zabezpečení použité na obálky lze obejít voláním GetValue nebo SetValue . Proto ponechte obalové vlastnosti veřejné; jinak jen znesnadníte legitimním volajícím jejich použití, aniž byste poskytli jakoukoli skutečnou výhodu v zabezpečení.

Prostředí Windows Runtime neposkytuje způsob, jak zaregistrovat vlastní vlastnost závislosti pouze pro čtení.

Vlastnosti závislostí a konstruktory tříd

Existuje obecný princip, že konstruktory tříd by neměly volat virtuální metody. Důvodem je to, že konstruktory lze volat pro provedení základní inicializace konstruktoru odvozené třídy a vstup do virtuální metody prostřednictvím konstruktoru může nastat, když instance objektu, která se vytváří, ještě není zcela inicializována. Pokud odvozujete z jakékoli třídy, která již je odvozena z DependencyObject, mějte na paměti, že systém vlastností sám volá a zveřejňuje virtuální metody interně jako součást svých služeb. Abyste se vyhnuli potenciálním problémům s inicializací za běhu, nenastavujte hodnoty vlastností závislostí v konstruktorech tříd.

Registrace vlastností závislostí pro aplikace C++/CX

Implementace registrace vlastnosti v jazyce C++/CX je složitější než jazyk C#, a to jak z důvodu oddělení hlavičky a souboru implementace, tak i kvůli tomu, že inicializace v kořenovém oboru souboru implementace je chybný postup. (Rozšíření komponent Visual C++ (C++/CX) umístí statický inicializační kód z kořenového oboru přímo do DllMain, zatímco kompilátory jazyka C# přiřazují statické inicializátory ke třídám, a proto se vyhýbají problémům se zámkem načítání DllMain.) Osvědčeným postupem je deklarovat pomocnou funkci, která provádí všechny registrace vlastností závislostí pro třídu, jednu funkci na třídu. Pro každou vlastní třídu, kterou vaše aplikace využívá, budete muset odkazovat na pomocnou registrační funkci poskytovanou každou vlastní třídou, kterou chcete použít. Každou pomocnou registrační funkci zavolejte jednou jako součást konstruktoru aplikace (App::App()) před InitializeComponent. Tento konstruktor se spustí pouze při prvním skutečném spuštění aplikace. Například, nebude se spouštět znovu, když se pozastavená aplikace obnoví. Jak je vidět na předchozím příkladu registrace v C++, kontrola nullptr kolem každého volání Register je důležitá: je to záruka, že žádný volající do funkce nemůže vlastnost zaregistrovat dvakrát. Druhé volání registrace by pravděpodobně způsobilo zhroucení vaší aplikace bez kontroly, protože vlastnost by byla duplicitní. Tento vzor implementace si můžete prohlédnout v ukázce uživatele XAML a vlastních ovládacích prvků , pokud se podíváte na kód verze C++/CX ukázky.