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 wspólnego środowiska uruchomieniowego (CLR) , właściwość zależna dodaje obsługę stylizacji, 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.
Warunki wstępne
W tym artykule zakłada się, że masz podstawową wiedzę na temat właściwości zależności i że przeczytałeś omówienie 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, które są zarejestrowane w systemie właściwości WPF poprzez wywołania Register lub RegisterReadOnly. Metoda Register
zwraca wystąpienie DependencyProperty, które zawiera zarejestrowaną nazwę i cechy właściwości zależności. Przypiszesz wystąpienie DependencyProperty
do statycznego pola tylko do odczytu, znanego jako identyfikator właściwości zależności , które zgodnie z konwencją nosi nazwę <property name>Property
. Na przykład pole identyfikatora właściwości Background jest 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 typów DependencyObject. Większość klas WPF obsługuje właściwości zależności, ponieważ DependencyObject
znajduje się blisko korzenia 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 Właściwości zależności — omówienie.
Opakowania właściwości zależności
Właściwości zależności WPF, które nie są właściwościami dołączanymi, są uwidaczniane przez powłokę CLR, która implementuje akcesory get
i set
. Korzystając z opakowania 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 jak każdą inną właściwość CLR. Metody dostępu get
i set
współdziałają z podstawowym systemem właściwości za pośrednictwem 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żnych zwykle nie wywołują GetValue
ani SetValue
bezpośrednio, ale jeśli implementujesz niestandardową właściwość zależną, użyjesz tych metod w osłonie.
Kiedy zaimplementować właściwość zależności
Podczas implementowania właściwości w klasie, która pochodzi z DependencyObject, należy ustawić ją jako właściwość zależności, tworząc kopię zapasową właściwości za pomocą identyfikatora DependencyProperty. To, czy utworzenie właściwości zależności jest korzystne, zależy od scenariusza. Chociaż użycie prywatnego pola do wspierania właściwości 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ą swoją wartość z elementu nadrzędnego w drzewie elementów. W tym celu należy zarejestrować się przy użyciu RegisterAttached, nawet jeśli dodatkowo utworzysz otoczkę 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 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 i walidację właściwości zależności.
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 w klasach pochodnych.
Obsługa projektanta WPF programu Visual Studio, na przykład edytowanie właściwości niestandardowej kontrolki w oknie właściwości. Aby uzyskać więcej informacji, zobacz przegląd projektowania kontrolek Control.
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 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.
(Opcjonalnie) Tworzenie metadanych właściwości zależności.
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.
Zdefiniuj identyfikator DependencyProperty jako pole
public static readonly
dla typu właściciela. Nazwa pola identyfikatora to nazwa właściwości z dołączonym sufiksemProperty
.Zdefiniuj właściwość otoki CLR o takiej samej nazwie jak nazwa właściwości zależności. W otoku CLR zaimplementuj akcesory
get
iset
, które łączą się z właściwością zależności, która obsługuje tę otokę.
Rejestrowanie nieruchomoś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 wszelkimi definicjami składowymi. 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 Register
jest wykonywane poza definicjami członków, jest przypisanie wartości zwracanej do pola public static readonly
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
nazywa właściwość zależności AquariumGraphic
.
// 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)))
Notatka
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 zarejestrowanej przez inny typ, nawet taki, po którym klasa nie dziedziczy, poprzez dodanie swojej klasy jako właściciela tej 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.
Implementacja opakowania właściwości
Zgodnie z konwencją nazwa właściwości opakowującej musi być taka sama jak pierwszy parametr wywołania Register, czyli nazwa właściwości będącej zależnością. Implementacja otoki wywoła GetValue w akcesorze get
i SetValue w akcesorze set
(dla właściwości odczytu i zapisu). W poniższym przykładzie pokazano opakowanie — w kolejności połączeniu wywołania rejestracji i deklaracji pola identyfikatora. Wszystkie publiczne właściwości zależności we wszystkich 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 wrappera powinna zawierać tylko kody GetValue i SetValue. Aby dowiedzieć się więcej na ten temat, zobacz Implikacje dotyczące niestandardowych właściwości zależności.
Jeśli właściwość nie jest zgodna z ustalonymi konwencjami nazewnictwa, możesz napotkać 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ć XAML i wspierać środowisko projektanta na poziomie poszczególnych właściwości.
Bieżąca implementacja ładowarki XAML WPF całkowicie pomija wrappery i stosuje konwencję nazewnictwa do przetwarzania wartości atrybutów. Aby uzyskać więcej informacji, zobacz właściwości ładowania i zależności XAML.
Metadane dotyczące 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, tak jak 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 pochodzącej z FrameworkElement, możesz użyć bardziej wyspecjalizowanej klasy metadanych FrameworkPropertyMetadata, a nie jej klasy bazowej PropertyMetadata. Kilka FrameworkPropertyMetadata signatur konstruktorów pozwala określić różne kombinacje 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 przeciążenia FrameworkPropertyMetadata pozwalają określić opcje flag metadanych dla twojej właściwości. System właściwości przekształca te flagi w dyskretne właściwości, a wartości flag są wykorzystywane przez procesy WPF, takie jak silnik 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 w jego obiekcie nadrzędnym. Na przykład ustaw tę flagę metadanych dla właściwości
Width
.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ę metadanych dla właściwości
Alignment
.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ę dla właściwości
Background
lub inną właściwość, która ma wpływ na kolor elementu.
Można również używać tych flag jako danych wejściowych do własnych implementacji wywołań zwrotnych systemu właściwości (lub układu). Na przykład, możesz użyć wywołania zwrotnego OnPropertyChanged, aby wywołać InvalidateArrange, gdy właściwość instancji zgłasza zmianę wartości i ma AffectsArrange ustawione w metadanych.
Niektóre właściwości wpływają na charakterystykę renderowania elementu nadrzędnego na inny sposób. Na przykład zmiany właściwości MinOrphanLines mogą 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ć IsDataBindingAllowed, aby wyłączyć powiązanie danych, gdy nie ma realistycznego scenariusza lub gdy wydajność powiązania danych jest problematyczna, na przykład na dużych obiektach.
Mimo że domyślne powiązanie danych tryb 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 korzystającej z powiązania danych dwukierunkowych jest MenuItem.IsSubmenuOpen, która ma stan oparty na innych właściwościach i wywołaniach metody. Scenariusz dla
IsSubmenuOpen
polega na tym, że jego logika ustawień i komponowanie MenuItemwspół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 element nadrzędny i element podrzędny mają wspólną właściwość, i ma sens, aby element podrzędny dziedziczył wartość właściwości od elementu nadrzędnego. Przykładem właściwości dziedziczonej jest DataContext, która obsługuje operacje powiązań korzystające z scenariusza nadrzędno-podrzędnego do prezentacji danych. Dziedziczenie wartości właściwości pozwala określić kontekst danych w korzeniu strony lub aplikacji, co eliminuje potrzebę określania 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żywaj dziedziczenia wartości właściwości oszczędnie, ponieważ wiąże się to z kosztami dla wydajności. Aby uzyskać więcej informacji, zobacz sekcję 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ść SelectedIndex ustawia flagę
Journal
, aby zalecić aplikacjom przechowywanie historii dzienników wybranych 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 IsMouseOver jest tylko 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 typu zależności 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 ().
Bezpieczeństwo właściwości zależnych
Zazwyczaj właściwości zależności są deklarowane jako właściwości publiczne, a pola identyfikatora DependencyProperty jako pola public static readonly
. 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 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 dependency property security.
W przypadku właściwości zależności tylko do odczytu wartość zwracana z RegisterReadOnly to DependencyPropertyKey, a zazwyczaj nie uczynisz DependencyPropertyKey
składową swojej klasy public
. 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 do 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 DependencyObject, 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 pozwala klasom pochodnym na uczestnictwo w ustalaniu 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 Bezpieczne wzorce konstruktorów dla DependencyObjects.
Zobacz też
- Właściwości zależności — omówienie
- metadane właściwości zależności
- Omówienie tworzenia kontrolek Control
- właściwości zależności typu zbiorczego
- zabezpieczenia właściwości zależności
- Ładowanie XAML i właściwości zależności
- Bezpieczne wzorce konstruktorów dla obiektów DependencyObjects
.NET Desktop feedback