Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Erfahren Sie, wie Sie Code für eine benutzerdefinierte Panel-Klasse schreiben, die Methoden ArrangeOverride und MeasureOverride implementierenund die Children-Eigenschaft verwenden.
Wichtige APIs: Panel, ArrangeOverride, MeasureOverride
Der Beispielcode zeigt eine benutzerdefinierte Panelimplementierung, aber wir widmen uns nicht viel Zeit, um die Layoutkonzepte zu erläutern, die beeinflussen, wie Sie ein Panel für verschiedene Layoutszenarien anpassen können. Weitere Informationen zu diesen Layoutkonzepten und deren Anwendung auf Ihr bestimmtes Layoutszenario finden Sie in der Übersicht über benutzerdefinierte XAML-Panels.
Ein Panel ist ein Objekt, das ein Layoutverhalten für untergeordnete Elemente bereitstellt, die es enthält, wenn das XAML-Layoutsystem ausgeführt wird und die App-UI gerendert wird. Sie können benutzerdefinierte Panels für das XAML-Layout definieren, indem Sie eine benutzerdefinierte Klasse aus der klasse Panel ableiten. Sie definieren das Verhalten Ihres Panels, indem Sie die Methoden ArrangeOverride und MeasureOverride überschreiben und Logik bereitstellen, die die untergeordneten Elemente misst und anordnet. Dieses Beispiel wird von Panel abgeleitet. Wenn Sie mit Panel beginnen, haben die Methoden ArrangeOverride und MeasureOverride kein Startverhalten. Ihr Code stellt das Gateway bereit, mit dem untergeordnete Elemente dem XAML-Layoutsystem bekannt werden und in der Benutzeroberfläche gerendert werden. Daher ist es wirklich wichtig, dass Ihr Code alle Elemente berücksichtigt und den angegebenen Mustern folgt, die das Layoutsystem erwartet.
Ihr Layoutszenario
Wenn Sie ein benutzerdefiniertes Panel definieren, legen Sie ein Layoutszenario fest.
Ein Layoutszenario wird durch Folgendes ausgedrückt:
- Was das Panel machen wird, wenn es untergeordnete Elemente hat.
- Wenn das Panel Einschränkungen für seinen eigenen Platz hat
- Wie die Logik des Panels alle Maße, Platzierungen, Positionen und Größen bestimmt, die letztendlich zu einem gerenderten Benutzeroberflächen-Layout (UI-Layout) untergeordneter Elemente führen
Das hier gezeigte BoxPanel ist für ein bestimmtes Szenario vorgesehen. Im Interesse, den Code in diesem Beispiel in erster Linie zu behalten, werden wir das Szenario noch nicht ausführlich erläutern und konzentrieren uns stattdessen auf die erforderlichen Schritte und die Codierungsmuster. Wenn Sie zuerst mehr über das Szenario wissen möchten, fahren Sie mit "Das Szenario für BoxPanel" fort, und kehren Sie dann zum Code zurück.
Beginnen Sie mit der Ableitung von Panel
Beginnen Sie, indem Sie eine benutzerdefinierte Klasse von Panel ableiten. Wahrscheinlich ist die einfachste Möglichkeit, dies zu tun, eine separate Codedatei für diese Klasse zu definieren, indem Sie die Kontextmenüoptionen Add | Neues Element | Class für ein Projekt aus dem Solution Explorer in Microsoft Visual Studio verwenden. Benennen Sie die Klasse (und Datei) BoxPanel.
Die Vorlagendatei für eine Klasse beginnt nicht mit vielen using-Anweisungen , da sie nicht speziell für Windows-Apps bestimmt ist. Fügen Sie also zunächst using-Anweisungen hinzu. Die Vorlagendatei beginnt auch mit einigen using-Anweisungen, die Sie wahrscheinlich nicht benötigen und wahrscheinlich gelöscht werden können. Hier ist eine vorgeschlagene Liste von using-Anweisungen, um Typen aufzulösen, die Sie für typischen benutzerdefinierten Panel-Code benötigen:
using System;
using System.Collections.Generic; // if you need to cast IEnumerable for iteration, or define your own collection properties
using Windows.Foundation; // Point, Size, and Rect
using Windows.UI.Xaml; // DependencyObject, UIElement, and FrameworkElement
using Windows.UI.Xaml.Controls; // Panel
using Windows.UI.Xaml.Media; // if you need Brushes or other utilities
Jetzt, da Sie Panel auflösen können, machen Sie es zur Basisklasse von BoxPanel. Machen Sie BoxPanel außerdem öffentlich:
public class BoxPanel : Panel
{
}
Definieren Sie auf Klassenebene einige Int - und Double-Werte , die von mehreren Ihrer Logikfunktionen gemeinsam verwendet werden, die jedoch nicht als öffentliche API verfügbar gemacht werden müssen. Im Beispiel lauten sie: maxrc, rowcount, colcount, cellwidth, cellheight, maxcellheight, aspectratio.
Nachdem Sie dies getan haben, sieht die vollständige Codedatei wie folgt aus (Entfernen von Kommentaren zur Verwendung, jetzt wissen Sie, warum wir sie haben):
using System;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
public class BoxPanel : Panel
{
int maxrc, rowcount, colcount;
double cellwidth, cellheight, maxcellheight, aspectratio;
}
Von hier aus zeigen wir Ihnen jeweils eine Elementdefinition an, sei es, dass eine Methode außer Kraft setzen oder etwas unterstützt, z. B. eine Abhängigkeitseigenschaft. Sie können diese dem obigen Skelett in beliebiger Reihenfolge hinzufügen.
MeasureOverride
protected override Size MeasureOverride(Size availableSize)
{
// Determine the square that can contain this number of items.
maxrc = (int)Math.Ceiling(Math.Sqrt(Children.Count));
// Get an aspect ratio from availableSize, decides whether to trim row or column.
aspectratio = availableSize.Width / availableSize.Height;
// Now trim this square down to a rect, many times an entire row or column can be omitted.
if (aspectratio > 1)
{
rowcount = maxrc;
colcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
}
else
{
rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
colcount = maxrc;
}
// Now that we have a column count, divide available horizontal, that's our cell width.
cellwidth = (int)Math.Floor(availableSize.Width / colcount);
// Next get a cell height, same logic of dividing available vertical by rowcount.
cellheight = Double.IsInfinity(availableSize.Height) ? Double.PositiveInfinity : availableSize.Height / rowcount;
foreach (UIElement child in Children)
{
child.Measure(new Size(cellwidth, cellheight));
maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight;
}
return LimitUnboundedSize(availableSize);
}
Das erforderliche Muster einer MeasureOverride-Implementierung ist die Schleife durch jedes Element in Panel.Children. Rufen Sie immer die Measure-Methode für jedes dieser Elemente auf. Measure hat einen Parameter vom Typ Size. Die Größe, die Sie hier angeben, ist die Größe, die Ihr Panel bereitstellt, um für dieses bestimmte untergeordnete Element verfügbar zu sein. Bevor Sie also die loop ausführen und mit dem Aufrufen von Measure beginnen können, müssen Sie wissen, wie viel Platz jede Zelle aufnehmen kann. In der MeasureOverride-Methode steht Ihnen der availableSize-Wert zur Verfügung. Dies ist die Größe, die das übergeordnete Element des Panels beim Aufrufen von Measure verwendet hat, was der Auslöser dafür war, dass diese MeasureOverride aufgerufen wurde. Eine typische Logik besteht also darin, ein Schema zu entwickeln, bei dem jedes untergeordnete Element den Raum der gesamten verfügbaren Größe des Panels teilt. Anschließend übergeben Sie jede Größenteilung an Measure jedes untergeordneten Elements.
Die Größenaufteilung bei BoxPanel ist relativ einfach: Es teilt den verfügbaren Raum in eine Anzahl von Boxen auf, die weitgehend durch die Anzahl der Elemente bestimmt wird. Boxen werden basierend auf der Zeilen- und Spaltenanzahl sowie der verfügbaren Größe festgelegt. Manchmal wird eine Zeile oder Spalte aus einem Quadrat nicht benötigt, daher wird sie verworfen, und das Panel wird anstelle eines Quadrats zu einem Rechteck in Bezug auf das Zeilen : Spalten-Verhältnis. Weitere Informationen dazu, wie diese Logik erreicht wurde, fahren Sie mit "Das Szenario für BoxPanel" fort.
Was bewirkt also die Maßnahme? Es legt einen Wert für die schreibgeschützte DesiredSize-Eigenschaft für jedes Element fest, in dem Measure aufgerufen wurde. Ein DesiredSize-Wert ist möglicherweise wichtig, sobald Sie zum Anordnungsdurchlauf gelangen, da die DesiredSize kommuniziert, was die Größe beim Anordnen und im endgültigen Rendering sein kann oder sollte. Auch wenn Sie DesiredSize nicht in Ihrer eigenen Logik verwenden, benötigt das System es weiterhin.
Es ist möglich, dass dieser Bereich verwendet werden kann, wenn die Höhenkomponente von availableSize ungebunden ist. Wenn dies der Fall ist, verfügt das Panel nicht über eine bekannte Höhe zum Dividieren. In diesem Fall informiert die Logik des Messvorgangs jedes untergeordnete Element, dass es noch keine begrenzte Höhe hat. Dies geschieht durch Übergeben einer Größe an den Measure-Aufruf für untergeordnete Elemente, bei denen Size.Height unendlich ist. Das ist legal. Wenn Measure aufgerufen wird, besteht die Logik darin, dass die DesiredSize-Eigenschaft auf das Minimum von dem festgelegt wird, was an Measure übergeben wurde oder der natürlichen Größe des Elements, die aus Faktoren wie einer explizit festgelegten Höhe und Breite resultiert.
Hinweis
Die interne Logik von StackPanel zeigt auch dieses Verhalten: StackPanel übergibt einen unbegrenzten Dimensionswert an Measure für die Child-Elemente, was anzeigt, dass es keine Einschränkung für diese in der Ausrichtungsdimension gibt. StackPanel passt seine Größe in der Regel dynamisch an, um alle untergeordneten Elemente in einem Stapel aufzunehmen, der in dieser Dimension wächst.
Das Panel selbst kann jedoch keine Größe mit einem unendlichen Wert aus MeasureOverride zurückgeben, da dies während des Layouts eine Ausnahme auslöst. Ein Teil der Logik besteht also darin, die maximale Höhe zu ermitteln, die von allen untergeordneten Elementen angefordert wird, und diese Höhe als Zellenhöhe zu verwenden, falls diese nicht bereits aus den eigenen Größenbeschränkungen des Panels stammt. Hier ist die Hilfsfunktion LimitUnboundedSize, auf die im vorherigen Code verwiesen wurde. Sie nimmt die maximale Zellhöhe und verwendet diese, um dem Panel eine begrenzte Höhe zu geben und stellt sicher, dass cellheight eine begrenzte Zahl ist, bevor der Anordnungsdurchlauf initiiert wird.
// This method limits the panel height when no limit is imposed by the panel's parent.
// That can happen to height if the panel is close to the root of main app window.
// In this case, base the height of a cell on the max height from desired size
// and base the height of the panel on that number times the #rows.
Size LimitUnboundedSize(Size input)
{
if (Double.IsInfinity(input.Height))
{
input.Height = maxcellheight * colcount;
cellheight = maxcellheight;
}
return input;
}
ArrangeOverride
protected override Size ArrangeOverride(Size finalSize)
{
int count = 1;
double x, y;
foreach (UIElement child in Children)
{
x = (count - 1) % colcount * cellwidth;
y = ((int)(count - 1) / colcount) * cellheight;
Point anchorPoint = new Point(x, y);
child.Arrange(new Rect(anchorPoint, child.DesiredSize));
count++;
}
return finalSize;
}
Das erforderliche Muster einer ArrangeOverrideImplementierung ist die Schleife durch jedes Element in Panel.Children. Rufen Sie immer die Arrange-Methode für jedes dieser Elemente auf.
Beachten Sie, dass es nicht so viele Berechnungen gibt wie in MeasureOverride; das ist typisch. Die Größe von untergeordneten Elementen ist bereits aus der eigenen MeasureOverride-Logik des Panels oder aus dem DesiredSize-Wert der einzelnen untergeordneten Elemente bekannt, der während des Messdurchgangs festgelegt wurde. Wir müssen jedoch noch entscheiden, an welcher Stelle innerhalb des Panels jedes Kind angezeigt wird. In einem typischen Panel sollte jedes Kind an einer anderen Position gerendert werden. Ein Panel, das überlappende Elemente erstellt, ist für typische Szenarien nicht wünschenswert (obwohl es nicht ausgeschlossen ist, Panels mit gezielten Überschneidungen zu erstellen, wenn das wirklich Ihr beabsichtigtes Szenario ist).
Dieses Panel wird nach dem Konzept von Zeilen und Spalten angeordnet. Die Anzahl der Zeilen und Spalten wurde bereits berechnet (da dies für die Messung notwendig war). Jetzt tragen also die Form der Zeilen und Spalten sowie die bekannten Größen jeder Zelle zur Logik der Definition einer Renderingposition (der anchorPoint) für jedes Element bei, das dieses Panel enthält. Dieser Punkt wird zusammen mit der Größe, die bereits aus dem Messen bekannt ist, als die beiden Komponenten verwendet, die ein Rect bilden.
Rect ist der Eingabetyp für "Anordnen".
Panels müssen ihre Inhalte manchmal abschneiden. Wenn dies der Fall ist, ist die beschnittene Größe die Größe, die in DesiredSize vorhanden ist, da die Measure-Logik sie als Minimum dessen festlegt, was an Measure übergeben wurde, oder als andere natürliche Größenfaktoren. Daher müssen Sie in der Regel während der Anordnung nicht speziell auf Clipping achten; das Clipping erfolgt einfach durch die Übergabe der DesiredSize bei jedem Arrange-Aufruf.
Sie benötigen nicht immer eine Zählung, während Sie die loop durchlaufen, wenn alle Informationen, die Sie zum Definieren der Renderingposition benötigen, auf andere Wege bekannt sind. In der Canvas-Layoutlogik spielt die Position in der Children-Auflistung z. B. keine Rolle. Alle Informationen, die zum Positionieren der einzelnen Elemente in einer Canvas erforderlich sind, werden durch das Lesen der Canvas.Left- und Canvas.Top-Werte der untergeordneten Elemente als Teil der Logik zur Anordnung bekannt. Die BoxPanel Logik benötigt eine Zählung, um mit der Anzahl zu vergleichen, damit bekannt ist, wann eine neue Zeile begonnen und der Y-Wert versetzt wird.
Es ist typisch, dass die Eingabe finalSize und die Größe , die Sie aus einer ArrangeOverride-Implementierung zurückgeben, identisch sind. Weitere Informationen dazu finden Sie im Abschnitt "ArrangeOverride" der Übersicht über benutzerdefinierte XAML-Panels.
Eine Verfeinerung: Kontrolle über die Zeilen- vs. Spaltenanzahl
Sie können dieses Panel so wie es jetzt ist kompilieren und verwenden. Wir fügen jedoch eine weitere Verfeinerung hinzu. Im gerade gezeigten Code platziert die Logik die zusätzliche Zeile oder Spalte auf der Seite, die am längsten im Seitenverhältnis ist. Für eine größere Kontrolle über die Formen von Zellen kann es jedoch wünschenswert sein, einen 4x3-Satz von Zellen anstelle von 3x4 auszuwählen, auch wenn das eigene Seitenverhältnis des Panels "Hochformat" ist. Daher fügen wir eine optionale Abhängigkeitseigenschaft hinzu, die das Panel consumer festlegen kann, um dieses Verhalten zu steuern. Hier ist die Definition der Abhängigkeitseigenschaft, die sehr einfach ist:
// Property
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
// Dependency Property Registration
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(BoxPanel), new PropertyMetadata(null, OnOrientationChanged));
// Changed callback so we invalidate our layout when the property changes.
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
if (dependencyObject is BoxPanel panel)
{
panel.InvalidateMeasure();
}
}
Und unten sehen Sie, wie sich die Verwendung von Orientation auf die Messlogik in MeasureOverride auswirkt. Eigentlich ändert sich nur die Art und Weise, wie rowcount und colcount aus maxrc und dem tatsächlichen Seitenverhältnis abgeleitet werden, und aufgrund dessen gibt es entsprechende Größenunterschiede für jede Zelle. Ist Orientation"Vertikal" (Standard), wird der Wert des tatsächlichen Seitenverhältnisses invertiert, bevor er für die Zeilen- und Spaltenanzahl in unserem Rechtecklayout im "Hochformat" verwendet wird.
// Get an aspect ratio from availableSize, decides whether to trim row or column.
aspectratio = availableSize.Width / availableSize.Height;
// Transpose aspect ratio based on Orientation property.
if (Orientation == Orientation.Vertical) { aspectratio = 1 / aspectratio; }
Das Szenario für BoxPanel
Das besondere Szenario für BoxPanel ist, dass es sich um ein Panel handelt, in dem einer der wichtigsten Faktoren bei der Aufteilung des Raums darin besteht, die Anzahl der untergeordneten Elemente zu kennen und den bekannten verfügbaren Raum des Panels aufzuteilen. Panels sind von Natur aus rechteckige Formen. Viele Panels funktionieren, indem sie diesen Rechteckraum in weitere Rechtecke unterteilen; Dies ist die Funktion von Grid für seine Zellen. In Grids Fall wird die Größe der Zellen durch ColumnDefinition - und RowDefinition-Werte festgelegt, und Elemente deklarieren die genaue Zelle, in die sie mit den angefügten Eigenschaften "Grid.Row" und "Grid.Column " eingefügt werden. Das Abrufen eines guten Layouts aus einem Raster erfordert in der Regel, die Anzahl der untergeordneten Elemente vorher zu kennen, sodass genügend Zellen vorhanden sind und jedes untergeordnete Element seine angefügten Eigenschaften so festlegt, dass es in seine eigene Zelle passt.
Aber was geschieht, wenn die Anzahl der Kinder dynamisch ist? Das ist sicher möglich; Ihr App-Code kann Sammlungen Elemente hinzufügen, als Reaktion auf jede dynamische Laufzeitbedingung, die Sie als wichtig genug erachten, um die Benutzeroberfläche zu aktualisieren. Wenn Sie Datenbindung zum Sichern von Sammlungen/Geschäftsobjekten verwenden, wird das Abrufen solcher Aktualisierungen und das Aktualisieren der Benutzeroberfläche automatisch behandelt, sodass dies häufig die bevorzugte Technik ist (siehe Datenbindung im Detail).
Aber nicht alle App-Szenarien eignen sich für die Datenbindung. Manchmal müssen Sie zur Laufzeit neue UI-Elemente erstellen und sichtbar machen.
BoxPanel ist für dieses Szenario vorgesehen. Eine Änderung der Anzahl der untergeordneten Elemente ist kein Problem für BoxPanel, weil es die Anzahl der untergeordneten Elemente in Berechnungen verwendet und sowohl die vorhandenen als auch die neuen untergeordneten Elemente in ein neues Layout integriert, so dass alle reinpassen.
Ein erweitertes Szenario, um BoxPanel weiter zu erweitern (nicht hier gezeigt), könnte sowohl dynamische untergeordnete Elemente aufnehmen als auch die DesiredSize eines untergeordneten Elements als stärkerer Faktor bei der Größenanpassung einzelner Zellen verwenden. In diesem Szenario können unterschiedliche Zeilen- oder Spaltengrößen sowie nicht-gitterförmige Strukturen verwendet werden, damit weniger Platz *verschwendet* wird. Dies erfordert eine Strategie, wie mehrere Rechtecke verschiedener Größen und Seitenverhältnisse in ein umschließendes Rechteck passen können, sowohl aus ästhetischen Gründen als auch um die kleinstmögliche Größe zu erreichen.
BoxPanel tut das nicht; Es wird eine einfachere Technik zum Teilen des Raums verwendet.
BoxPanelDie Technik besteht darin, die kleinste quadratische Zahl zu bestimmen, die größer als die untergeordnete Anzahl ist. Beispielsweise würden 9 Elemente in ein Quadrat mit 3 x 3 passen. Für 10 Elemente ist ein Quadrat von 4 x 4 erforderlich. Sie können jedoch häufig Elemente anpassen, während Sie weiterhin eine Zeile oder eine Spalte des Ausgangsquadrats entfernen, um Platz zu sparen. Im Beispiel mit count=10 passt das in ein 4x3- oder 3x4-Rechteck.
Vielleicht fragen Sie sich, warum das Panel nicht stattdessen 5x2 für 10 Elemente wählen würde, da dies gut zur Anzahl der Elemente passt. In der Praxis werden Panels jedoch als Rechtecke bemessen, die selten ein stark ausgeprägtes Seitenverhältnis aufweisen. Die Methode der kleinsten Quadrate ist eine Möglichkeit, die Größenanpassungslogik so zu beeinflussen, dass sie gut mit typischen Layoutformen funktioniert und nicht die Größenanpassung dort fördert, wo die Zellformen ungerade Seitenverhältnisse erhalten.
Zugehörige Themen
Referenz
Konzepte
Windows developer