Layoutsystem
In diesem Thema wird das Windows Presentation Foundation (WPF)-Layoutsystem beschrieben. Das Verständnis dazu, wie und wann Layoutberechnungen auftreten, ist von grundlegender Bedeutung für das Erstellen von Benutzeroberflächen in WPF.
Dieses Thema enthält folgende Abschnitte:
Umgebende Felder für Elemente
Das Layoutsystem
Messen und Anordnen von untergeordneten Elementen
Panel-Elemente und benutzerdefiniertes Layoutverhalten
Überlegungen zur Leistungsfähigkeit des Layouts
Teilpixelrendering und Layoutrundung
Weitere Informationen
Umgebende Felder für Elemente
Wenn Sie ein Layout in WPF erstellen möchten, ist es wichtig, dass Sie Kenntnisse über das umgebende Feld haben, das alle Elemente umgibt. Jedes vom Layoutsystem verwendete FrameworkElement kann als Rechteck gesehen werden, das in das Layout eingefügt wird. DieLayoutInformation-Klasse gibt die Grenzen der Layoutzuordnung eines Elements oder einen Slot zurück. Die Größe des Rechtecks wird festgelegt, indem der verfügbare Bildschirmplatz, die Größe eventuell vorhandener Einschränkungen, Layout-spezifische Eigenschaften wie Margin und Padding sowie das individuelle Verhalten des übergeordneten Panel-Elements berechnet werden. Durch Verarbeitung dieser Daten ist das Layoutsystem in der Lage, die Position aller untergeordneten Elemente für ein bestimmtes Panel zu berechnen. Sie sollten berücksichtigen, dass die auf dem übergeordneten Element definierten Größenanpassungsmerkmale (wie z. B. Border) die untergeordneten Elemente beeinflussen.
Die folgende Abbildung veranschaulicht ein einfaches Layout.
Dieses Layout kann mit dem folgenden XAML-Code erstellt werden.
<Grid Name="myGrid" Background="LightSteelBlue" Height="150">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Name="txt1" Margin="5" FontSize="16" FontFamily="Verdana" Grid.Column="0" Grid.Row="0">Hello World!</TextBlock>
<Button Click="getLayoutSlot1" Width="125" Height="25" Grid.Column="0" Grid.Row="1">Show Bounding Box</Button>
<TextBlock Name="txt2" Grid.Column="1" Grid.Row="2"/>
</Grid>
Ein einzelnes TextBlock-Element wird innerhalb eines Grid gehostet. Während der Text nur die linke obere Ecke der ersten Spalte ausfüllt, ist der reservierte Platz für den TextBlock eigentlich viel größer. Das umgebende Feld für ein FrameworkElement kann mit der GetLayoutSlot-Methode abgerufen werden. Die folgende Abbildung stellt das umgebende Feld für das TextBlock-Element dar.
Wie durch das gelbe Rechteck angezeigt, ist der reservierte Platz für das TextBlock-Element tatsächlich viel größer als angezeigt. Wenn dem Grid zusätzliche Elemente hinzugefügt werden, könnte diese Zuordnung abhängig von dem Typ und der Größe der hinzugefügten Elemente kleiner oder größer werden.
Der Layoutplatz des TextBlock wird mit der GetLayoutSlot-Methode in einen Path übersetzt. Diese Technik kann hilfreich sein, um das umgebende Feld eines Elements anzuzeigen.
Private Sub getLayoutSlot1(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim myRectangleGeometry As New RectangleGeometry
myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1)
Dim myGeometryDrawing As New GeometryDrawing
Dim myPath As New Path
myPath.Data = myRectangleGeometry
myPath.Stroke = Brushes.LightGoldenrodYellow
myPath.StrokeThickness = 5
Grid.SetColumn(myPath, 0)
Grid.SetRow(myPath, 0)
myGrid.Children.Add(myPath)
txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString()
End Sub
private void getLayoutSlot1(object sender, System.Windows.RoutedEventArgs e)
{
RectangleGeometry myRectangleGeometry = new RectangleGeometry();
myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1);
GeometryDrawing myGeometryDrawing = new GeometryDrawing();
Path myPath = new Path();
myPath.Data = myRectangleGeometry;
myPath.Stroke = Brushes.LightGoldenrodYellow;
myPath.StrokeThickness = 5;
Grid.SetColumn(myPath, 0);
Grid.SetRow(myPath, 0);
myGrid.Children.Add(myPath);
txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString();
}
Das Layoutsystem
Im einfachsten Fall ist das Layout ein rekursives System, das dafür sorgt, dass die Größe eines Elements angepasst, das Element positioniert und gezeichnet wird. Im Detail beschreibt das Layout den Prozess zum Messen und Anordnen der Member einer Children-Auflistung eines Panel-Elements. Layout ist ein intensiver Prozess. Je größer die Children-Auflistung, desto größer die Anzahl der Berechnungen, die vorgenommen werden müssen. Auch das Layoutverhalten, das vom Panel-Element (dem Besitzer der Auflistung) definiert wird, kann zu höherer Komplexität führen. Ein relativ einfacher Panel, z. B. Canvas, kann eine bedeutend bessere Leistung aufweisen als ein komplexerer Panel, z. B. Grid.
Jedes Mal, wenn ein untergeordnetes UIElement seine Position ändert, kann ein neuer Durchlauf des Layoutsystems ausgelöst werden. Sie sollten wissen, durch welche Ereignisse das Layoutsystem aufgerufen werden kann, da unnötige Aufrufe die Leistung der Anwendung verschlechtern können. Im Folgenden wird der Prozess beschrieben, der ausgeführt wird, wenn das Layoutsystem aufgerufen wird.
Ein untergeordnetes UIElement startet den Layoutprozess, indem es zunächst seine Kerneigenschaften messen lässt.
Die in FrameworkElement definierten Größeneigenschaften werden ausgewertet, z. B. Width, Height und Margin.
Die für das Panel-Element spezifische Logik wird angewendet, z. B. die Dock-Richtung oder die Stapel-Orientation.
Der Inhalt wird angeordnet, nachdem alle untergeordneten Elemente gemessen wurden.
Die Children-Auflistung wird auf dem Bildschirm gezeichnet.
Der Prozess wird erneut aufgerufen, wenn der Auflistung zusätzliche Children hinzugefügt werden, eine LayoutTransform angewendet oder die UpdateLayout-Methode aufgerufen wird.
Dieser Prozess und die Vorgehensweise zum Aufrufen des Prozesses wird in den folgenden Abschnitten ausführlich erläutert.
Messen und Anordnen von untergeordneten Elementen
Das Layoutsystem führt für jedes Member der untergeordneten Children-Auflistung zwei Durchläufe durch: eine Maßübergabe und eine Anordnungsübergabe. Jedes untergeordnete Panel-Element bietet eine eigene MeasureOverride-Methode und ArrangeOverride-Methode für das eigene spezifische Layoutverhalten.
Während der Maßübergabe wird jedes Member der Children-Auflistung ausgewertet. Der Prozess beginnt mit einem Aufruf an die Measure-Methode. Diese Methode wird innerhalb der Implementierung des übergeordneten Panel-Elements aufgerufen. Sie muss nicht explizit aufgerufen werden, um das Layout durchzuführen.
Zuerst werden systemeigene Größeneigenschaften für das UIElement ausgewertet, z. B. Clip und Visibility. Dies generiert einen Wert mit der Bezeichnung constraintSize, der an MeasureCore übergeben wird.
Danach werden auf FrameworkElement definierte Frameworkeigenschaften verarbeitet, was sich auf den Wert von constraintSize auswirkt. Diese Eigenschaften beschreiben normalerweise die Größenanpassungsmerkmale für das zugrunde liegende UIElement, z. B. Height, Width, Margin und Style. Jede dieser Eigenschaften kann den Raum ändern, der zur Anzeige des Elements erforderlich ist. MeasureOverride wird dann mit constraintSize als Parameter aufgerufen.
Hinweis |
---|
Es gibt einen Unterschied zwischen den Eigenschaften von Height, Width, ActualHeight und ActualWidth.Die ActualHeight-Eigenschaft ist beispielsweise ein berechneter Wert, der auf anderen Höheneingaben und dem Layoutsystem beruht.Der Wert wird von dem Layoutsystem selbst auf Grundlage des tatsächlichen Renderingdurchlaufs festgelegt und kann aus diesem Grund etwas kleiner sein als der Wert, der für Eigenschaften festgelegt wird, welche die Grundlage für die Eingabeänderung sind, wie z. B. Height. Da es sich bei ActualHeight um einen berechneten Wert handelt, sollten Sie daran denken, dass es mehrere oder inkrementelle gemeldete Änderungen dieses Werts geben kann, die das Ergebnis verschiedener Vorgänge des Layoutsystems sind.Unter Umständen meldet das Layoutsystem den erforderlichen Platz für untergeordnete Elemente, Einschränkungen durch das übergeordnete Element usw. |
Das Ziel der Maßübergabe ist letztendlich, dass das untergeordnete Element seine DesiredSize festlegt. Dies geschieht während des MeasureCore-Aufrufs. Der DesiredSize-Wert wird von Measure zur Verwendung während der Anordungsübergabe für den Inhalt gespeichert.
Die Anordnungsübergabe beginnt mit einem Aufruf an die Arrange-Methode. Während der Anordnungsübergabe generiert das übergeordnete Panel-Element ein Rechteck, das die Grenzen des untergeordneten Elements darstellt. Dieser Wert wird zur Verarbeitung an die ArrangeCore-Methode übergeben.
Die ArrangeCore-Methode wertet den DesiredSize-Wert des untergeordneten Elements aus und wertet alle zusätzlichen Ränder aus, die sich möglicherweise auf die gerenderte Größe des Elements auswirken. ArrangeCore generiert einen arrangeSize, der an die ArrangeOverride-Methode des Panel als Parameter übergeben wird. ArrangeOverride generiert den finalSize des untergeordneten Elements. Schließlich führt die ArrangeCore-Methode eine endgültige Auswertung der Offseteigenschaften, wie Rand und Abstand, durch und platziert das untergeordnete Element innerhalb seines Layoutplatzes. Das untergeordnete Element muss nicht (und wird häufig nicht) den gesamten zugeordneten Platz ausfüllen. Dem übergeordneten Panel-Element wird dann die Steuerung zurückgegeben, und der Layoutprozess ist abgeschlossen.
Panel-Elemente und benutzerdefiniertes Layoutverhalten
WPF schließt eine Gruppe von Elementen ein, die sich von Panel ableiten. DiesePanel-Elemente aktivieren viele komplexe Layouts. Das Stapeln von Elementen kann beispielsweise problemlos durch Verwenden des StackPanel-Elements erzielt werden, während ein Canvas komplexere, frei fließende Layouts ermöglicht.
Die folgende Tabelle bietet einen Überblick über die verfügbaren Panel-Layoutelemente.
Panel-Name |
Beschreibung |
---|---|
Definiert einen Bereich, in dem Sie untergeordnete Elemente explizit mithilfe von Koordinaten positionieren können, die relativ zum Canvas-Bereich sind. |
|
Definiert einen Bereich, in dem Sie untergeordnete Elemente entweder horizontal oder vertikal relativ zueinander anordnen können. |
|
Definiert einen flexiblen Rasterbereich, der aus Spalten und Zeilen besteht. |
|
Ordnet untergeordnete Elemente in einer einzelnen Zeile an, die horizontal oder vertikal ausgerichtet sein kann. |
|
Stellt ein Framework für Panel-Elemente bereit, die die Auflistung ihrer untergeordneten Daten virtualisieren. Dies ist eine abstrakte Klasse. |
|
Positioniert untergeordnete Elemente sequenziell von links nach rechts und umbricht den Inhalt am Rand des enthaltenden Felds auf die nächste Zeile. Je nach dem Wert der Orientation-Eigenschaft erfolgt die weitere Anordnung nacheinander von oben nach unten oder von rechts nach links. |
Für Anwendungen, die ein Layout erfordern, welches durch Verwendung der vordefinierten Panel-Elemente nicht erstellt werden kann, können benutzerdefinierte Layoutverhalten erzielt werden, indem Elemente von Panel erben und die MeasureOverride-Methode und die ArrangeOverride-Methode überschrieben werden. Ein Beispiel hierfür finden Sie unter Beispiel für einen benutzerdefinierten radial angeordneten Bereich.
Überlegungen zur Leistungsfähigkeit des Layouts
Das Layout ist ein rekursiver Prozess. Bei jedem Aufruf des Layoutystems werden alle untergeordneten Elemente in einer Children-Auflistung verarbeitet. Das Layoutsystem sollte also nur dann ausgelöst werden, wenn es notwendig ist. Die folgenden Überlegungen können Ihnen helfen, eine bessere Leistung zu erzielen.
Beachten Sie, welche Eigenschaftswertänderungen ein rekursives Update durch das Layoutsystem erzwingen.
Abhängigkeitseigenschaften, deren Werte bewirken können, dass das Layoutsystem initialisiert wird, werden mit öffentlichen Flags markiert. AffectsMeasure und AffectsArrange stellen nützliche Anhaltspunkte dazu bereit, welche Eigenschaftswertänderungen ein rekursives Update durch das Layoutsystem erzwingen. Im Allgemeinen kann jede Eigenschaft, die die Größe des umgebenden Felds eines Elements beeinflussen kann, das AffectsMeasure-Flag auf true setzen. Weitere Informationen finden Sie unter Übersicht über Abhängigkeitseigenschaften.
Verwenden Sie, wenn möglich, einen RenderTransform statt eines LayoutTransform.
LayoutTransform kann sehr nützlich sein, um den Inhalt einer user interface (UI) zu beeinflussen. Wenn sich der Effekt der Transformation jedoch nicht auf die Position anderer Elemente auswirken muss, empfiehlt es sich, stattdessen einen RenderTransform zu verwenden, da RenderTransform das Layoutsystem nicht aufruft. LayoutTransform wendet die Transformation an und erzwingt ein rekursives Layoutupdate, um die neue Position des betroffenen Elements zu berücksichtigen.
Vermeiden Sie unnötige Aufrufe an UpdateLayout.
Die UpdateLayout-Methode erzwingt eine rekursive Layoutaktualisierung und ist häufig nicht notwendig. Wenn Sie nicht sicher sind, ob eine vollständige Aktualisierung notwendig ist, verlassen Sie sich auf das Layoutsystem, das diese Methode für Sie aufruft.
Bei einer großen Children-Auflistung sollten Sie in Erwägung ziehen, ein VirtualizingStackPanel-Element anstelle des regulären StackPanel-Elements zu verwenden.
Durch das Virtualisieren der Auflistung untergeordneter Elemente behält VirtualizingStackPanel nur die Objekte im Speicher, die sich derzeit im ViewPort des übergeordneten Elements befinden. In den meisten Szenarien verbessert dies die Leistung erheblich.
Teilpixelrendering und Layoutrundung
Das WPF-Grafiksystem verwendet geräteunabhängige Einheiten, um Auflösungs- und Geräteunabhängigkeit zu ermöglichen. Jedes geräteunabhängige Pixel wird automatisch mit der dots per inch (dpi)-Einstellung des Systems skaliert. Dies ermöglicht WPF-Anwendungen eine ordnungsgemäße Skalierung für unterschiedliche dpi-Einstellungen und bewirkt, dass die Anwendung automatisch dpi-Einstellungen berücksichtigt.
Die dpi-Unabhängigkeit kann jedoch zu einem unregelmäßigen Rendern von Rändern aufgrund von Antialiasing führen. Diese im Allgemeinen als verschwommene oder halbtransparente Ränder wahrgenommenen Artefakte können auftreten, wenn ein Rand in der Mitte eines Gerätepixels und nicht zwischen Gerätepixeln liegt. Das Layoutsystem bietet eine Möglichkeit, dies mit der Layoutrundung zu korrigieren. Layoutrundung erfolgt, wenn das Layoutsystem während der Layoutübergabe alle nicht ganzzahligen Pixelwerte rundet.
Die Layoutrundung ist standardmäßig deaktiviert. Um die Layoutrundung zu aktivieren, legen Sie die UseLayoutRounding-Eigenschaft in einem beliebigen FrameworkElement auf true fest. Da es sich um eine Abhängigkeitseigenschaft handelt, wird der Wert in der visuellen Struktur an alle untergeordneten Elemente weitergegeben. Um die Layoutrundung für die gesamte Benutzeroberfläche zu aktivieren, legen Sie UseLayoutRounding im Stammcontainer auf true fest. Ein Beispiel finden Sie unter UseLayoutRounding.
Weitere Informationen
Der erste Schritt zum Verständnis des Layouts besteht darin, dass Sie wissen, wie Elemente gemessen und angeordnet werden. Weitere Informationen über die verfügbaren Panel-Elemente finden Sie unter Übersicht über Panel-Elemente. Informationen zu den verschiedenen Positionierungseigenschaften, die das Layout beeinflussen können, finden Sie unter Übersicht über Alignment, Margin und Padding. Ein Beispiel für ein benutzerdefiniertes Panel-Element finden Sie unter Beispiel für einen benutzerdefinierten radial angeordneten Bereich. Wenn Sie bereit sind, alles in einer Lightweightanwendung zusammenzufügen, lesen Sie Exemplarische Vorgehensweise: Erste Schritte mit WPF.