Udostępnij za pośrednictwem


Drzewa w WPF

W wielu technologiach elementy i składniki są zorganizowane w strukturze drzewa, w której deweloperzy bezpośrednio manipulują węzłami obiektów w drzewie, aby wpłynąć na renderowanie lub zachowanie aplikacji. Program Windows Presentation Foundation (WPF) używa również kilku metafor struktury drzewa do definiowania relacji między elementami programu. W większości deweloperzy WPF mogą utworzyć aplikację w kodzie lub zdefiniować fragmenty aplikacji w języku XAML, myśląc koncepcyjnie o metaforze drzewa obiektów, ale będą wywoływać konkretny interfejs API lub używać określonego znaczników, aby to zrobić, zamiast ogólnego interfejsu API manipulowania drzewami obiektów, takiego jak można użyć w modelu DOM XML. WPF uwidacznia dwie klasy pomocnicze, które zapewniają widok metafory drzewa, LogicalTreeHelper i VisualTreeHelper. Terminy drzewa wizualnego i drzewa logicznego są również używane w dokumentacji WPF, ponieważ te same drzewa są przydatne do zrozumienia zachowania niektórych kluczowych funkcji WPF. W tym temacie opisano, co reprezentuje drzewo wizualne i drzewo logiczne, omawia sposób, w jaki takie drzewa odnoszą się do ogólnej koncepcji drzewa obiektów oraz wprowadza LogicalTreeHelper i VisualTreeHelpers.

Drzewa w WPF

Najbardziej kompletną strukturą drzewa w WPF jest drzewo obiektów. Jeśli zdefiniujesz stronę aplikacji w języku XAML, a następnie załadujesz kod XAML, struktura drzewa zostanie utworzona na podstawie relacji zagnieżdżania elementów w znacznikach. Jeśli zdefiniujesz aplikację lub część aplikacji w kodzie, struktura drzewa zostanie utworzona na podstawie sposobu przypisywania wartości właściwości dla właściwości implementujących model zawartości dla danego obiektu. W WPF istnieją dwa sposoby, w jaki kompletne drzewo obiektów jest koncepcyjnie przedstawione i może być udostępnione w publicznym interfejsie API: jako drzewo logiczne i jako drzewo wizualne. Różnice między drzewem logicznym a wizualnym nie zawsze są istotne, ale czasami mogą powodować problemy z niektórymi podsystemami WPF i wpływać na wybory w znacznikach lub w kodzie.

Mimo że nie zawsze manipulujesz drzewem logicznym lub drzewem wizualnym bezpośrednio, zrozumienie koncepcji interakcji drzew jest przydatne do zrozumienia platformy WPF jako technologii. Myślenie o WPF w kategoriach pewnego rodzaju metafory drzewa jest również kluczowe dla zrozumienia, jak działa dziedziczenie właściwości i routing zdarzeń w WPF.

Uwaga / Notatka

Ponieważ drzewo obiektów jest bardziej pojęciem niż rzeczywistym interfejsem API, innym sposobem myślenia o koncepcji jest graf obiektu. W praktyce istnieją relacje między obiektami podczas wykonywania, w których metafora drzewa przestaje być adekwatna. Niemniej jednak, szczególnie w przypadku interfejsu użytkownika zdefiniowanego przez język XAML, metafora drzewa jest wystarczająco odpowiednia, że większość dokumentacji WPF będzie używać terminu drzewo obiektów podczas odwoływania się do tej ogólnej koncepcji.

Drzewo logiczne

W WPF dodajesz zawartość do elementów interfejsu użytkownika, ustawiając właściwości obiektów, które są w tle tych elementów. Możesz na przykład dodać elementy do kontrolki ListBox , manipulując jej Items właściwością. W ten sposób umieszczasz elementy w ItemCollection, które stanowią wartość właściwości Items. Podobnie, aby dodać obiekty do DockPanel, manipulujesz jego właściwością Children. W tym miejscu dodasz obiekty do obiektu UIElementCollection. Aby zapoznać się z przykładem kodu, zobacz Instrukcje: dynamiczne dodawanie elementu.

W rozszerzalnym języku znaczników aplikacji (XAML), podczas umieszczania elementów listy w ListBox lub innych elementach interfejsu użytkownika w elemencie DockPanel, należy również użyć właściwości Items i Children, jawnie lub niejawnie, jak w poniższym przykładzie.

<DockPanel
  Name="ParentElement"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  >
  <!--implicit: <DockPanel.Children>-->
  <ListBox DockPanel.Dock="Top">
    <!--implicit: <ListBox.Items>-->
    <ListBoxItem>
      <TextBlock>Dog</TextBlock>
    </ListBoxItem>
    <ListBoxItem>
      <TextBlock>Cat</TextBlock>
    </ListBoxItem>
    <ListBoxItem>
      <TextBlock>Fish</TextBlock>
    </ListBoxItem>
  <!--implicit: </ListBox.Items>-->
  </ListBox>
  <Button Height="20" Width="100" DockPanel.Dock="Top">Buy a Pet</Button>
  <!--implicit: </DockPanel.Children>-->
</DockPanel>

Jeśli chcesz przetworzyć ten kod XAML jako kod XML w modelu obiektu dokumentu, a jeśli tagi zostały uwzględnione jako niejawne (co byłoby legalne), wynikowe drzewo DOM XML zawierałoby elementy dla <ListBox.Items> i innych niejawnych elementów. Jednak XAML nie działa w ten sposób: przy odczytywaniu znaczników i zapisie do obiektów, wynikowy graf obiektu dosłownie nie zawiera ListBox.Items. Jednak ma ListBox właściwość o nazwie Items, która zawiera ItemCollection element, a ten ItemCollection jest inicjowany, ale pusty, gdy XAML ListBox jest przetwarzany. Następnie każdy element podrzędny, który istnieje jako zawartość ListBox, jest dodawany do ItemCollection przez wywołania analizatora do ItemCollection.Add. Ten przykład przetwarzania kodu XAML w drzewie obiektów jest do tej pory pozornie przykładem, w którym utworzone drzewo obiektów jest w zasadzie drzewem logicznym.

Jednak drzewo logiczne nie jest pełnym grafem obiektów, który istnieje dla interfejsu użytkownika aplikacji w czasie wykonywania, nawet po uwzględnieniu niejawnych elementów składni XAML. Głównym powodem tego są elementy wizualne i szablony. Rozważmy na przykład wartość Button. Drzewo logiczne zgłasza obiekt Button oraz jego reprezentację tekstową Content. Jednak ten przycisk ma więcej cech w kontekście drzewa obiektów czasu wykonania. Na ekranie przycisk jest wyświetlany tylko dlatego, że zastosowano określony Button szablon kontrolki. Wizualizacje pochodzące z zastosowanego szablonu (na przykład ciemnoszary zdefiniowany przez szablon wokół wizualnego przycisku) nie są zgłaszane w drzewie logicznym, nawet jeśli patrzysz na drzewo logiczne podczas działania programu (np. obsługujesz zdarzenie wejściowe z widocznego interfejsu użytkownika, a następnie odczytujesz drzewo logiczne). Aby znaleźć wizualizacje szablonu, należy zamiast tego zbadać drzewo wizualne.

Aby uzyskać więcej informacji na temat mapowania składni XAML na utworzony graf obiektu i niejawnej składni w języku XAML, zobacz Składnia XAML w szczegółach lub XAML w WPF.

Przeznaczenie drzewa logicznego

Drzewo logiczne istnieje tak, aby modele zawartości mogły łatwo iterować nad ich możliwymi obiektami podrzędnymi i tak, aby modele zawartości mogły być rozszerzalne. Ponadto drzewo logiczne zapewnia strukturę dla niektórych powiadomień, takich jak podczas ładowania wszystkich obiektów w drzewie logicznym. Zasadniczo drzewo logiczne to przybliżenie struktury obiektów w czasie wykonywania na poziomie frameworku, które wyklucza elementy wizualne, ale jest wystarczające dla wielu operacji zapytań względem kompozycji aplikacji czasu wykonywania.

Ponadto zarówno odwołania do zasobów statycznych, jak i dynamicznych są rozwiązywane przez wyszukanie w górę drzewa logicznego kolekcji na początkowym obiekcie żądającym Resources, a następnie kontynuowanie przeszukiwania drzewa logicznego i sprawdzanie każdego FrameworkElement (lub FrameworkContentElement) dla innej wartości Resources, która zawiera ResourceDictionary, potencjalnie zawierając ten klucz. Drzewo logiczne jest używane do wyszukiwania zasobów, gdy jest obecne zarówno drzewo logiczne, jak i drzewo wizualne. Aby uzyskać więcej informacji na temat słowników zasobów i wyszukiwania, zobacz Zasoby XAML.

Kompozycja drzewa logicznego

Drzewo logiczne jest definiowane na poziomie struktury WPF, co oznacza, że element podstawowy WPF, który jest najbardziej odpowiedni dla operacji drzewa logicznego, jest FrameworkElement albo .FrameworkContentElement Jednak, jak widać, jeśli faktycznie używasz interfejsu LogicalTreeHelper API, drzewo logiczne czasami zawiera węzły, które nie są ani FrameworkElement, ani FrameworkContentElement. Na przykład drzewo logiczne podaje wartość Text dla TextBlock, która jest ciągiem znaków.

Nadpisywanie drzewa logicznego

Zaawansowani autorzy kontrolek mogą zastąpić drzewo logiczne przez zastąpienie kilku interfejsów API definiujących sposób dodawania lub usuwania obiektów w drzewie logicznym przez ogólny obiekt lub model zawartości. Aby zapoznać się z przykładem przesłonięcia drzewa logicznego, zobacz Zastępowanie drzewa logicznego.

Dziedziczenie wartości właściwości

Dziedziczenie wartości właściwości działa za pośrednictwem drzewa hybrydowego. Rzeczywiste metadane, które zawierają właściwość Inherits umożliwiającą dziedziczenie, to klasa na poziomie frameworku WPF FrameworkPropertyMetadata. W związku z tym zarówno element nadrzędny, który przechowuje oryginalną wartość, jak i obiekt podrzędny, który dziedziczy tę wartość, muszą być albo FrameworkElement, albo FrameworkContentElement i muszą być częścią pewnego drzewa logicznego. Jednak w przypadku istniejących właściwości WPF, które obsługują dziedziczenie właściwości, dziedziczenie wartości właściwości jest w stanie utrwalać przez pośredniczącego obiektu, który nie znajduje się w drzewie logicznym. Jest to istotne głównie w przypadku, gdy elementy szablonu korzystają z dziedziczonych wartości właściwości, które są ustawione na wystąpieniu wykorzystującym szablon lub na jeszcze wyższych poziomach kompozycji na poziomie strony, a tym samym wyższych w drzewie logicznym. Aby dziedziczenie wartości właściwości działało spójnie przez taką granicę, właściwość dziedzicząca musi być zarejestrowana jako dołączona właściwość i należy postępować zgodnie z tym wzorcem, jeśli zamierzasz zdefiniować niestandardową właściwość zależności z zachowaniem dziedziczenia właściwości. Dokładne drzewo wykorzystywane do dziedziczenia właściwości nie może być całkowicie przewidziane przez metodę narzędziową klasy pomocniczej, nawet w czasie wykonywania. Aby uzyskać więcej informacji, zobacz Dziedziczenie wartości właściwości.

Drzewo wizualne

Oprócz koncepcji drzewa logicznego istnieje również koncepcja drzewa wizualnego w WPF. Drzewo wizualizacji opisuje strukturę obiektów wizualnych reprezentowanych przez klasę bazową Visual . Podczas pisania szablonu dla kontrolki definiujesz lub ponownie definiujesz drzewo wizualne, które ma zastosowanie do tej kontrolki. Drzewo wizualne jest również interesujące dla programistów, którzy chcą kontrolować rysowanie na niższym poziomie ze względu na wydajność i optymalizację. Jedną z reprezentacji drzewa wizualnego w ramach konwencjonalnego programowania aplikacji WPF jest fakt, że trasy zdarzeń dla zdarzenia kierowanego głównie przebiegają wzdłuż drzewa wizualnego, a nie drzewa logicznego. Ta subtelność zachowania zdarzeń kierowanych może nie być natychmiast widoczna, chyba że jesteś autorem kontrolki. Kierowanie zdarzeń przez drzewo wizualne umożliwia kontrolkom zaprojektowanym do implementacji kompozycji na poziomie interfejsu wizualnego obsługę zdarzeń lub tworzenie ustawień zdarzeń.

Drzewa, elementy zawartości i hosty zawartości

Elementy zawartości (klasy pochodzące z ContentElement) nie są częścią drzewa wizualnego; nie dziedziczą Visual i nie mają reprezentacji wizualnej. Aby w ogóle pojawić się w interfejsie użytkownika, ContentElement musi być hostowany w hoście zawartości, który jest zarówno Visual, jak i uczestnikiem drzewa logicznego. Zazwyczaj taki obiekt jest obiektem FrameworkElement. Można koncepcyjnie określić, że host zawartości jest nieco jak "przeglądarka" dla zawartości i wybiera sposób wyświetlania tej zawartości w regionie ekranu, który kontroluje host. Gdy zawartość jest hostowana, może stać się częścią niektórych procesów związanych z drzewem wizualnym. Zwykle klasa hosta zawiera kod implementacji FrameworkElement, który dodaje dowolne hostowane ContentElement do trasy zdarzeń poprzez podwęzły drzewa logicznej zawartości, mimo że hostowana zawartość nie jest częścią prawdziwego drzewa wizualnego. Jest to konieczne, aby ContentElement mogło wywołać zdarzenie kierowane, które jest skierowane do dowolnego elementu z wyjątkiem siebie.

Przechodzenie drzewa

Klasa LogicalTreeHelper udostępnia metody GetChildren, GetParent i FindLogicalNode dla przechodzenia drzewa logicznego. W większości przypadków nie należy przechodzić przez logiczne drzewo istniejących kontrolek, ponieważ te kontrolki prawie zawsze uwidaczniają ich logiczne elementy podrzędne jako dedykowaną właściwość kolekcji, która obsługuje dostęp do kolekcji, taki jak Add, indeksator itd. Przechodzenie drzewa jest głównie scenariuszem, który jest używany przez autorów kontrolek, którzy zdecydują się nie pochodzić z zamierzonych wzorców kontrolek, takich jak ItemsControl lub Panel gdzie właściwości kolekcji są już zdefiniowane, i którzy zamierzają zapewnić własną obsługę właściwości kolekcji.

Drzewo wizualne obsługuje również klasę pomocnika dla przechodzenia drzewa wizualnego, VisualTreeHelper. Drzewo wizualne nie jest ujawnione w tak wygodny sposób za pomocą właściwości specyficznych dla kontrolki, dlatego klasa VisualTreeHelper jest zalecanym sposobem przechodzenia przez drzewo wizualne, jeśli jest to konieczne w twoim scenariuszu programistycznym. Aby uzyskać więcej informacji, zobacz Renderowanie grafiki WPF: omówienie.

Uwaga / Notatka

Czasami konieczne jest sprawdzenie drzewa wizualnego zastosowanego szablonu. Podczas korzystania z tej techniki należy zachować ostrożność. Nawet jeśli nawigujesz po drzewie wizualnym dla kontrolki, w której definiujesz szablon, użytkownicy tej kontrolki zawsze mogą zmienić szablon, ustawiając atrybut Template w wystąpieniach, a nawet użytkownik końcowy może wpłynąć na zastosowany szablon, zmieniając motyw systemowy.

Ścieżki dla zdarzeń trasowanych jako "drzewo"

Jak wspomniano wcześniej, trasa dowolnego kierowanego zdarzenia przebiega wzdłuż jednej, uprzednio określonej ścieżki drzewa, które jest hybrydą reprezentacji drzewa wizualnego i logicznego. Ścieżka zdarzenia może przebiegać w górę lub w dół w modelu drzewa, w zależności od tego, czy jest to zdarzenie kierowane z propagacją tunelową, czy z propagacją bąbelkową. Koncepcja trasy zdarzeń nie ma bezpośrednio wspierającej klasy pomocniczej, którą można wykorzystać do samodzielnego nawigowania trasą zdarzenia, niezależnie od wywoływania zdarzenia, które faktycznie trasuje. Istnieje klasa reprezentująca trasę , EventRouteale metody tej klasy są zwykle przeznaczone tylko do użytku wewnętrznego.

Słowniki zasobów i drzewa

Wyszukiwanie słownika zasobów dla wszystkich Resources zdefiniowanych na stronie przechodzi zasadniczo przez drzewo logiczne. Obiekty, które nie znajdują się w drzewie logicznym, mogą odwoływać się do zasobów kluczy, ale sekwencja wyszukiwania zasobów rozpoczyna się w momencie, w którym ten obiekt jest połączony z drzewem logicznym. W WPF tylko węzły drzewa logicznego mogą mieć właściwość Resources, która zawiera element ResourceDictionary, dlatego nie ma korzyści w przechodzeniu drzewa wizualnego w poszukiwaniu zasobów z klucza ResourceDictionary.

Jednak wyszukiwanie zasobów może również wykraczać poza bezpośrednie drzewo logiczne. W przypadku znaczników aplikacji wyszukiwanie zasobów może następnie przejść do słowników zasobów na poziomie aplikacji, a potem do obsługi motywu i wartości systemowych, które są przywoływane jako właściwości statyczne lub klucze. Motywy mogą również odwoływać się do wartości systemowych poza drzewem logicznym motywu, jeśli odwołania do zasobów są dynamiczne. Aby uzyskać więcej informacji na temat słowników zasobów i logiki odnośników, zobacz Zasoby XAML.

Zobacz także