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

W tym temacie opisano przyczyny, dla których deweloperzy aplikacji i autorzy składników programu Windows Presentation Foundation (WPF) mogą chcieć utworzyć niestandardową właściwość zależności oraz opisano kroki implementacji, a także niektóre opcje implementacji, które mogą poprawić wydajność, użyteczność lub wszechstronność właściwości.

Wymagania wstępne

W tym temacie założono, że rozumiesz właściwości zależności z perspektywy konsumenta istniejących właściwości zależności w klasach WPF i zapoznaj się z tematem Właściwości zależności — omówienie . Aby postępować zgodnie z przykładami w tym temacie, należy również zrozumieć język XAML i wiedzieć, jak pisać aplikacje WPF.

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

W przeciwnym razie można włączyć właściwość środowiska uruchomieniowego języka wspólnego (CLR), aby obsługiwać style, powiązanie danych, dziedziczenie, animacje i wartości domyślne, implementując ją jako właściwość zależności. Właściwości zależności to właściwości zarejestrowane w systemie właściwości WPF przez wywołanie Register metody (lub RegisterReadOnly), które są wspierane przez pole identyfikatora DependencyProperty . Właściwości zależności mogą być używane tylko przez DependencyObject typy, ale DependencyObject jest dość wysokie w hierarchii klas WPF, więc większość klas dostępnych w WPF 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 tym zestawie SDK, zobacz Omówienie właściwości zależności.

Przykłady właściwości zależności

Przykłady właściwości zależności, które są implementowane w klasach WPF, obejmują Background właściwość, Width właściwość i Text właściwość, między innymi. Każda właściwość zależności uwidoczniona przez klasę ma odpowiednie publiczne pole statyczne typu DependencyProperty uwidocznione w tej samej klasie. Jest to identyfikator właściwości zależności. Identyfikator nosi nazwę używającą konwencji: nazwę właściwości zależności z dołączonym ciągiem Property . Na przykład odpowiednie DependencyProperty pole identyfikatora Background właściwości to BackgroundProperty. Identyfikator przechowuje informacje o właściwości zależności, ponieważ została zarejestrowana, a identyfikator jest następnie używany później dla innych operacji dotyczących właściwości zależności, takich jak wywołanie metody SetValue.

Jak wspomniano w sekcji Omówienie właściwości zależności, wszystkie właściwości zależności w WPF (z wyjątkiem większości dołączonych właściwości) są również właściwościami CLR ze względu na implementację "otoki". W związku z tym z kodu można uzyskać lub ustawić właściwości zależności, wywołując metody dostępu CLR definiujące otoki w taki sam sposób, jak w przypadku innych właściwości CLR. Jako użytkownik ustalonych właściwości zależności zazwyczaj nie używa DependencyObject się metod GetValue i SetValue, które są punktem połączenia z bazowym systemem właściwości. Zamiast tego istniejąca implementacja właściwości CLR będzie już wywoływana GetValue i SetValue w get implementacjach otoki właściwości i set przy użyciu odpowiedniego pola identyfikatora. Jeśli samodzielnie implementujesz niestandardową właściwość zależności, zdefiniujesz otokę w podobny sposób.

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

Podczas implementowania właściwości w klasie, tak długo, jak klasa pochodzi z DependencyObjectklasy , masz możliwość utworzenia kopii zapasowej właściwości za pomocą identyfikatora DependencyProperty , a tym samym, aby uczynić ją właściwością zależności. Posiadanie właściwości jest właściwością zależności nie zawsze jest konieczne lub odpowiednie i będzie zależeć od potrzeb scenariusza. Czasami typowa technika tworzenia kopii zapasowej właściwości z polem prywatnym jest odpowiednia. Należy jednak zaimplementować właściwość jako właściwość zależności za każdym razem, gdy właściwość ma obsługiwać co najmniej jedną z następujących funkcji WPF:

  • Chcesz, aby właściwość można było ustawić w stylu. Aby uzyskać więcej informacji, zobacz Styling and Templating (Styling and Templating).

  • Chcesz, aby właściwość obsługiwała 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.

  • Chcesz, aby właściwość można było ustawić przy użyciu odwołania do zasobów dynamicznych. Aby uzyskać więcej informacji, zobacz Zasoby XAML.

  • Chcesz automatycznie dziedziczyć wartość właściwości z elementu nadrzędnego w drzewie elementów. W takim przypadku zarejestruj się przy RegisterAttached użyciu metody, nawet jeśli utworzysz również otokę właściwości dla dostępu CLR. Aby uzyskać więcej informacji, zobacz Dziedziczenie wartości właściwości.

  • Chcesz, aby właściwość był animatowalny. Aby uzyskać więcej informacji, zobacz Omówienie animacji.

  • Chcesz, aby system właściwości zgłaszał, kiedy poprzednia wartość właściwości została zmieniona przez akcje podjęte przez system właściwości, środowisko lub użytkownika albo odczytywanie i używanie stylów. Za pomocą metadanych właściwości właściwość może określić metodę wywołania zwrotnego, która będzie wywoływana za każdym razem, gdy system właściwości określi, że wartość właściwości została ostatecznie zmieniona. 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.

  • Chcesz użyć ustalonych konwencji metadanych, które są również używane przez procesy WPF, takie jak raportowanie, czy zmiana wartości właściwości powinna wymagać, aby system układu ponownie skomponował wizualizacje dla elementu. Możesz też użyć przesłonięć metadanych, aby klasy pochodne mogły zmieniać cechy oparte na metadanych, takie jak wartość domyślna.

  • Chcesz, aby właściwości kontrolki niestandardowej otrzymywały obsługę Projektant WPF programu Visual Studio, na przykład edytowanie okna właściwości. Aby uzyskać więcej informacji, zobacz Omówienie tworzenia kontrolek.

Podczas badania tych scenariuszy należy również rozważyć, czy scenariusz można osiągnąć, przesłaniając metadane istniejącej właściwości zależności, zamiast implementować zupełnie nową właściwość. To, czy zastąpienie metadanych jest praktyczne, zależy od scenariusza i tego, jak ściśle ten scenariusz przypomina implementację w istniejących właściwościach i klasach zależności WPF. Aby uzyskać więcej informacji na temat zastępowania metadanych w istniejących właściwościach, zobacz Metadane właściwości zależności.

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

Definiowanie właściwości zależności składa się z czterech odrębnych pojęć. Te pojęcia nie muszą być rygorystycznymi krokami proceduralnymi, ponieważ niektóre z nich są łączone jako pojedyncze wiersze kodu w implementacji:

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

  • Zarejestruj nazwę właściwości w systemie właściwości, określając typ właściciela i typ wartości właściwości. Określ również metadane właściwości, jeśli są używane.

  • Zdefiniuj DependencyProperty identyfikator jako publicstaticreadonly pole typu właściciela.

  • Zdefiniuj właściwość "otoka" CLR, której nazwa odpowiada nazwie właściwości zależności. Zaimplementuj właściwość get "otoki" środowiska CLR i set metody dostępu, aby nawiązać połączenie z właściwością zależności, która ją wspiera.

Rejestrowanie właściwości w systemie właściwości

Aby właściwość była właściwością zależności, należy zarejestrować ją w tabeli obsługiwanej przez system właściwości i nadać jej unikatowy identyfikator, który jest używany jako kwalifikator dla późniejszych operacji systemu właściwości. Te operacje mogą być operacjami wewnętrznymi lub własnym kodem wywołującym interfejsy API systemu właściwości. Aby zarejestrować właściwość, należy wywołać Register metodę w treści klasy (wewnątrz klasy, ale poza definicjami składowych). Pole identyfikatora jest również udostępniane przez Register wywołanie metody jako wartość zwracaną. Powodem, Register dla którego wywołanie jest wykonywane poza innymi definicjami elementów członkowskich, jest użycie tej wartości zwracanej do przypisania i utworzeniareadonlypublicstaticpola typu DependencyProperty w ramach klasy. To pole staje się identyfikatorem właściwości zależności.

public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender,
      new PropertyChangedCallback(OnUriChanged)
  )
);
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))

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

Istnieją ustalone konwencje nazewnictwa dotyczące właściwości zależności, które należy przestrzegać we wszystkich, ale wyjątkowych okolicznościach.

Właściwość zależności będzie mieć podstawową nazwę "AquariumGraphic", jak w tym przykładzie, która jest podana jako pierwszy parametr .Register Ta nazwa musi być unikatowa w obrębie każdego typu rejestru. Właściwości zależności dziedziczone za pośrednictwem typów bazowych są uważane za już część typu rejestru; nie można ponownie zarejestrować nazw dziedziczych właściwości. Istnieje jednak technika dodawania klasy jako właściciela właściwości zależności nawet wtedy, gdy ta właściwość zależności nie jest dziedziczona; aby uzyskać szczegółowe informacje, zobacz Metadane właściwości zależności.

Podczas tworzenia pola identyfikatora nadaj temu polu nazwę właściwości, która została zarejestrowana, oraz sufiks Property. To pole jest twoim identyfikatorem właściwości zależności i będzie ono używane później jako dane wejściowe dla SetValue elementu i GetValue wywołania, które zostaną wprowadzone w otokach, za pomocą dowolnego innego kodu dostępu do właściwości za pomocą własnego kodu, przez dowolny dozwolony dostęp do kodu zewnętrznego, przez system właściwości i potencjalnie przez procesory XAML.

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.

Implementowanie otoki

Implementacja otoki powinna wywołać GetValue implementację get i SetValue w set implementacji (oryginalne wywołanie rejestracji i pole są tutaj również wyświetlane w celu zapewnienia przejrzystości).

We wszystkich, ale wyjątkowych okolicznościach implementacje otoki powinny wykonywać tylko GetValue akcje i SetValue , odpowiednio. Przyczyna tego zagadnienia została omówiona w temacie Właściwości ładowania i zależności XAML.

Wszystkie istniejące właściwości zależności publicznej, które znajdują się w klasach WPF, używają tego prostego modelu implementacji otoki; większość złożoności działania właściwości zależności jest z natury zachowaniem systemu właściwości lub jest implementowana za pomocą innych pojęć, takich jak przymus lub zmiana właściwości wywołania zwrotnego za pośrednictwem metadanych właściwości.


public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender,
      new PropertyChangedCallback(OnUriChanged)
  )
);
public Uri AquariumGraphic
{
  get { return (Uri)GetValue(AquariumGraphicProperty); }
  set { SetValue(AquariumGraphicProperty, value); }
}

Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))
Public Property AquariumGraphic() As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set(ByVal value As Uri)
        SetValue(AquariumGraphicProperty, value)
    End Set
End Property

Ponownie, zgodnie z konwencją, nazwa właściwości otoki musi być taka sama jak nazwa wybrana i podana jako pierwszy parametr Register wywołania, które zarejestrowało właściwość. Jeśli twoja właściwość nie jest zgodnie z konwencją, niekoniecznie powoduje to wyłączenie wszystkich możliwych zastosowań, ale napotkasz kilka godnych uwagi problemów:

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

  • Większość narzędzi i projektantów musi polegać na konwencjach nazewnictwa, aby prawidłowo serializować XAML lub 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 podczas przetwarzania wartości atrybutów. Aby uzyskać więcej informacji, zobacz Właściwości ładowania i zależności XAML.

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

Podczas rejestrowania właściwości zależności rejestracja za pośrednictwem systemu właściwości tworzy obiekt metadanych, który przechowuje właściwości właściwości. Wiele z tych cech ma wartości domyślne, które są ustawiane, jeśli właściwość jest zarejestrowana przy użyciu prostych podpisów .Register Inne podpisy Register umożliwiają określenie metadanych, które mają być rejestrowane podczas rejestrowania właściwości. Najbardziej typowe metadane podane dla właściwości zależności to nadanie im wartości domyślnej, która jest stosowana w nowych wystąpieniach korzystających z właściwości .

Jeśli tworzysz właściwość zależności, która istnieje w klasie pochodnej FrameworkElementklasy , możesz użyć bardziej wyspecjalizowanej klasy FrameworkPropertyMetadata metadanych, a nie klasy bazowej PropertyMetadata . Konstruktor klasy FrameworkPropertyMetadata ma kilka podpisów, w których można określić różne cechy metadanych w połączeniu. Jeśli chcesz określić tylko wartość domyślną, użyj podpisu, który przyjmuje pojedynczy parametr typu Object. Przekaż ten parametr obiektu jako wartość domyślną specyficzną dla typu właściwości (podana wartość domyślna musi być typem podanym jako propertyType parametr w wywołaniu Register ).

W przypadku FrameworkPropertyMetadataprogramu można również określić flagi opcji metadanych dla właściwości. Te flagi są konwertowane na odrębne właściwości metadanych właściwości po rejestracji i są używane do przekazywania niektórych warunkowych do innych procesów, takich jak aparat układu.

Ustawianie odpowiednich flag metadanych

  • Jeśli właściwość (lub zmienia się jej wartość) wpływa na interfejs użytkownika (UI), a w szczególności wpływa na sposób, w jaki system układu powinien mieć rozmiar lub renderować element na stronie, ustaw co najmniej jedną z następujących flag: AffectsMeasure, AffectsArrange, AffectsRender.

    • AffectsMeasure wskazuje, że zmiana tej właściwości wymaga zmiany renderowania interfejsu użytkownika, w której obiekt zawierający może wymagać więcej lub mniej miejsca w obiekcie nadrzędnym. Na przykład właściwość "Width" powinna mieć ten zestaw flag.

    • AffectsArrange wskazuje, że zmiana tej właściwości wymaga zmiany renderowania interfejsu użytkownika, która zwykle nie wymaga zmiany w miejscu dedykowanym, ale wskazuje, że pozycjonowanie w przestrzeni uległo zmianie. Na przykład właściwość "Wyrównanie" powinna mieć ten zestaw flag.

    • AffectsRender wskazuje, że wystąpiła inna zmiana, która nie wpłynie na układ i miarę, ale wymaga innego renderowania. Przykładem może być właściwość, która zmienia kolor istniejącego elementu, na przykład "Tło".

    • Te flagi są często używane jako protokół w metadanych dla własnych implementacji zastępowania systemu właściwości lub wywołań zwrotnych układu. Na przykład może istnieć OnPropertyChanged wywołanie InvalidateArrange zwrotne, które wywoła, jeśli jakakolwiek właściwość wystąpienia zgłasza zmianę wartości i ma AffectsArrange wartość tak jak true w metadanych.

  • Niektóre właściwości mogą mieć wpływ na charakterystykę renderowania zawierającego element nadrzędny, na sposoby powyżej i poza zmianami wymaganego rozmiaru wymienionego powyżej. Przykładem jest MinOrphanLines właściwość używana w modelu dokumentu przepływu, w której zmiany tej właściwości mogą zmienić ogólne renderowanie dokumentu przepływu zawierającego akapit. Użyj polecenia AffectsParentArrange lub AffectsParentMeasure , aby zidentyfikować podobne przypadki we własnych właściwościach.

  • Domyślnie właściwości zależności obsługują powiązanie danych. Można celowo wyłączyć powiązanie danych w przypadkach, w których nie ma realistycznego scenariusza powiązania danych lub gdy wydajność powiązania danych dla dużego obiektu jest rozpoznawana jako problem.

  • Domyślnie powiązanie Mode danych dla właściwości zależności domyślnie ma wartość OneWay. Zawsze można zmienić powiązanie na TwoWay wystąpienie powiązania. Aby uzyskać szczegółowe informacje, zobacz Określanie kierunku powiązania. Jednak jako autor właściwości zależności możesz domyślnie ustawić, aby właściwość korzystała z TwoWay trybu powiązania. Przykładem istniejącej właściwości zależności jest MenuItem.IsSubmenuOpen; scenariusz dla tej właściwości polega na tym, że IsSubmenuOpen logika ustawień i komponowanie MenuItem interakcji z domyślnym stylem motywu. Logika IsSubmenuOpen właściwości używa powiązania danych natywnie do zachowania stanu właściwości zgodnie z innymi właściwościami stanu i wywołaniami metod. Inną przykładowa właściwość, która jest domyślnie powiązana TwoWay , to TextBox.Text.

  • Możesz również włączyć dziedziczenie właściwości we właściwości niestandardowej zależności, ustawiając flagę Inherits . Dziedziczenie właściwości jest przydatne w przypadku scenariusza, w którym elementy nadrzędne i elementy podrzędne mają wspólną właściwość i warto, aby elementy podrzędne miały tę konkretną wartość właściwości ustawioną na tę samą wartość co zestaw nadrzędny. Przykładowa właściwość dziedziczona to DataContext, która służy do tworzenia powiązań operacji w celu włączenia ważnego scenariusza szczegółów wzorca na potrzeby prezentacji danych. Dzięki dziedziczeniu DataContext wszystkie elementy podrzędne dziedziczą również ten kontekst danych. Ze względu na dziedziczenie wartości właściwości można określić kontekst danych na stronie lub katalogu głównym aplikacji i nie trzeba go ponownie określać dla powiązań we wszystkich możliwych elementach podrzędnych. DataContext jest również dobrym przykładem ilustrowania, że dziedziczenie zastępuje wartość domyślną, ale zawsze można ją ustawić lokalnie na dowolnym konkretnym elemenie podrzędnym; Aby uzyskać szczegółowe informacje, zobacz Use the Master-Detail Pattern with Hierarchical Data (Używanie wzorca szczegółów z danymi hierarchicznymi). Dziedziczenie wartości właściwości ma możliwy koszt wydajności i dlatego należy używać oszczędnie; aby uzyskać szczegółowe informacje, zobacz Dziedziczenie wartości właściwości.

  • Ustaw flagę Journal , aby wskazać, czy właściwość zależności powinna zostać wykryta lub użyta przez usługi dziennika nawigacji. Przykładem jest SelectedIndex właściwość. Każdy element wybrany w kontrolce wyboru powinien być utrwalany podczas nawigowania po historii dziennika.

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. Jednak scenariusze, dla których można zdefiniować właściwość jako tylko do odczytu, są nieco inne, podobnie jak procedura rejestrowania ich w systemie właściwości i uwidaczniania identyfikatora. 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ą pewne dodatkowe problemy z implementacją, które należy wziąć pod uwagę. Aby uzyskać szczegółowe informacje, zobacz Właściwości zależności typu kolekcji.

Zagadnienia dotyczące zabezpieczeń właściwości zależności

Właściwości zależności powinny być deklarowane jako właściwości publiczne. Pola identyfikatora właściwości zależności powinny być zadeklarowane jako publiczne pola statyczne. Nawet jeśli próbujesz zadeklarować inne poziomy dostępu (takie jak chronione), 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. Nawet pole identyfikatora chronionego jest potencjalnie dostępne z powodu interfejsów API raportowania metadanych lub określania wartości, które są częścią systemu właściwości, takich jak LocalValueEnumerator. Aby uzyskać więcej informacji, zobacz Dependency Property Security (Zabezpieczenia właściwości zależności).

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, takie jak FxCop), że konstruktory klas nie powinny wywoływać metod wirtualnych. Jest to spowodowane tym, że konstruktory mogą być wywoływane jako podstawowe inicjowanie konstruktora klasy pochodnej, a wprowadzanie metody wirtualnej za pośrednictwem konstruktora może wystąpić w niekompletnym stanie inicjowania tworzonego wystąpienia obiektu. Gdy pochodzisz z dowolnej klasy, która już pochodzi z DependencyObjectklasy , należy pamiętać, że sam 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 bardzo konkretny wzorzec konstruktora. Aby uzyskać szczegółowe informacje, zobacz Sejf Constructor Patterns for DependencyObjects (Wzorce konstruktorów Sejf dla obiektów DependencyObjects).

Zobacz też