## 17.6 Logischer und visueller Elementbaum Wenn Sie eine Benutzeroberfläche mit der WPF erstellen, erzeugen Sie eine Hierarchie ineinander verschachtelter Elemente. Dabei gibt es mit Window oder Page immer ein Wurzelelement, in dem die anderen Elemente enthalten sind. Jedes Element kann seinerseits wieder praktisch unbegrenzt Kindelemente enthalten. Auf diese Weise bildet sich eine durchaus tief gehende Elementstruktur, die als Elementbaum bezeichnet wird. Aufgrund der Architektur der WPF wird zwischen zwei Baumstrukturen unterschieden: logischer Elementbaum visueller Elementbaum Zur Verdeutlichung der Unterschiede zwischen den genannten beiden Elementbäumen soll der folgende XAML-Code dienen, der innerhalb eines Grid-Controls ein Label- und ein Button-Steuerelement beschreibt.
    <Window x:Class="ElementTree.MainWindow" ...>
      <Stackpanel>
        <Label Content="Label" />
        <Button Content="Button" />
      </Stackpanel>
    </Window>
Der logische Elementbaum wird in diesem XAML-Beispiel durch die Elemente MainWindow, StackPanel, Label und Button gebildet. Er enthält demnach alle Elemente, die in XAML bzw. im Code definiert sind. Grundsätzlich gehören zu einem logischen Baum jedoch weder Füllmuster noch Animationen. Jedes WPF-Steuerelement ist selbst durch eine mehr oder weniger große Anzahl visueller Einzelkomponenten aufgebaut. Zum Beispiel besitzt eine Schaltfläche kein fixes Layout, sondern setzt sich aus den Einzelkomponenten ButtonChrome und ContentPresenter zusammen. Alle Einzelkomponenten, die als Basis eine der beiden Klassen System.Windows.Media.Visual oder System.Windows.Media.Media3D.Visual3D haben, bilden zusammen den visuellen Elementbaum. Andere Elemente, beispielsweise String-Objekte, gehören nicht zum visuellen Elementbaum, weil sie kein eigenes Render-Verhalten benötigen. Zu den Elementen des visuellen Elementbaums hingegen sind auch die Klassen Button und Label zu zählen. In Abbildung 17.5 ist die Zugehörigkeit der Einzelkomponenten zu den verschiedenen Elementbäumen dargestellt. Die Ausgangsbasis ist hierbei der am Anfang dieses Abschnitts gezeigte XAML-Code. Hier klicken, um das Bild zu vergrößern Abbildung 17.5 Logischer und visueller Elementbaum ### 17.6.1 Warum wird zwischen den Elementbäumen unterschieden? WPF-Steuerelemente haben kein eigenes, fixes Layout. Beim Rendern wird der visuelle Elementbaum jeder einzelnen Komponente durchlaufen. Beispielsweise ist der Rahmen einer Schaltfläche zwar Bestandteil der Schaltfläche, kann aber jederzeit durch ein anderes Element ersetzt werden. Hieraus ergeben sich vielfältige, individuelle Gestaltungsmöglichkeiten. Infolgedessen kommt es aber auch zu einem Problem hinsichtlich der Auslösung von Ereignissen. Da der Rahmen einer Schaltfläche praktisch beliebig beschrieben werden kann, muss der Button erkennen können, ob er innerhalb oder außerhalb des Rahmens angeklickt wird. Dazu ist ein Ansatz notwendig, der sich am tatsächlichen Layout orientiert. In der WPF sind aus diesem Grund Routed Events beschrieben, die sich nicht am logischen, sondern am visuellen Elementbaum orientieren. Mit diesen Ereignissen beschäftigen wir uns in Abschnitt 24.7. ### 17.6.2 Elementbäume mit Code ermitteln Sie können den logischen und den virtuellen Elementbaum auch mit Code ermitteln. Dazu stellt Ihnen die WPF mit den Klassen System.Windows.LogicalTreeHelper System.Window.Media.VisualTreeHelper die benötigten Hilfsmittel zur Verfügung. Zur Ermittlung der logischen Baumstruktur dient die statische Methode GetChildren der Klasse LogicalTreeHelper. Die Methode erwartet als Argument ein Objekt vom Typ DependencyObject, d. h., das Objekt muss von dieser Klasse abgeleitet sein. Sie übergeben der Methode das Objekt, dessen direkt untergeordneten Elemente Sie ermitteln möchten. Der Rückgabewert ist eine Liste, die in einer Schleife durchlaufen werden kann. Vom Ansatz her sehr ähnlich ist das Coding der Klasse VisualTreeHelper. Zuerst wird mit der Methode GetChildrenCount ermittelt, aus wie vielen untergeordneten Elementen sich eine Komponente zusammensetzt. Den Rückgabewert können Sie in einer for-Schleife verwenden. Mit der Methode GetChild können Sie auf ein untergeordnetes Element direkt zugreifen. Das folgende Beispielprogramm soll zeigen, wie Sie beide Klassen und ihre Methoden einsetzen. Dazu sind mit GetLogicalTree und GetVisualTree zwei Methoden codiert, die für das Auswerten des logischen und virtuellen Elementbaums verantwortlich zeichnen. Beide schreiben die spezifische Baumstruktur mit Debug.WriteLine-Anweisungen in der Weise in das Ausgabefenster, dass die Hierarchie durch Einrückungen zu erkennen ist.
    // ----------------------------------------------------------
    // Beispiel: ...\Kapitel 17\ElementBaum
    // ----------------------------------------------------------
    public partial class MainWindow : Window {
      public MainWindow() {
        InitializeComponent();
        Debug.WriteLine("");
        GetLogicalTree(0, this);
      }
      protected override void OnContentRendered(EventArgs e) {
        base.OnContentRendered(e);
        Debug.WriteLine("");
        GetVisualTree(0, this);
      }
      void GetLogicalTree(int depth, object obj) {
        Debug.WriteLine(new string('-', depth) + obj);
        if (obj is DependencyObject == false) return;
          foreach (object child in LogicalTreeHelper.GetChildren(
                                          obj as DependencyObject))
        GetLogicalTree(depth + 4, child);
      }
      void GetVisualTree(int depth, DependencyObject obj) {
        Debug.WriteLine(new string('-', depth) + obj.ToString());
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
          GetVisualTree(depth + 4, VisualTreeHelper.GetChild(obj, i));
      }
    }
Der logische Elementbaum kann gezeichnet werden, sobald im Konstruktor das Window mit der Methode InitializeComponent initialisiert worden ist. Der visuelle Elementbaum kann erst abgefragt werden, wenn das Fenster gerendert worden ist. Das ist der Fall bei der Auslösung des Ereignisses ContentRendered an. Um das Ereignis um die Ausgabe im Ausgabefenster zu erweitern, wird die Methode OnContentRendered überschrieben. Sie können den Programmcode in jeder WPF-Anwendung einsetzen, um die Struktur im Ausgabefenster zu analysieren (siehe Abbildung 17.6). Hier klicken, um das Bild zu vergrößern