Niestandardowe właściwości zależności (WPF .NET)

Windows Presentation Foundation (WPF) deweloperzy aplikacji i autorzy składników mogą tworzyć niestandardowe właściwości zależności, aby rozszerzyć funkcjonalność swoich właściwości. W przeciwieństwie do właściwości środowiska uruchomieniowego języka wspólnego (CLR ), właściwość zależności dodaje obsługę stylów, powiązań danych, dziedziczenia, animacji i wartości domyślnych. Background, Widthi są przykładami Text istniejących właściwości zależności w klasach WPF. W tym artykule opisano sposób implementowania niestandardowych właściwości zależności oraz przedstawiono opcje poprawy wydajności, użyteczności i użyteczności.

Ważne

Dokumentacja przewodników klasycznych dla platform .NET 6 i .NET 5 (w tym .NET Core 3.1) jest w trakcie budowy.

Wymagania wstępne

W tym artykule przyjęto założenie, że masz podstawową wiedzę na temat właściwości zależności oraz że przeczytaliśmy artykuł Przegląd właściwości zależności. Aby postępować zgodnie z przykładami w tym artykule, warto zapoznać się z Extensible Application Markup Language (XAML) i dowiedzieć się, jak pisać aplikacje WPF.

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

Właściwości zależności to właściwości, które są rejestrowane w systemie właściwości WPF za pośrednictwem wywołań Register lub RegisterReadOnly . Metoda Register zwraca wystąpienie DependencyProperty , które zawiera zarejestrowaną nazwę i właściwości zależności. Przypiszesz wystąpienie do DependencyProperty statycznego pola tylko do odczytu, nazywanego identyfikatorem właściwości zależności, które zgodnie z konwencją nosi nazwę <property name>Property. Na przykład pole identyfikatora dla właściwości Background to zawsze BackgroundProperty.

Identyfikator właściwości zależności jest używany jako pole zapasowe do uzyskiwania lub ustawiania wartości właściwości, a nie standardowego wzorca kopii zapasowej właściwości przy użyciu pola prywatnego. System właściwości nie tylko używa identyfikatora, ale również może go używać procesory XAML, a kod (i prawdopodobnie kod zewnętrzny) może uzyskać dostęp do właściwości zależności za pośrednictwem ich identyfikatorów.

Właściwości zależności można stosować tylko do klas pochodzących z typów DependencyObject . Większość klas WPF obsługuje właściwości zależności, ponieważ znajduje DependencyObject się blisko katalogu głównego hierarchii klas WPF. Aby uzyskać więcej informacji na temat właściwości zależności oraz terminologii i konwencji używanych do ich opisywania, zobacz Omówienie właściwości zależności.

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

Właściwości zależności WPF, które nie są dołączone, są udostępniane przez otokę CLR, która implementuje i getset klasy dostępu. Za pomocą otoki właściwości konsumenci właściwości zależności mogą pobrać lub ustawić wartości właściwości zależności, tak jak każdą inną właściwość CLR. Klasy get dostępu i set wchodzą w interakcję z bazowym systemem właściwości za DependencyObject.GetValueDependencyObject.SetValue pośrednictwem wywołań i , przekazując identyfikator właściwości zależności jako parametr. Użytkownicy właściwości zależności GetValueSetValue zwykle nie wywołują metody ani bezpośrednio, ale jeśli wdrażasz niestandardową właściwość zależności, użyjesz tych metod w otoce.

Kiedy należy zaimplementować właściwość zależności

Podczas implementowania właściwości w klasie DependencyObject, która pochodzi od klasy , należy ją uczynić właściwością zależności, kopiując ją za pomocą identyfikatora DependencyProperty . To, czy korzystne jest utworzenie właściwości zależności, zależy od scenariusza. Chociaż obsługa właściwości przy użyciu pola prywatnego jest odpowiednia dla niektórych scenariuszy, rozważ zaimplementowanie właściwości zależności, jeśli chcesz, aby właściwość obsługiła co najmniej jedną z następujących możliwości WPF:

  • Właściwości, które można ustawiać w stylu. Aby uzyskać więcej informacji, zobacz Style i szablony.

  • Właściwości, które obsługują powiązanie danych. Aby uzyskać więcej informacji na temat właściwości zależności powiązania danych, zobacz Wiązanie właściwości dwóch kontrolek.

  • Właściwości, które można ustawiać za pomocą dynamicznych odwołań do zasobów. Aby uzyskać więcej informacji, zobacz zasoby XAML.

  • Właściwości, które automatycznie dziedziczą ich wartość z elementu nadrzędnego w drzewie elementów. W tym celu należy zarejestrować się przy użyciu funkcji RegisterAttached, nawet jeśli utworzysz również otokę właściwości na potrzeby dostępu do clr. Aby uzyskać więcej informacji, zobacz Dziedziczenie wartości właściwości.

  • Właściwości, które można animować. Aby uzyskać więcej informacji, zobacz Przegląd animacji.

  • Powiadomienie przez system właściwości WPF o zmianie wartości właściwości. Zmiany mogą być spowodowane działaniami systemu właściwości, środowiska, użytkownika lub stylów. Właściwość może określić metodę wywołania zwrotnego w metadanych właściwości, która będzie wywoływana za każdym razem, gdy system właściwości określi, że wartość właściwości uległa zmianie. Powiązaną koncepcją jest koercja wartości właściwości. Aby uzyskać więcej informacji, zobacz Wywołania zwrotne i walidacja właściwości zależności.

  • Dostęp do metadanych właściwości zależności, które są odczytywane przez procesy WPF. Na przykład można użyć metadanych właściwości, aby:

    • Określ, czy zmieniona wartość właściwości zależności powinna sprawić, że system układu ponownie skomponuje wizualizacje dla elementu.

    • Ustaw wartość domyślną właściwości zależności przez zastąpienie metadanych w klasach pochodnych.

  • Visual Studio obsługę projektanta WPF, na przykład edytowanie właściwości kontrolki niestandardowej w oknie Właściwości. Aby uzyskać więcej informacji, zobacz Control authoring overview (Omówienie tworzenia kontrolek).

W niektórych scenariuszach zastępowanie metadanych istniejącej właściwości zależności jest lepszym rozwiązaniem niż implementowanie nowej właściwości zależności. To, czy zastąpienie metadanych jest praktyczne, zależy od scenariusza i tego, jak blisko ten scenariusz przypomina implementację istniejących właściwości i klas zależności WPF. Aby uzyskać więcej informacji na temat zastępowania metadanych na istniejących właściwościach zależności, zobacz Metadane właściwości zależności.

Lista kontrolna tworzenia właściwości zależności

Wykonaj następujące kroki, aby utworzyć właściwość zależności. Niektóre kroki można połączyć i zaimplementować w jednym wierszu kodu.

  1. (Opcjonalnie) Utwórz metadane właściwości zależności.

  2. Zarejestruj właściwość zależności w systemie właściwości, określając nazwę właściwości, typ właściciela, typ wartości właściwości i opcjonalnie metadane właściwości.

  3. Zdefiniuj DependencyProperty identyfikator jako public static readonly pole dla typu właściciela. Nazwa pola identyfikatora jest nazwą właściwości z dołączonym sufiksem Property .

  4. Zdefiniuj właściwość otoki CLR o takiej samej nazwie jak nazwa właściwości zależności. W otoce CLR zaim implementacji i getset accessors, które łączą się z właściwością zależności, która jest kopią zapasową otoki.

Rejestrowanie właściwości

Aby właściwość była właściwością zależności, należy zarejestrować ją w systemie właściwości. Aby zarejestrować właściwość, wywołaj Register metodę z wewnątrz treści klasy, ale poza definicjami członków. Metoda Register zwraca unikatowy identyfikator właściwości zależności, który będzie używany podczas wywoływania interfejsu API systemu właściwości. Przyczyną, dla którego wywołanie Register jest wykonane poza definicjami członków, jest przypisanie wartości zwracanej do public static readonly pola typu DependencyProperty. To pole, które utworzysz w klasie, jest identyfikatorem właściwości zależności. W poniższym przykładzie pierwszy argument nazwy Register właściwości zależności AquariumGraphicto .

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

Uwaga

Definiowanie właściwości zależności w treści klasy jest typową implementacją, ale istnieje również możliwość zdefiniowania właściwości zależności w konstruktorze statycznym klasy. Takie podejście może mieć sens, jeśli do zainicjowania właściwości zależności jest potrzebny więcej niż jeden wiersz kodu.

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

Ustanowiona konwencja nazewnictwa właściwości zależności jest obowiązkowa dla normalnego zachowania systemu właściwości. Nazwa pola identyfikatora, które tworzysz, musi być zarejestrowaną nazwą właściwości z sufiksem Property.

Nazwa właściwości zależności musi być unikatowa w obrębie klasy rejestracji. Właściwości zależności dziedziczone przez typ podstawowy zostały już zarejestrowane i nie mogą być rejestrowane przez typ pochodny. Można jednak użyć właściwości zależności, która została zarejestrowana przez inny typ, nawet typ, z których klasa nie dziedziczy, dodając klasę jako właściciela właściwości zależności. Aby uzyskać więcej informacji na temat dodawania klasy jako właściciela, zobacz Metadane właściwości zależności.

Implementowanie otoki właściwości

Zgodnie z konwencją nazwa właściwości otoki musi Register być taka sama jak pierwszy parametr wywołania, który jest nazwą właściwości zależności. Implementacja otoki wywoła w GetValue accessor get i SetValue w accessor set (dla właściwości odczytu i zapisu). W poniższym przykładzie przedstawiono otokę — po wywołaniu rejestracji i deklaracji pola identyfikatora. Wszystkie właściwości zależności publicznych w klasach WPF używają podobnego modelu otoki.

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );

// Declare a read-write property wrapper.
public Uri AquariumGraphic
{
    get => (Uri)GetValue(AquariumGraphicProperty);
    set => SetValue(AquariumGraphicProperty, value);
}
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

' Declare a read-write property wrapper.
Public Property AquariumGraphic As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set
        SetValue(AquariumGraphicProperty, Value)
    End Set
End Property

Z wyjątkiem rzadkich przypadków implementacja otoki powinna zawierać tylko kod GetValueSetValue i . Aby uzyskać informacje na ten temat, zobacz Implikacje dotyczące właściwości zależności niestandardowych.

Jeśli właściwość nie jest przestrzegana ustalonych konwencji nazewnictwa, mogą wystąpić następujące problemy:

  • Niektóre aspekty stylów i szablonów nie będą działać.

  • Większość narzędzi i projektantów korzysta z konwencji nazewnictwa, aby prawidłowo serializować kod XAML i zapewniać pomoc w środowisku projektanta na poziomie właściwości.

  • Bieżąca implementacja modułu ładującego XAML WPF całkowicie pomija otoki i opiera się na konwencji nazewnictwa w celu przetwarzania wartości atrybutów. Aby uzyskać więcej informacji, zobacz Ładowanie kodu XAML i właściwości zależności.

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

Podczas rejestrowania właściwości zależności system właściwości tworzy obiekt metadanych do przechowywania właściwości. Przeciążenia metody Register umożliwiają określenie metadanych właściwości podczas rejestracji, na przykład Register(String, Type, Type, PropertyMetadata). Typowe zastosowanie metadanych właściwości to zastosowanie niestandardowej wartości domyślnej dla nowych wystąpień, które używają właściwości zależności. Jeśli metadane właściwości nie zostaną podane, system właściwości przypisze wartości domyślne do wielu właściwości zależności.

Jeśli tworzysz właściwość FrameworkElementzależności dla klasy pochodzącej z klasy , FrameworkPropertyMetadata możesz użyć bardziej wyspecjalizowanej klasy metadanych, a nie jej klasy bazowej PropertyMetadata. Kilka FrameworkPropertyMetadata podpisów konstruktorów umożliwia określenie różnych kombinacji właściwości metadanych. Jeśli chcesz tylko określić wartość domyślną, FrameworkPropertyMetadata(Object) użyj wartości i przekaż wartość domyślną do parametru Object . Upewnij się, że typ wartości jest taki, jak propertyType określony w wywołaniu Register .

Niektóre FrameworkPropertyMetadata przeciążenia umożliwiają określenie flag opcji metadanych dla właściwości. System właściwości konwertuje te flagi na właściwości dyskretne, a wartości flag są używane przez procesy WPF, takie jak aparat układu.

Ustawianie flag metadanych

Podczas ustawiania flag metadanych należy wziąć pod uwagę następujące kwestie:

  • Jeśli wartość właściwości (lub jej zmiana) wpływa na sposób renderowania elementu interfejsu użytkownika przez system układu, należy ustawić co najmniej jedną z następujących flag:

    • AffectsMeasure, co oznacza, że zmiana wartości właściwości wymaga zmiany renderowania interfejsu użytkownika, a w szczególności miejsca zajmowanego przez obiekt w ramach jego elementu nadrzędnego. Na przykład ustaw tę flagę metadanych dla Width właściwości.

    • AffectsArrange, co oznacza, że zmiana wartości właściwości wymaga zmiany renderowania interfejsu użytkownika, a w szczególności położenia obiektu w ramach jego elementu nadrzędnego. Zazwyczaj rozmiar obiektu również nie zmienia się. Na przykład ustaw tę flagę metadanych dla Alignment właściwości.

    • AffectsRender, co oznacza, że nastąpiła zmiana, która nie ma wpływu na układ i miarę, ale nadal wymaga innego renderowania. Na przykład ustaw tę flagę dla właściwości Background lub dowolną inną właściwość, która ma wpływ na kolor elementu.

    Możesz również użyć tych flag jako danych wejściowych dla implementacji przesłonięcia wywołań zwrotnych systemu właściwości (lub układu). Na przykład możesz użyć wywołania zwrotnego OnPropertyChangedInvalidateArrangeAffectsArrange , aby wywołać wywołanie , gdy właściwość wystąpienia zgłasza zmianę wartości i została ustawiona w metadanych.

  • Niektóre właściwości wpływają na właściwości renderowania ich elementu nadrzędnego w inny sposób. Na przykład zmiany właściwości mogą MinOrphanLines zmienić ogólne renderowanie dokumentu przepływu. Użyj AffectsParentArrange lub AffectsParentMeasure , aby zasygnalizować akcje nadrzędne we własnych właściwościach.

  • Domyślnie właściwości zależności obsługują powiązanie danych. Można jednak użyć funkcji IsDataBindingAllowed , aby wyłączyć powiązanie danych, gdy nie ma realistycznego scenariusza lub gdy wydajność powiązania danych jest problematyczna, na przykład w przypadku dużych obiektów.

  • Mimo że domyślny tryb powiązania danych dla właściwości zależności OneWayto , TwoWaytryb powiązania określonego powiązania można zmienić na . Aby uzyskać więcej informacji, zobacz Binding direction (Kierunek powiązania). Jako autor właściwości zależności możesz nawet utworzyć powiązanie dwukierunkowe w trybie domyślnym. Przykładem istniejącej właściwości zależności MenuItem.IsSubmenuOpen, która używa powiązania danych dwukierunkowego, jest , która ma stan oparty na innych właściwościach i wywołaniach metod. Scenariusz jest taki IsSubmenuOpen , że jego logika ustawień i składanie MenuItem, współdziałają z domyślnym stylem motywu. TextBox.Text jest inną właściwością zależności WPF, która domyślnie używa powiązania dwukierunkowego.

  • Dziedziczenie właściwości dla właściwości zależności można włączyć, ustawiając flagę Inherits . Dziedziczenie właściwości jest przydatne w scenariuszach, w których elementy nadrzędne i podrzędne mają wspólną właściwość i warto, aby element podrzędny dziedziczył wartość nadrzędną wspólnej właściwości. Przykładem właściwości dziedzicznej jest DataContext, która obsługuje operacje powiązania, które używają scenariusza wzorzec-szczegół do prezentacji danych. Dziedziczenie wartości właściwości umożliwia określenie kontekstu danych na stronie lub w katalogu głównym aplikacji, co pozwala zaoszczędzić na konieczności określenia go dla powiązań elementu podrzędnego. Mimo że dziedziczona wartość właściwości zastępuje wartość domyślną, wartości właściwości można ustawić lokalnie na dowolnym elemencie podrzędnym. Dziedziczenie wartości właściwości jest używane oszczędnie, ponieważ ma koszt wydajności. Aby uzyskać więcej informacji, zobacz Dziedziczenie wartości właściwości.

  • Ustaw flagę Journal , aby wskazać, że właściwość zależności powinna zostać wykryta lub użyta przez usługi dziennika nawigacji. Na przykład właściwość ustawia SelectedIndex flagę Journal , aby zalecić aplikacjom zachowanie wybranej historii dzienników elementów.

Właściwości zależności tylko do odczytu

Można zdefiniować właściwość zależności, która jest tylko do odczytu. Typowy scenariusz to właściwość zależności, która przechowuje stan wewnętrzny. Na przykład jest tylko IsMouseOver do odczytu, ponieważ jego stan powinien być określany tylko przez dane wejściowe myszy. Aby uzyskać więcej informacji, zobacz Właściwości zależności tylko do odczytu.

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

Właściwości zależności typu kolekcji mają dodatkowe problemy z implementacją, które należy wziąć pod uwagę, takie jak ustawienie wartości domyślnej dla typów referencyjnych i obsługa powiązań danych dla elementów kolekcji. Aby uzyskać więcej informacji, zobacz Właściwości zależności typu kolekcji.

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

Zazwyczaj właściwości zależności deklaruje się jako właściwości publiczne, DependencyProperty a pola identyfikatorów jako public static readonly pola. Jeśli określisz bardziej restrykcyjny poziom dostępu, protectedtaki jak , właściwość zależności będzie nadal dostępna za pośrednictwem jej identyfikatora w połączeniu z interfejsami API systemu właściwości. Nawet chronione pole identyfikatora jest potencjalnie dostępne za pośrednictwem interfejsów API raportowania metadanych WPF lub określania wartości, takich jak LocalValueEnumerator. Aby uzyskać więcej informacji, zobacz Zabezpieczenia właściwości zależności.

W przypadku właściwości zależności tylko do RegisterReadOnlyDependencyPropertyKeyodczytu wartość zwracana przez typ to , a zazwyczaj nie należy do DependencyPropertyKeypublic klasy. Ponieważ system właściwości WPF DependencyPropertyKey nie propaguje kodu poza kodem, set właściwość zależności tylko do odczytu ma lepsze zabezpieczenia niż właściwość zależności do odczytu i zapisu.

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

Istnieje ogólna zasada w programowaniu kodu zarządzanego, często wymuszana przez narzędzia do analizy kodu, że konstruktory klasy nie powinny wywołać metod wirtualnych. Wynika to z tego, że konstruktory podstawowe mogą być wywoływane podczas inicjowania konstruktora klasy pochodnej, a metoda wirtualna wywoływana przez konstruktor podstawowy może zostać uruchomiony przed ukończeniem inicjowania klasy pochodnej. W przypadku wyprowadzenia z klasy, która DependencyObjectjuż pochodzi od klasy , system właściwości wywołuje i uwidacznia metody wirtualne wewnętrznie. Te metody wirtualne są częścią usług systemu właściwości WPF. Zastępowanie metod umożliwia klasom pochodnym uczestnictwo w określaniu wartości. Aby uniknąć potencjalnych problemów z inicjowaniem środowiska uruchomieniowego, nie należy ustawiać wartości właściwości zależności w konstruktorach klas, chyba że jest przestrzegany określony wzorzec konstruktora. Aby uzyskać więcej informacji, zobacz Sejf konstruktorów dla dependencyObjects.

Zobacz też