Właściwości zależności niestandardowych (WPF .NET)

Deweloperzy aplikacji i autorzy składników programu Windows Presentation Foundation (WPF) mogą tworzyć niestandardowe właściwości zależności w celu rozszerzenia funkcjonalności ich 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ązania danych, dziedziczenia, animacji i wartości domyślnych. Background, Widthi Text to przykłady 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 wszechstronności.

Ważne

Dokumentacja przewodnika dla komputerów dla platform .NET 7 i .NET 6 jest w budowie.

Wymagania wstępne

W tym artykule przyjęto założenie, że masz podstawową wiedzę na temat właściwości zależności i zapoznasz się z omówieniem właściwości zależności. Aby postępować zgodnie z przykładami w tym artykule, warto zapoznać się z językiem Extensible Application Markup Language (XAML) i wiedzieć, jak pisać aplikacje WPF.

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

Właściwości zależności to właściwości zarejestrowane w systemie właściwości WPF za pośrednictwem Register lub RegisterReadOnly wywołania. Metoda Register zwraca wystąpienie, które zawiera zarejestrowaną DependencyProperty nazwę i cechy właściwości zależności. Wystąpienie zostanie przypisane DependencyProperty do statycznego pola tylko do odczytu, znanego jako identyfikator właściwości zależności, który zgodnie z konwencją nosi nazwę <property name>Property. Na przykład pole identyfikatora Background właściwości to zawsze BackgroundProperty.

Identyfikator właściwości zależności jest używany jako pole zapasowe do pobierania lub ustawiania wartości właściwości, a nie standardowego wzorca tworzenia kopii zapasowej właściwości z polem prywatnym. Nie tylko system właściwości używa identyfikatora, procesory XAML mogą go używać, a kod (i prawdopodobnie kod zewnętrzny) może uzyskiwać 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, które pochodzą z DependencyObject typów. Większość klas WPF obsługuje właściwości zależności, ponieważ DependencyObject znajduje 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 właściwości, są udostępniane przez otokę CLR, która implementuje get i set metody dostępu. Korzystając z otoki właściwości, użytkownicy właściwości zależności mogą pobierać lub ustawiać wartości właściwości zależności, tak samo jak każda inna właściwość CLR. Metody get i set współdziałają z podstawowym systemem właściwości za pomocą wywołań DependencyObject.GetValue i DependencyObject.SetValue przekazując identyfikator właściwości zależności jako parametr. Użytkownicy właściwości zależności zwykle nie wywołuje GetValue ani SetValue bezpośrednio, ale jeśli implementujesz niestandardową właściwość zależności, użyjesz tych metod w otoce.

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

Podczas implementowania właściwości w klasie, która pochodzi z DependencyObjectklasy , należy ją utworzyć jako właściwość zależności, tworząc kopię zapasową właściwości za pomocą identyfikatora DependencyProperty . Niezależnie od tego, czy korzystne jest utworzenie właściwości zależności, zależy od danego scenariusza. Mimo że tworzenie kopii zapasowej właściwości z polem prywatnym jest odpowiednie dla niektórych scenariuszy, rozważ zaimplementowanie właściwości zależności, jeśli chcesz, aby właściwość obsługiwała co najmniej jedną z następujących funkcji WPF:

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

  • Właściwości obsługujące 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 ustawić za pomocą odwołań do zasobów dynamicznych. 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 metody RegisterAttached, nawet jeśli utworzysz również otokę właściwości na potrzeby dostępu CLR. Aby uzyskać więcej informacji, zobacz Dziedziczenie wartości właściwości.

  • Właściwości, które są animatowalne. Aby uzyskać więcej informacji, zobacz Omówienie animacji.

  • Powiadomienie przez system właściwości WPF po zmianie wartości właściwości. Zmiany mogą być spowodowane akcjami 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óre będą wywoływane za każdym razem, gdy system właściwości określi, że wartość właściwości uległa zmianie. Powiązana koncepcja to przymus wartości właściwości. Aby uzyskać więcej informacji, zobacz Wywołania zwrotne właściwości zależności i walidacja.

  • Dostęp do metadanych właściwości zależności odczytywanych przez procesy WPF. Na przykład można użyć metadanych właściwości do:

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

    • Ustaw wartość domyślną właściwości zależności, przesłaniając metadane dla klas pochodnych.

  • Obsługa projektanta WPF programu Visual Studio, na przykład edytowanie właściwości kontrolki niestandardowej w oknie Właściwości . Aby uzyskać więcej informacji, zobacz Omówienie tworzenia kontrolek.

W niektórych scenariuszach zastąpienie metadanych istniejącej właściwości zależności jest lepszym rozwiązaniem niż implementacja nowej właściwości zależności. To, czy zastąpienie metadanych jest praktyczne, zależy od danego scenariusza i tego, jak ściśle 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 dotyczących istniejących właściwości 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) Tworzenie metadanych 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 typu właściciela. Nazwa pola identyfikatora to nazwa 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 zaimplementuj get metody i set metody dostępu łączące się z właściwością zależności, która wspiera otokę.

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ć swoją właściwość, wywołaj metodę Register z wewnątrz treści klasy, ale poza definicjami składowych. 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. Powodem, dla którego wywołanie jest wykonywane poza definicjami elementów członkowskich, jest to, że Register przypisujesz wartość zwracaną public static readonly do 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 Register nazwy właściwości AquariumGraphiczależności .

// 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 potrzebujesz więcej niż jednego wiersza kodu, aby zainicjować właściwość zależności.

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 tworzonego pola identyfikatora 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 rejestrującej. Właściwości zależności dziedziczone za pośrednictwem typu podstawowego zostały już zarejestrowane i nie mogą być zarejestrowane 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órego 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 być taka sama jak pierwszy parametr Register wywołania, który jest nazwą właściwości zależności. Implementacja otoki wywoła GetValue metodę get dostępu i SetValue w set metodzie dostępu (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ć GetValue tylko kod i SetValue . Ze względu na to zobacz Implikacje dla niestandardowych właściwości zależności.

Jeśli właściwość nie jest zgodna z ustalonymi konwencjami 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 opiera się na konwencjach nazewnictwa, aby prawidłowo serializować język XAML i zapewnić pomoc w środowisku projektanta na poziomie poszczególnych właściwości.

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

Metadane zależności właściwoś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 Register metody umożliwiają określenie metadanych właściwości podczas rejestracji, na przykład Register(String, Type, Type, PropertyMetadata). Typowym zastosowaniem metadanych właściwości jest zastosowanie niestandardowej wartości domyślnej dla nowych wystąpień korzystających z właściwości zależności. Jeśli nie podasz metadanych właściwości, system właściwości przypisze wartości domyślne do wielu właściwości właściwości zależności.

Jeśli tworzysz właściwość zależności dla klasy pochodnej z FrameworkElementklasy , możesz użyć bardziej wyspecjalizowanej klasy metadanych, a nie jej klasy FrameworkPropertyMetadataPropertyMetadatabazowej . Kilka FrameworkPropertyMetadata podpisów konstruktorów umożliwia określenie różnych kombinacji cech metadanych. Jeśli chcesz po prostu określić wartość domyślną, użyj FrameworkPropertyMetadata(Object) i przekaż wartość domyślną do parametru Object . Upewnij się, że typ wartości jest zgodny z propertyType określonym 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 zmiany) wpływa na sposób renderowania elementu interfejsu użytkownika przez system układu, ustaw co najmniej jedną z następujących flag:

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

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

    • AffectsRender, co wskazuje, ż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ę Background dla właściwości lub inną właściwość, która ma wpływ na kolor elementu.

    Można również użyć tych flag jako danych wejściowych do zastąpień implementacji wywołań zwrotnych systemu właściwości (lub układu). Możesz na przykład użyć wywołania zwrotnego OnPropertyChanged , aby wywołać InvalidateArrange wywołanie, gdy właściwość wystąpienia zgłasza zmianę wartości i została ustawiona AffectsArrange w metadanych.

  • Niektóre właściwości wpływają na charakterystykę renderowania elementu nadrzędnego na inny sposób. Na przykład zmiany MinOrphanLines właściwości mogą zmienić ogólne renderowanie dokumentu przepływu. Użyj AffectsParentArrange polecenia lub AffectsParentMeasure zasygnalizuj 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ć IsDataBindingAllowed polecenia , 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ślnym trybem powiązania danych dla właściwości zależności jest OneWay, można zmienić tryb powiązania określonego powiązania na TwoWay. Aby uzyskać więcej informacji, zobacz Kierunek wiązania. Jako autor właściwości zależności możesz nawet wybrać powiązanie dwukierunkowe w trybie domyślnym. Przykładem istniejącej właściwości zależności używającej dwukierunkowego powiązania danych jest MenuItem.IsSubmenuOpen, która ma stan oparty na innych właściwościach i wywołaniach metod. W tym IsSubmenuOpen scenariuszu jest to, że jego logika ustawień i komponowanie MenuItemelementu współdziałają z domyślnym stylem motywu. TextBox.Text to kolejna właściwość zależności WPF, która domyślnie używa powiązania dwukierunkowego.

  • Możesz włączyć dziedziczenie właściwości dla właściwości zależności, 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 ma sens, aby element podrzędny dziedziczył wartość nadrzędną dla właściwości wspólnej. Przykładem właściwości dziedziczonej jest DataContext, która obsługuje operacje powiązań, które używają scenariusza szczegółów wzorca na potrzeby prezentacji danych. Dziedziczenie wartości właściwości umożliwia określenie kontekstu danych na stronie lub katalogu głównym aplikacji, co powoduje zapisanie konieczności określenia go dla powiązań elementów podrzędnych. 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 elemenie podrzędnym. Użyj dziedziczenia wartości właściwości 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 być wykrywana lub używana przez usługi dziennika nawigacji. Na przykład SelectedIndex właściwość ustawia flagę Journal , aby zalecać, aby aplikacje zachowywały wybraną historię rejestrowania 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 do odczytu, IsMouseOver 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ą, takie jak ustawienie wartości domyślnej dla typów referencyjnych i obsługi 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 są deklarowane jako właściwości publiczne i DependencyProperty pola identyfikatora jako public static readonly pola. Jeśli określisz bardziej restrykcyjny poziom dostępu, taki jak protected, właściwość zależności będzie nadal dostępna za pośrednictwem jego identyfikatora w połączeniu z interfejsami API systemu właściwości. Nawet pole identyfikatora chronionego 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 odczytu wartość zwrócona z RegisterReadOnly klasy to DependencyPropertyKey, a zazwyczaj nie należy do niej składowej DependencyPropertyKeypublic klasy. Ponieważ system właściwości WPF nie propaguje DependencyPropertyKey poza kodem, właściwość zależności tylko do odczytu ma lepsze set zabezpieczenia niż właściwość zależności odczytu i zapisu.

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

Istnieje ogólna zasada programowania kodu zarządzanego, często wymuszana przez narzędzia do analizy kodu, która konstruktory klas nie powinny wywoływać metod wirtualnych. Dzieje się tak, ponieważ konstruktory podstawowe mogą być wywoływane podczas inicjowania konstruktora klasy pochodnej, a metoda wirtualna wywoływana przez konstruktor podstawowy może być uruchamiana przed zakończeniem inicjowania klasy pochodnej. Gdy pochodzisz z klasy, która już pochodzi z DependencyObjectklasy , 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 stosujesz określony wzorzec konstruktora. Aby uzyskać więcej informacji, zobacz Sejf wzorce konstruktorów dla obiektów DependencyObjects.

Zobacz też