Udostępnij przez


Niestandardowe właściwości zależności

W tym miejscu wyjaśniono, jak definiować i implementować własne właściwości zależności dla aplikacji środowiska uruchomieniowego systemu Windows przy użyciu języka C++, C# lub Visual Basic. Lista przyczyn, dla których deweloperzy aplikacji i autorzy składników mogą chcieć utworzyć niestandardowe właściwości zależności. Opisujemy kroki implementacji niestandardowej właściwości zależności, a także niektóre najlepsze rozwiązania, które mogą poprawić wydajność, użyteczność lub wszechstronność właściwości zależności.

Wymagania wstępne

Zakładamy, że zapoznaliśmy się z omówieniem właściwości zależności i że rozumiesz właściwości zależności z perspektywy konsumenta istniejących właściwości zależności. Aby postępować zgodnie z przykładami w tym temacie, należy również zrozumieć język XAML i wiedzieć, jak napisać podstawową aplikację środowiska uruchomieniowego systemu Windows przy użyciu języka C++, C# lub Visual Basic.

Co to jest właściwość zależności?

Aby obsługiwać style, powiązanie danych, animacje i wartości domyślne dla właściwości, należy zaimplementować ją jako właściwość zależną. Wartości właściwości zależności nie są przechowywane jako pola w klasie, są przechowywane przez strukturę xaml i są przywoływały przy użyciu klucza, który jest pobierany, gdy właściwość jest zarejestrowana w systemie właściwości Środowiska uruchomieniowego systemu Windows przez wywołanie metody DependencyProperty.Register . Właściwości zależności mogą być używane tylko przez typy pochodzące z obiektu DependencyObject. Jednak obiekt DependencyObject jest dość wysoki w hierarchii klas, więc większość klas przeznaczonych do obsługi interfejsu użytkownika i prezentacji może obsługiwać właściwości zależności. Aby uzyskać więcej informacji na temat właściwości zależności i niektórych terminologii i konwencji używanych do opisywania ich w tej dokumentacji, zobacz Omówienie właściwości zależności.

Przykłady właściwości zależności w środowisku uruchomieniowym systemu Windows to między innymi : Control.Background, FrameworkElement.Width i TextBox.Text.

Konwencja polega na tym, że każda właściwość zależności udostępniona przez klasę ma odpowiadającą publiczną statyczną właściwość readonly typu DependencyProperty, która jest dostępna w tej samej klasie i zapewnia identyfikator dla właściwości zależności. Nazwa identyfikatora jest zgodna z następującą konwencją: nazwa właściwości zależności z ciągiem "Property" dodanym na końcu nazwy. Na przykład odpowiadający mu identyfikator DependencyProperty dla właściwości Control.Background to Control.BackgroundProperty. Identyfikator przechowuje informacje o właściwości zależności, ponieważ została zarejestrowana, a następnie może służyć do innych operacji obejmujących właściwość zależności, takich jak wywoływanie metody SetValue.

Opakowania właściwości

Właściwości zależności zwykle mają implementację opakowania. Bez użycia wrappera, jedynym sposobem na pobranie lub ustawienie właściwości byłoby skorzystanie z metod narzędziowych właściwości zależności GetValue i SetValue oraz przekazanie do nich identyfikatora jako parametru. Jest to raczej nienaturalne użycie czegoś, co rzekomo jest właściwością. Jednak w przypadku opakowania, twój kod oraz wszelki inny kod odwołujący się do właściwości zależności mogą używać prostej składni obiekt-właściwość, która jest naturalna dla używanego języka.

Jeśli samodzielnie zaimplementujesz niestandardową właściwość zależności i chcesz, aby była publiczna i łatwa do wywołania, zdefiniuj również otoki właściwości. Otoki właściwości są również przydatne do raportowania podstawowych informacji o właściwości zależności do procesów odbicia lub analizy statycznej. W szczególności opakowanie służy do umieszczania atrybutów, takich jak ContentPropertyAttribute.

Kiedy zaimplementować właściwość jako właściwość zależną

Za każdym razem, gdy implementujesz publiczną właściwość odczytu/zapisu w klasie, o ile klasa pochodzi z klasy DependencyObject, możesz sprawić, że właściwość będzie działać jako właściwość zależności. Czasami typowa technika przypisania właściwości przy użyciu pola prywatnego jest wystarczająca. Definiowanie właściwości niestandardowej jako właściwości zależności nie zawsze jest konieczne ani odpowiednie. Wybór będzie zależał od scenariuszy, które chcesz, aby Twoja funkcjonalność obsługiwała.

Możesz rozważyć zaimplementowanie właściwości jako właściwości zależności, jeśli chcesz, aby obsługiwała co najmniej jedną z tych funkcji środowiska uruchomieniowego systemu Windows lub aplikacji środowiska uruchomieniowego systemu Windows:

  • Ustawianie właściwości za pomocą stylu
  • Działanie jako właściwość docelowa dla powiązania danych za pomocą {Binding}
  • Obsługa animowanych wartości za pośrednictwem Storyboarda
  • Raportowanie, kiedy wartość właściwości została zmieniona przez:
    • Akcje wykonywane przez sam system właściwości
    • Środowisko
    • Akcje użytkownika
    • Style odczytywania i pisania

Lista kontrolna dotycząca definiowania właściwości zależności

Definiowanie właściwości zależności można traktować jako zestaw pojęć. Te pojęcia nie muszą być krokami proceduralnymi, ponieważ kilka pojęć można rozwiązać w jednym wierszu kodu w implementacji. Ta lista zawiera krótkie omówienie. W dalszej części tego tematu wyjaśnimy poszczególne pojęcia, a w kilku językach przedstawimy przykładowy kod.

  • Zarejestruj nazwę właściwości w systemie właściwości (wywołaj metodę Register), określając typ właściciela i typ wartości właściwości.
    • Istnieje wymagany parametr dla Register, który oczekuje metadanych dotyczących właściwości. Określ wartość null dla tej wartości lub jeśli chcesz zmienić zachowanie właściwości lub wartość domyślną opartą na metadanych, którą można przywrócić, wywołując funkcję ClearValue, określ wystąpienie właściwościMetadata.
  • Zdefiniuj identyfikator DependencyProperty jako publiczny statyczny element członkowski właściwości readonly w typie właściciela.
  • Zdefiniuj właściwość opakowania zgodnie z modelem dostępu do właściwości używanym w języku, który wdrażasz. Nazwa właściwości otoki powinna być zgodna z ciągiem nazwa użytym podczas rejestrowania. Zaimplementuj metody dostępu get i set, aby połączyć obiekt opakowujący z właściwością zależną, którą opakowuje, poprzez wywołanie metod GetValue i SetValue oraz przekazanie identyfikatora własnej właściwości jako parametru.
  • (Opcjonalnie) Umieść atrybuty, takie jak ContentPropertyAttribute w otoce.

Uwaga / Notatka

Jeśli definiujesz niestandardową właściwość dołączoną, zazwyczaj pomijasz obudowę. Zamiast tego piszesz inny akcesor, którego może używać procesor XAML. Zobacz Właściwości dołączone niestandardowo.

Rejestracja nieruchomości

Aby właściwość była właściwością zależności, należy zarejestrować ją w magazynie właściwości obsługiwanym przez środowisko uruchomieniowe Windows. Aby zarejestrować właściwość, należy wywołać metodę Register .

W przypadku języków microsoft .NET (C# i Microsoft Visual Basic) należy wywołać metodę Register w treści klasy (wewnątrz klasy, ale poza definicjami składowych). Identyfikator jest dostarczany przez wywołanie metody Register jako wartość zwracaną. Wywołanie Register jest zwykle wykonywane jako konstruktor statyczny lub jako część inicjalizacji właściwości public static readonly typu DependencyProperty w ramach Twojej klasy. Ta właściwość uwidacznia identyfikator właściwości zależności. Oto przykłady wywołania Register.

Uwaga / Notatka

Rejestrowanie właściwości zależności w ramach definicji właściwości identyfikatora jest typową implementacją, ale można również zarejestrować właściwość zależności w konstruktorze statycznym klasy. Takie podejście może mieć sens, jeśli potrzebujesz więcej niż jednego wiersza kodu, aby zainicjować właściwość zależności.

W przypadku języka C++/CX dostępne są opcje podziału implementacji między nagłówka i pliku kodu. Typowe rozróżnienie polega na zadeklarowaniu samego identyfikatora jako publicznej właściwości statycznej w nagłówku, z implementacją get, ale bez set. Implementacja get odnosi się do pola prywatnego, które jest niezainicjowanym wystąpieniem DependencyProperty. Można również zadeklarować opakowania oraz implementacje get i set opakowań. W tym przypadku nagłówek zawiera pewną minimalną implementację. Jeśli opakowanie wymaga przypisania środowiska uruchomieniowego Windows, przypisz atrybut również w nagłówku. Umieść wywołanie Register w pliku kodu w funkcji pomocniczej, która jest uruchamiana tylko wtedy, gdy aplikacja inicjuje się po raz pierwszy. Użyj zwracanej wartości Register , aby wypełnić statyczne, ale niezainicjowane identyfikatory zadeklarowane w nagłówku, które początkowo ustawiono na nullptr w głównym zakresie pliku implementacji.

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

Uwaga / Notatka

W przypadku kodu C++/CX przyczyną, dla której masz pole prywatne i publiczną właściwość tylko do odczytu, która eksponuje właściwość DependencyProperty, jest to, że inni wywołujący, którzy korzystają z Twojej właściwości zależności, mogą również używać narzędzi API systemu właściwości, które wymagają, aby identyfikator był publiczny. Jeśli zachowasz identyfikator prywatny, użytkownicy nie będą mogli używać tych interfejsów API narzędzi. Przykłady takich interfejsów API i scenariuszy to GetValue lub SetValue według wyboru, ClearValue, GetAnimationBaseValue, SetBinding i Setter.Property. W tym celu nie można użyć pola publicznego, ponieważ reguły metadanych środowiska uruchomieniowego systemu Windows nie zezwalają na pola publiczne.

Konwencje nazw właściwości zależności

Istnieją konwencje nazewnictwa właściwości zależności; powinno się ich trzymać we wszystkich oprócz wyjątkowych okolicznościach. Sama właściwość zależności ma podstawową nazwę ("Label" w poprzednim przykładzie), która jest podana jako pierwszy parametr rejestru. Nazwa musi być unikatowa w obrębie każdego typu rejestracji, a wymóg unikatowości ma również zastosowanie do wszystkich odziedziczonych członków. Właściwości zależności dziedziczone za pośrednictwem typów bazowych są uważane za część typu rejestrującego; nazw dziedziczonych właściwości nie można zarejestrować ponownie.

Ostrzeżenie

Chociaż podana tutaj nazwa może być dowolnym identyfikatorem ciągu, który jest prawidłowy w programowaniu dla wybranego języka, zwykle chcesz mieć możliwość ustawienia właściwości zależności w języku XAML. Aby ustawić właściwość w XAML, nazwa, którą wybierzesz, musi być prawidłową nazwą XAML. Aby uzyskać więcej informacji, zobacz Omówienie języka XAML.

Podczas tworzenia właściwości identyfikatora połącz nazwę właściwości, którą zarejestrowałeś, z sufiksem "Property" (na przykład "LabelProperty"). Ta właściwość jest identyfikatorem właściwości zależności i jest używana jako dane wejściowe dla wywołań SetValue i GetValue, które wykonujesz w twoich własnych otokach właściwości. Jest on również używany przez system właściwości i inne procesory XAML, takie jak {x:Bind}

Wdrażanie opakowania

Otoka właściwości powinna wywołać metodę GetValue w implementacji get i SetValue w implementacji zestawu .

Ostrzeżenie

Z wyjątkiem wyjątkowych okoliczności, implementacje otoki powinny wykonywać tylko operacje GetValue i SetValue. W przeciwnym razie uzyskasz inne zachowanie, gdy właściwość jest ustawiana za pomocą języka XAML, a kiedy jest ustawiona za pomocą kodu. W celu zwiększenia wydajności analizator XAML pomija elementy opakowujące podczas ustawiania właściwości zależności i odwołuje się do magazynu zapasowego za pośrednictwem 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);
    }
  }

Metadane właściwości dla niestandardowej właściwości zależności

Gdy metadane właściwości są przypisywane do właściwości zależności, te same metadane są stosowane do tej właściwości dla każdego wystąpienia typu właściciela właściwości lub jej podklas. W metadanych właściwości można określić dwa zachowania:

  • Wartość domyślna przypisywana przez system właściwości do wszystkich przypadków właściwości.
  • Statyczna metoda wywołania zwrotnego, która jest automatycznie wywoływana w systemie właściwości za każdym razem, gdy zostanie wykryta zmiana wartości właściwości.

Wywoływanie rejestru przy użyciu metadanych właściwości

W poprzednich przykładach wywoływania metody DependencyProperty.Register przekazaliśmy wartość null dla parametru propertyMetadata . Aby umożliwić właściwości zależności podanie wartości domyślnej lub użycie wywołania zwrotnego zmienionej właściwości, należy zdefiniować wystąpienie PropertyMetadata , które zapewnia jedną lub obie te możliwości.

Zazwyczaj należy podać PropertyMetadata jako instancję utworzoną bezpośrednio w kodzie, w parametrach metody DependencyProperty.Register.

Uwaga / Notatka

Jeśli definiujesz implementację CreateDefaultValueCallback , musisz użyć metody narzędzia PropertyMetadata.Create , a nie wywołać konstruktora PropertyMetadata , aby zdefiniować wystąpienie WłaściwościMetadata .

W następnym przykładzie zmodyfikowano wcześniej pokazane przykłady DependencyProperty.Register , odwołując się do wystąpienia PropertyMetadata z wartością PropertyChangedCallback . Implementacja wywołania zwrotnego "OnLabelChanged" zostanie wyświetlona w dalszej części tej sekcji.

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

Wartość domyślna

Można określić wartość domyślną właściwości zależności, tak aby właściwość zawsze zwracała określoną wartość domyślną, gdy jest ona nieskonstawiona. Ta wartość może być inna niż wartość domyślna z natury dla typu tej właściwości.

Jeśli wartość domyślna nie zostanie określona, to dla właściwości zależnej wartością domyślną będzie null w przypadku typu referencyjnego albo prymityw językowy odpowiadający typowi wartości (na przykład 0 dla liczby całkowitej lub pusty ciąg dla typu string). Główną przyczyną ustanowienia wartości domyślnej jest to, że ta wartość jest przywracana podczas wywoływania funkcji ClearValue we właściwości . Ustanowienie wartości domyślnej dla poszczególnych właściwości może być wygodniejsze niż ustanawianie wartości domyślnych w konstruktorach, szczególnie w przypadku typów wartości. Jednak w przypadku typów referencyjnych upewnij się, że ustanowienie wartości domyślnej nie tworzy niezamierzonego wzorca singletonu. Aby uzyskać więcej informacji, zobacz Najlepsze rozwiązania w dalszej części tego tematu

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

Uwaga / Notatka

Nie rejestruj się przy użyciu wartości domyślnej UnsetValue. Jeśli to zrobisz, będzie to mylić osoby korzystające z nieruchomości i będzie miało niezamierzone konsekwencje w ramach systemu nieruchomości.

CreateDefaultValueCallback

W niektórych scenariuszach definiujesz właściwości zależności dla obiektów używanych w więcej niż jednym wątku interfejsu użytkownika. Może to być przypadek, jeśli definiujesz obiekt danych używany przez wiele aplikacji lub kontrolkę używaną w więcej niż jednej aplikacji. Możesz włączyć wymianę obiektu między różnymi wątkami interfejsu użytkownika, udostępniając implementację CreateDefaultValueCallback , a nie domyślne wystąpienie wartości, które jest powiązane z wątkiem, który zarejestrował właściwość. Zasadniczo element CreateDefaultValueCallback definiuje fabrykę wartości domyślnych. Wartość zwracana przez CreateDefaultValueCallback jest zawsze skojarzona z bieżącym wątkiem interfejsu użytkownika używającym obiektu w CreateDefaultValueCallback.

Aby zdefiniować metadane określające element CreateDefaultValueCallback, należy wywołać metodę PropertyMetadata.Create , aby zwrócić wystąpienie metadanych; Konstruktory PropertyMetadata nie mają podpisu zawierającego parametr CreateDefaultValueCallback .

Typowy wzorzec implementacji dla klasy CreateDefaultValueCallback polega na utworzeniu nowej klasy DependencyObject , ustawieniu określonej wartości właściwości każdej właściwości obiektu DependencyObject na wartość domyślną, a następnie zwrócenie nowej klasy jako odwołania do obiektu za pośrednictwem wartości zwracanej metody CreateDefaultValueCallback .

Metoda wywołania zwrotnego przy zmianie właściwości

Można zdefiniować metodę wywołania zwrotnego zmiany właściwości, aby zdefiniować interakcje właściwości z innymi właściwościami zależności lub zaktualizować właściwość wewnętrzną lub stan obiektu za każdym razem, gdy właściwość ulegnie zmianie. Jeśli wywołanie zwrotne nastąpi, system właściwości ustalił, że istnieje skuteczna zmiana wartości właściwości. Ponieważ metoda wywołania zwrotnego jest statyczna, parametr d wywołania zwrotnego jest ważny, ponieważ informuje o tym, które wystąpienie klasy zgłosiło zmianę. Typowa implementacja używa właściwości NewValue danych zdarzeń i przetwarza te wartości w jakiś sposób, zwykle wykonując jakąś inną zmianę w obiekcie przekazanym jako d. Dodatkowe odpowiedzi na zmianę właściwości mają na celu odrzucenie wartości zgłoszonej przez wartość NewValue, przywrócenie wartości OldValue lub ustawienie wartości na ograniczenie programowe zastosowane do wartości NewValue.

W następnym przykładzie przedstawiono implementację PropertyChangedCallback . Implementuje metodę, do której odwoływałeś się w poprzednich przykładach Register, jako część argumentów konstrukcyjnych PropertyMetadata. Scenariusz rozwiązany przez to wywołanie zwrotne polega na tym, że klasa ma również obliczoną właściwość tylko do odczytu o nazwie "HasLabelValue" (implementacja nie jest pokazana). Za każdym razem, gdy właściwość "Label" zostanie ponownie oszacowana, ta metoda wywołania zwrotnego jest wywoływana, a wywołanie zwrotne umożliwia zachowanie zależnej wartości obliczeniowej w synchronizacji ze zmianami właściwości zależności.

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

Zmiana zachowania właściwości dla struktur i enumów

Jeśli typ właściwości DependencyProperty jest wyliczeniem lub strukturą, wywołanie zwrotne może być wywoływane nawet wtedy, gdy wartości wewnętrzne struktury lub wartości wyliczenia nie uległy zmianie. Różni się to od elementu pierwotnego systemu, takiego jak ciąg, w którym jest wywoływany tylko wtedy, gdy wartość została zmieniona. Jest to efekt uboczny operacji pakowania i rozpakowywania na tych danych, które są wykonywane wewnętrznie. Jeśli masz metodę PropertyChangedCallback dla właściwości, w której wartość jest wyliczeniem lub strukturą, musisz porównać wartości OldValue i NewValue, rzutując wartości samodzielnie i używając przeciążonych operatorów porównania, które są dostępne dla rzutowanych wartości. Lub jeśli taki operator nie jest dostępny (co może być w przypadku struktury niestandardowej), może być konieczne porównanie poszczególnych wartości. Zazwyczaj nie należy nic robić, jeśli wynikiem jest to, że wartości nie uległy zmianie.

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

Najlepsze rozwiązania

Podczas definiowania niestandardowej właściwości zależności należy wziąć pod uwagę następujące zagadnienia jako najlepsze rozwiązania.

DependencyObject i wątkowanie

Wszystkie wystąpienia obiektu DependencyObject należy utworzyć w wątku interfejsu użytkownika, który jest skojarzony z bieżącym oknem wyświetlanym przez aplikację Środowiska uruchomieniowego systemu Windows. Mimo że każdy obiekt DependencyObject musi zostać utworzony w głównym wątku interfejsu użytkownika, można uzyskać dostęp do obiektów przy użyciu odwołania dyspozytora z innych wątków, wywołując metodę Dispatcher.

Aspekty wątkowe obiektu DependencyObject są istotne, ponieważ zazwyczaj oznacza to, że tylko kod uruchamiany w wątku interfejsu użytkownika może ulec zmianie, a nawet odczytać wartość właściwości zależności. Problemy z wątkami można zwykle uniknąć w typowym kodzie interfejsu użytkownika przy poprawnym użyciu wzorców asynchronicznych i wątków roboczych w tle. Zazwyczaj natrafiasz na problemy z wątkami związanymi z DependencyObject, jeśli definiujesz własne typy DependencyObject i próbujesz ich używać w przypadku źródeł danych lub innych scenariuszy, gdzie DependencyObject nie jest koniecznie odpowiedni.

Unikanie niezamierzonych singletonów

Niezamierzony singleton może wystąpić, jeśli deklarujesz właściwość zależności, która przyjmuje typ referencyjny, i wywołujesz konstruktor tego typu referencyjnego jako część kodu, który ustanawia PropertyMetadata. Dzieje się tak, że wszystkie użycia właściwości zależności współdzielą tylko jedno wystąpienie PropertyMetadata i tym samym próbują współdzielić pojedynczy typ odniesienia, który został utworzony. Wszelkie podwłaściwości tego typu wartości ustawione za pośrednictwem właściwości zależności są następnie propagowane do innych obiektów w sposób, który może nie być zamierzony.

Konstruktorów klas można użyć do ustawiania wartości początkowych dla właściwości zależności typu odwołania, jeśli chcesz mieć wartość inną niż null, ale należy pamiętać, że będzie to uważane za wartość lokalną na potrzeby przeglądu właściwości zależności. Bardziej odpowiednie może być użycie szablonu do tego celu, jeśli klasa obsługuje szablony. Innym sposobem uniknięcia wzorca singletona, ale nadal zapewniając użyteczną wartość domyślną, jest udostępnienie właściwości statycznej w typie referencyjnym, która zapewnia odpowiednią wartość domyślną dla tej klasy.

Właściwości zależności typu kolekcji

Właściwości zależności typu kolekcji mają pewne dodatkowe problemy z implementacją, które należy wziąć pod uwagę.

Właściwości zależności typu kolekcji są stosunkowo rzadkie w interfejsie API środowiska uruchomieniowego systemu Windows. W większości przypadków można użyć kolekcji, w których elementy są podklasą DependencyObject , ale sama właściwość kolekcji jest implementowana jako konwencjonalna właściwość CLR lub C++. Dzieje się tak, ponieważ kolekcje nie muszą odpowiadać typowym scenariuszom, w których są zaangażowane właściwości zależności. Przykład:

  • Zwykle nie animujesz kolekcji.
  • Zazwyczaj elementy w kolekcji nie są wypełniane wstępnie za pomocą stylów ani szablonu.
  • Chociaż powiązanie z kolekcjami jest głównym scenariuszem, kolekcja nie musi być właściwością zależności, aby mogła być źródłem powiązania. W przypadku celów powiązań bardziej typowe jest użycie podklas ItemsControl lub DataTemplate do obsługi elementów kolekcji, lub stosowanie wzorców projektowych modelu-widoku. Aby uzyskać więcej informacji na temat powiązań do i z kolekcji, zobacz Szczegółowe powiązanie danych.
  • Powiadomienia dotyczące zmian kolekcji są lepiej rozwiązywane za pośrednictwem interfejsów, takich jak INotifyPropertyChanged lub INotifyCollectionChanged, lub przez wyprowadzenie typu kolekcji z ObservableCollection<T>.

Niemniej jednak istnieją scenariusze dotyczące właściwości zależności typu kolekcji. W następnych trzech sekcjach przedstawiono wskazówki dotyczące implementowania właściwości zależności typu kolekcji.

Inicjowanie kolekcji

Podczas tworzenia właściwości zależności można ustanowić wartość domyślną za pomocą metadanych właściwości zależności. Należy jednak uważać, aby nie używać pojedynczej kolekcji statycznej jako wartości domyślnej. Zamiast tego należy świadomie ustawić wartość kolekcji na unikatową kolekcję (wystąpienie) jako część logiki konstruktora klasy dla klasy, która jest właścicielem właściwości kolekcji.

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

Właściwość DependencyProperty i jej wartość domyślna z PropertyMetadata są częścią statycznej definicji DependencyProperty. Podając domyślną wartość kolekcji (lub inną wartość instancjonowaną) jako wartość domyślną, będzie ona współdzielona przez wszystkie instancje klasy, zamiast tego, aby każda instancja klasy miała swoją własną kolekcję, co zwykle jest pożądane.

Zmienianie powiadomień

Definiowanie kolekcji jako właściwości zależności nie powoduje automatycznego powiadomienia o zmianie elementów w kolekcji z powodu wywołania metody wywołania zwrotnego "PropertyChanged". Jeśli chcesz otrzymywać powiadomienia dotyczące kolekcji lub elementów kolekcji — na przykład dla scenariusza powiązania danych — zaimplementuj interfejs INotifyPropertyChanged lub INotifyCollectionChanged . Aby uzyskać więcej informacji, zobacz Szczegółowe powiązanie danych.

Zagadnienia dotyczące zabezpieczeń właściwości powiązanych

Zadeklaruj właściwości zależności jako właściwości publiczne. Zadeklaruj identyfikatory właściwości zależności jako publiczne statyczne elementy członkowskie do odczytu . Nawet jeśli próbujesz zadeklarować inne poziomy dostępu dozwolone przez język (na przykład chroniony), zawsze można uzyskać dostęp do właściwości zależności za pośrednictwem identyfikatora w połączeniu z interfejsami API systemu właściwości. Deklarowanie identyfikatora właściwości zależności jako wewnętrznego lub prywatnego nie będzie działać, ponieważ system właściwości nie może działać prawidłowo.

Właściwości wrapperów służą jedynie dla wygody, a mechanizmy zabezpieczające stosowane do wrapperów można obejść, wywołując zamiast tego GetValue lub SetValue. Dlatego zachowaj publiczne właściwości obiektu opakowującego; w przeciwnym razie utrudniasz prawidłowym użytkownikom korzystanie z właściwości, nie zapewniając żadnych rzeczywistych korzyści w zakresie bezpieczeństwa.

Środowisko uruchomieniowe systemu Windows nie zapewnia sposobu rejestrowania niestandardowej właściwości zależności jako tylko do odczytu.

Właściwości zależności i konstruktory klas

Istnieje ogólna zasada, że konstruktory klas nie powinny wywoływać metod wirtualnych. Jest to spowodowane tym, że konstruktory mogą być wywoływane w celu wykonania inicjowania podstawowego konstruktora klasy pochodnej, a wprowadzanie metody wirtualnej za pomocą konstruktora może wystąpić, gdy tworzone wystąpienie obiektu nie jest jeszcze całkowicie zainicjowane. Gdy dziedziczysz z dowolnej klasy, która już dziedziczy z klasy DependencyObject, pamiętaj, że sam system właściwości wywołuje i udostępnia metody wirtualne wewnętrznie w ramach swoich usług. Aby uniknąć potencjalnych problemów z inicjowaniem w czasie wykonywania, nie ustawiaj wartości właściwości zależności w konstruktorach klas.

Rejestrowanie właściwości zależności dla aplikacji C++/CX

Implementacja rejestrowania właściwości w języku C++/CX jest trudniejsza niż C#, zarówno ze względu na rozdzielenie pliku nagłówka i implementacji, jak i dlatego, że inicjowanie w głównym zakresie pliku implementacji jest złą praktyką. (Rozszerzenia składników języka Visual C++ (C++/CX) umieszczają kod inicjalizatora statycznego z zakresu głównego bezpośrednio do DllMain, podczas gdy kompilatory języka C# przypisują inicjatory statyczne do klas, a tym samym unikają problemów z blokowaniem w DllMain.) Najlepszym rozwiązaniem jest zadeklarowanie funkcji pomocniczej, która wykonuje całą rejestrację właściwości zależności dla klasy, jedną funkcję na klasę. Następnie dla każdej klasy niestandardowej używanej przez aplikację należy odwołać się do funkcji rejestracji pomocnika uwidocznionej przez każdą klasę niestandardową, której chcesz użyć. Wywołaj każdą funkcję rejestracji pomocnika raz w ramach konstruktora aplikacji (App::App()), przed InitializeComponent. Ten konstruktor działa tylko wtedy, gdy aplikacja jest naprawdę przywoływane po raz pierwszy, nie będzie działać ponownie, jeśli na przykład wstrzymana aplikacja zostanie wznowiona. Ponadto, jak pokazano w poprzednim przykładzie rejestracji w języku C++, sprawdzanie wartości nullptr wokół każdego wywołania rejestru jest ważne: jest to ubezpieczenie, że żaden obiekt wywołujący funkcji nie może zarejestrować nieruchomości dwa razy. Bez takiego sprawdzenia drugie wywołanie rejestracji prawdopodobnie spowodowałoby awarię aplikacji, ponieważ nazwa właściwości byłaby duplikatem. Ten wzorzec implementacji można zobaczyć w przykładzie dla użytkownika XAML i kontrolek niestandardowych , jeśli przyjrzysz się kodowi wersji języka C++/CX przykładu.