Zeichnen von Formen

Informationen zum Zeichnen von Formen wie Ellipsen, Rechtecken, Polygonen und Pfaden Mit der Klasse Pfad lassen sich ziemlich komplexe vektorbasierte Zeichnungssprache in einer XAML-Benutzeroberfläche visualisieren. Sie können z. B. Bézierkurven zeichnen.

Zwei Sätze von Klassen definieren einen Bereich in der XAML-Benutzeroberfläche: Form-Klassen und Geometrie-Klassen. Der Hauptunterschied zwischen diesen Klassen besteht darin, dass eine Form einen zugeordneten Pinsel hat und auf dem Bildschirm gerendert werden kann, während eine Geometrie einfach einen Bereich definiert und nur gerendert wird, wenn sie Informationen zu einer anderen Eigenschaft der Benutzeroberfläche beiträgt. Sie können sich eine Form als UIElement vorstellen, dessen Grenze durch eine Geometrie definiert ist. In diesem Thema werden in erster Linie die Form-Klassen behandelt.

Die Form-Klassen sind Linie, Ellipse, Rechteck, Polygon, Polylinie und Pfad. Der Pfad ist interessant, da er eine beliebige Geometrie definieren kann und die Geometrie-Klasse wird hier behandelt, weil sie eine Möglichkeit darstellt, die Teile eines Pfads zu definieren.

UWP und WinUI 2

Wichtig

Die Informationen und Beispiele in diesem Artikel sind für Apps optimiert, die das Windows App SDK und WinUI 3 verwenden, gelten jedoch allgemein für UWP-Apps, die WinUI 2 verwenden. In der UWP-API-Referenz finden Sie plattformspezifische Informationen und Beispiele.

Dieser Abschnitt enthält Informationen, die Sie zum Verwenden des Steuerelements in einer UWP- oder WinUI 2-App benötigen.

APIs für diese Formen gibt es im Windows.UI.Xaml.Shapes-Namespace.

Füllung und Strich für Formen

Damit eine Form im App-Canvas gerendert werden kann, müssen Sie ihr einen Pinsel zuordnen. Legen Sie die Eigenschaft Füllung der Form auf den gewünschten Pinsel fest. Weitere Informationen zu Pinseln finden Sie unter Verwenden von Pinseln.

Eine Form kann auch einen Strich haben, bei dem es sich um eine um den Umriss der Form gezeichnete Linie handelt. Ein Strich braucht auch einen Pinsel, der sein Aussehen bestimmt und einen Wert ungleich Null für StrokeThickness haben sollte. StrokeThickness ist eine Eigenschaft, die die Stärke der Umrisslinie der Form bestimmt. Wenn Sie keinen Pinselwert für Strich angeben oder StrokeThickness auf 0 festlegen, wird kein Rahmen um die Form gezeichnet.

Ellipse

Eine Ellipse ist eine Form mit gebogenem Umriss. Geben Sie zum Erstellen einer einfachen Ellipse eine Breite, eine Höhe und einen Pinsel für die Füllung an.

Im nächsten Beispiel wird eine Ellipse mit einer Breite von 200 und einer Höhe von 200 erstellt und der Pinsel SolidColorBrush in der Farbe SteelBlue als Füllung verwendet.

<Ellipse Fill="SteelBlue" Height="200" Width="200" />
var ellipse1 = new Ellipse();
ellipse1.Fill = new SolidColorBrush(Colors.SteelBlue);
ellipse1.Width = 200;
ellipse1.Height = 200;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(ellipse1);

Hier sehen Sie die gerenderte Ellipse.

A rendered Ellipse.

In diesem Fall würden die meisten Personen die Ellipse für einen Kreis halten, aber so wird eine Kreisform in XAML deklariert: eine Ellipse mit gleicher Breite und Höhe.

Wenn eine Ellipse in einem Benutzeroberflächenlayout positioniert wird, wird für ihre Größe die Größe des Rechteck mit dieser Breite und Höhe angenommen. Der Bereich außerhalb des Umrisses wird nicht gerendert, gehört aber dennoch zur Größe des Layout-Slots.

Ein Satz von 6 Ellipsen-Elementen ist Teil der Steuerelementvorlage für das Steuerelement ProgressRing, und 2 konzentrische Ellipsen-Elemente sind Teil eines RadioButton.

Rectangle

Ein Rechteck ist eine vierseitige Form, deren gegenüberliegende Seiten gleich sind. Geben Sie zum Erstellen eines einfachen Rechtecks eine Breite, eine Höhe und eine Füllung an.

Die Ecken eines Rechtecks können abgerundet werden. Geben Sie zum Erstellen abgerundeter Ecken einen Wert für die Eigenschaften RadiusX und RadiusY an. Diese Eigenschaften bestimmen die X-Achse und die Y-Achse einer Ellipse, die die Kurve der Ecken definiert. Der maximal zulässige Wert von RadiusX ist die Breite dividiert durch zwei, und der maximal zulässige Wert von RadiusY ist die Höhe dividiert durch zwei.

Im nächsten Beispiel wird ein Rechteck mit einer Breite von 200 und einer Höhe von 100 erstellt. Es hat den Wert Blue von SolidColorBrush für seine Füllung und den Wert Black von SolidColorBrush für seinen Strich. Wir legen die StrokeThickness auf 3 fest. Wir legen die Eigenschaft RadiusX auf 50 und die Eigenschaft RadiusY auf 10 fest, wodurch das Rechteck abgerundete Ecken erhält.

<Rectangle Fill="Blue"
           Width="200"
           Height="100"
           Stroke="Black"
           StrokeThickness="3"
           RadiusX="50"
           RadiusY="10" />
var rectangle1 = new Rectangle();
rectangle1.Fill = new SolidColorBrush(Colors.Blue);
rectangle1.Width = 200;
rectangle1.Height = 100;
rectangle1.Stroke = new SolidColorBrush(Colors.Black);
rectangle1.StrokeThickness = 3;
rectangle1.RadiusX = 50;
rectangle1.RadiusY = 10;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(rectangle1);

Hier sehen Sie das gerenderte Rechteck.

A rendered Rectangle.

Es gibt einige Szenarien für Benutzeroberflächendefinitionen, bei denen anstelle eines Rechtecks ein Rahmen besser geeignet sein könnte. Wenn Sie eine Rechteckform um andere Inhalte herum erstellen möchten, könnte es besser sein, einen Rahmen zu verwenden, weil er untergeordnete Inhalte enthalten kann und die Größe um diese Inhalte herum automatisch anpasst, anstatt die festen Abmessungen für Höhe und Breite wie beim Rechteck zu verwenden. Bei einem Rahmen gibt es ebenfalls die Möglichkeit abgerundeter Ecken, wenn Sie die Eigenschaft CornerRadius festlegen.

Andererseits ist ein Rechteck wahrscheinlich eine bessere Wahl für die Zusammensetzung von Steuerelementen. Eine Rechteck-Form kommt in vielen Steuerelementvorlagen vor, weil sie als „FocusVisual“-Teil für fokussierbare Steuerelemente verwendet wird. Immer wenn das Steuerelement den visuellen Status „Fokussiert“ hat, wird dieses Rechteck sichtbar gemacht, bei anderem Status wird es ausgeblendet.

Polygon

Ein Polygon ist eine Form mit einer Grenze, die durch eine beliebige Anzahl von Punkten definiert wird. Zum Erstellen der Grenze wird immer ein Punkt durch eine Linie mit dem nächsten verbunden, und der letzte Punkt wird mit dem ersten Punkt verbunden. Die Eigenschaft Punkte definiert die Sammlung von Punkten, die die Grenze bilden. In XAML definieren Sie die Punkte mit einer durch Kommas getrennten Liste. In CodeBehind verwenden Sie eine PointCollection zum Definieren der Punkte und fügen jeden einzelnen Punkt als Punkt-Wert zur Sammlung hinzu.

Sie müssen die Punkte nicht explizit so deklarieren, dass Startpunkt und Endpunkt als derselbe Punkt-Wert angegeben werden. Die Renderinglogik für ein Polygon geht davon aus, dass Sie eine geschlossene Form definieren, und verbindet den Endpunkt automatisch mit dem Startpunkt.

Im nächsten Beispiel wird ein Polygon mit vier Punkten erstellt, die auf (10,200), (60,140), (130,140) und (180,200) festgelegt sind. Es hat den Wert LightBlue von SolidColorBrush für seine Füllung und hat keinen Wert für Strich und somit keine Umrisslinie.

<Polygon Fill="LightBlue"
         Points="10,200,60,140,130,140,180,200" />
var polygon1 = new Polygon();
polygon1.Fill = new SolidColorBrush(Colors.LightBlue);

var points = new PointCollection();
points.Add(new Windows.Foundation.Point(10, 200));
points.Add(new Windows.Foundation.Point(60, 140));
points.Add(new Windows.Foundation.Point(130, 140));
points.Add(new Windows.Foundation.Point(180, 200));
polygon1.Points = points;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(polygon1);

Hier sehen Sie das gerenderten Polygon.

A rendered Polygon.

Tipp

Ein Punkt-Wert wird in XAML oft als Typ für andere Szenarien als das Deklarieren der Eckpunkte von Formen verwendet. Beispielsweise ist ein Punkt Teil der Ereignisdaten für Touchereignisse, sodass man genau wissen kann, wo in einem Koordinatenraum die Touchaktion erfolgt ist. Weitere Informationen zu Punkt und seiner Verwendung in XAML oder im Code finden Sie im API-Referenzthema zu Punkt.

Line

Eine Linie ist einfach eine Linie, die zwischen zwei Punkten im Koordinatenraum gezeichnet wird. Bei einer Linie wird der für Füllung angegebene Wert ignoriert, weil sie keinen Innenbereich hat. Für eine Linie müssen Sie unbedingt Werte für die Eigenschaften Strich und StrokeThickness angeben, da die Linie sonst nicht gerendert wird.

Sie verwenden keine Punkt-Werte zum Bestimmen einer Linien-Form, sondern stattdessen diskrete doppelte Werte für X1, Y1, X2 undY2. Dies ermöglicht minimales Markup für horizontale oder vertikale Linien. <Line Stroke="Red" X2="400"/> definiert beispielsweise eine horizontale Linie, die 400 Pixel lang ist. Die anderen X,Y-Eigenschaften sind standardmäßig 0, sodass dieser XAML-Code im Hinblick auf die Punkte eine Linie von (0,0) zu (400,0) zeichnen würde. Sie können dann mit TranslateTransform die gesamte Linie verschieben, wenn sie an einem anderen Punkt als (0,0) beginnen soll.

<Line Stroke="Red" X2="400"/>
var line1 = new Line();
line1.Stroke = new SolidColorBrush(Colors.Red);
line1.X2 = 400;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(line1);

Polyline

Eine Polylinie hat dahingehend Ähnlichkeit mit einem Polygon, dass die Grenze der Form durch eine Reihe von Punkten definiert wird, außer dass der letzte Punkt einer Polylinie nicht mit dem ersten Punkt verbunden wird.

Hinweis

Sie könnten zwar unter Punkte explizit einen identischen Start- und Endpunkt für die Polylinie angeben, aber dann hätten Sie stattdessen auch ein Polygon verwenden können.

Wenn Sie eine Füllung für eine Polylinie angeben, malt die Füllung den Innenbereich der Form aus, obwohl sich der Startpunkt und der Endpunkt der festgelegten Punkte für die Polylinie nicht überschneiden. Wenn Sie keine Füllung angeben, entspricht die Polylinie dem gerenderten Ergebnis bei Angabe mehrerer einzelner Linien-Elemente, bei denen die Start- und Endpunkte aufeinander folgender Linien sich überschneiden.

Wie bei einem Polygon definiert die Eigenschaft Punkte die Sammlung von Punkten, die die Grenze bilden. In XAML definieren Sie die Punkte mit einer durch Kommas getrennten Liste. In CodeBehind verwenden Sie eine PointCollection zum Definieren der Punkte und fügen jeden einzelnen Punkt als Punkt-Struktur zur Sammlung hinzu.

In diesem Beispiel wird eine Polylinie mit vier Punkten erstellt, die auf (10,200), (60,140), (130,140) und (180,200) festgelegt sind. Es ist ein Strich definiert, aber keine Füllung.

<Polyline Stroke="Black"
          StrokeThickness="4"
          Points="10,200,60,140,130,140,180,200" />
var polyline1 = new Polyline();
polyline1.Stroke = new SolidColorBrush(Colors.Black);
polyline1.StrokeThickness = 4;

var points = new PointCollection();
points.Add(new Windows.Foundation.Point(10, 200));
points.Add(new Windows.Foundation.Point(60, 140));
points.Add(new Windows.Foundation.Point(130, 140));
points.Add(new Windows.Foundation.Point(180, 200));
polyline1.Points = points;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(polyline1);

Dies ist die gerenderte Polylinie. Beachten Sie, dass der erste und letzte Punkt nicht durch den Strich-Umriss miteinander verbunden sind wie bei einem Polygon.

A rendered Polyline.

Pfad

Ein Pfad ist die vielseitigste Form, weil er zum Definieren einer beliebigen Geometrie verwendet werden kann. Diese Vielseitigkeit bringt aber Komplexität mit sich. Sehen wir uns nun an, wie Sie einen einfachen Pfad in XAML erstellen können.

Sie definieren die Geometrie eines Pfads mit der Eigenschaft Daten. Es gibt zwei Techniken zum Festlegen von Daten:

  • Sie können einen Zeichenkettenwert für Daten in XAML festlegen. In dieser Form verwendet der Wert Path.Data ein Serialisierungsformat für Grafiken. Nach dem ersten Erstellen wird dieser Wert in der Regel nicht in Zeichenfolgenform bearbeitet. Stattdessen werden Designtools verwendet, mit denen Sie in einer Design- oder Zeichnungsmetapher auf einer Oberfläche arbeiten können. Anschließend speichern oder exportieren Sie die Ausgabe. Dadurch erhalten Sie eine XAML-Datei oder ein XAML-Zeichenkettenfragment mit Path.Data-Informationen.
  • Sie können die Eigenschaft Daten auf ein einzelnes Geometrie-Objekt festlegen. Dies kann im Code oder in XAML erfolgen. Diese einzelne Geometrie ist in der Regel eine GeometryGroup, die als Container fungiert, der für die Zwecke des Objektmodells mehrere Geometriedefinitionen zu einem einzelnen Objekt zusammensetzen kann. Der häufigste Grund hierfür ist, dass Sie eine oder mehrere der Kurven und komplexen Formen verwenden möchten, die als Segment-Werte für eine PathFigure definiert werden können, z. B. BezierSegment.

Dieses Beispiel zeigt einen Pfad, der aus der Verwendung von Blend für Visual Studio hervorgegangen sein kann, mit der nur einige Vektorformen erzeugt und das Ergebnis als XAML gespeichert wurde. Der gesamte Pfad besteht aus einem Bézierkurvensegment und einem Liniensegment. Das Beispiel soll Ihnen in erster Linie einige Beispiele dafür geben, welche Elemente es im Serialisierungsformat Path.Data gibt und wofür die Zahlen stehen.

Diese Daten beginnen mit dem Verschiebungsbefehl, der durch „M“ gekennzeichnet ist, wodurch ein absoluter Startpunkt für den Pfad festgelegt wird.

Das erste Segment ist eine bei (100,200) beginnende und bei (400,175) endende kubische Bézierkurve, die anhand der beiden Kontrollpunkte (100,25) und (400,350) gezeichnet wird. Dieses Segment ist durch den „C“-Befehl in der Attributzeichenfolge Daten gekennzeichnet.

Das zweite Segment beginnt mit einem absoluten horizontalen Linienbefehl „H“, der eine Linie vom vorherigen untergeordneten Endpunkt (400,175) zu einem neuen Endpunkt (280,175) zeichnet. Da es sich um einen horizontalen Linienbefehl handelt, ist der angegebene Wert eine x-Koordinate.

<Path Stroke="DarkGoldenRod" 
      StrokeThickness="3"
      Data="M 100,200 C 100,25 400,350 400,175 H 280" />

Hier sehen Sie den gerenderten Pfad.

Screenshot of a simple rendered path.

Das nächste Beispiel zeigt den Einsatz der anderen behandelten Technik: eine GeometryGroup mit einer PathGeometry. Dieses Beispiel zeigt einige der beitragenden Geometrietypen, die als Teil von PathGeometry verwendet werden können: PathFigure und die verschiedenen Elemente, die in PathFigure.Segments ein Segment darstellen können.

<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
    <Path.Data>
        <GeometryGroup>
            <RectangleGeometry Rect="50,5 100,10" />
            <RectangleGeometry Rect="5,5 95,180" />
            <EllipseGeometry Center="100, 100" RadiusX="20" RadiusY="30"/>
            <RectangleGeometry Rect="50,175 100,10" />
            <PathGeometry>
                <PathGeometry.Figures>
                    <PathFigureCollection>
                        <PathFigure IsClosed="true" StartPoint="50,50">
                            <PathFigure.Segments>
                                <PathSegmentCollection>
                                    <BezierSegment Point1="75,300" Point2="125,100" Point3="150,50"/>
                                    <BezierSegment Point1="125,300" Point2="75,100"  Point3="50,50"/>
                                </PathSegmentCollection>
                            </PathFigure.Segments>
                        </PathFigure>
                    </PathFigureCollection>
                </PathGeometry.Figures>
            </PathGeometry>
        </GeometryGroup>
    </Path.Data>
</Path>
var path1 = new Microsoft.UI.Xaml.Shapes.Path();
path1.Fill = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 204, 204, 255));
path1.Stroke = new SolidColorBrush(Colors.Black);
path1.StrokeThickness = 1;

var geometryGroup1 = new GeometryGroup();
var rectangleGeometry1 = new RectangleGeometry();
rectangleGeometry1.Rect = new Rect(50, 5, 100, 10);
var rectangleGeometry2 = new RectangleGeometry();
rectangleGeometry2.Rect = new Rect(5, 5, 95, 180);
geometryGroup1.Children.Add(rectangleGeometry1);
geometryGroup1.Children.Add(rectangleGeometry2);

var ellipseGeometry1 = new EllipseGeometry();
ellipseGeometry1.Center = new Point(100, 100);
ellipseGeometry1.RadiusX = 20;
ellipseGeometry1.RadiusY = 30;
geometryGroup1.Children.Add(ellipseGeometry1);

var pathGeometry1 = new PathGeometry();
var pathFigureCollection1 = new PathFigureCollection();
var pathFigure1 = new PathFigure();
pathFigure1.IsClosed = true;
pathFigure1.StartPoint = new Windows.Foundation.Point(50, 50);
pathFigureCollection1.Add(pathFigure1);
pathGeometry1.Figures = pathFigureCollection1;

var pathSegmentCollection1 = new PathSegmentCollection();
var pathSegment1 = new BezierSegment();
pathSegment1.Point1 = new Point(75, 300);
pathSegment1.Point2 = new Point(125, 100);
pathSegment1.Point3 = new Point(150, 50);
pathSegmentCollection1.Add(pathSegment1);

var pathSegment2 = new BezierSegment();
pathSegment2.Point1 = new Point(125, 300);
pathSegment2.Point2 = new Point(75, 100);
pathSegment2.Point3 = new Point(50, 50);
pathSegmentCollection1.Add(pathSegment2);
pathFigure1.Segments = pathSegmentCollection1;

geometryGroup1.Children.Add(pathGeometry1);
path1.Data = geometryGroup1;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot">
layoutRoot.Children.Add(path1);

Hier sehen Sie den gerenderten Pfad.

Screenshot of a complex rendered path.

Die Verwendung von PathGeometry kann besser lesbar sein als das Auffüllen einer Path.Data-Zeichenkette. Auf der anderen Seite verwendet Path.Data eine Syntax, die mit Bildpfaddefinitionen für skalierbare Vektorgrafiken (SVG) kompatibel ist. Es kann daher sinnvoll sein, Grafiken von SVG zu portieren oder als Ausgabe von einem Tool wie Blend.