Zum Inhalt
Kuonen.UI

Formen

Rectangle, Ellipse, Line, Polyline, Polygon und Path zeichnen Fläche und Kontur (Strichmuster, Ecken, Enden). Stretch passt die Form dem Platz an, ohne die Kontur zu strecken. Getroffen wird die Fläche nur mit Fill, die Kontur nur mit Stroke: die Linie unten reagiert auf der Strichbreite, nicht im Umriss.

Vorführung: FormenVorführung: Formen

KXAML

FormenSeite.kxaml
<StackPanel xmlns="https://ui.kuonen.org/kxaml"
            xmlns:x="https://ui.kuonen.org/kxaml/x"
            x:Class="Kuonen.UI.Galerie.Seiten.FormenVorfuehrung"
            Spacing="16">
  <!-- Die sechs Formen, je mit Namen darunter. Punktlisten und Strichmuster stehen als Text ("2 1": zweimal Strichbreite
       Strich, einmal Lücke). Die Pfadsprache beschreibt das Herz. -->
  <WrapPanel Orientation="Horizontal">
    <StackPanel Spacing="4" Margin="0,0,16,16">
      <Rectangle Width="100" Height="60" RadiusX="8" RadiusY="8" Fill="#CCE4F7" Stroke="#0078D4" StrokeThickness="3" Margin="6" />
      <TextBlock Text="Rectangle" />
    </StackPanel>
    <StackPanel Spacing="4" Margin="0,0,16,16">
      <Ellipse Width="100" Height="60" Fill="#CCE4F7" Stroke="#0078D4" StrokeThickness="3" StrokeDashArray="2 1" Margin="6" />
      <TextBlock Text="Ellipse" />
    </StackPanel>
    <StackPanel Spacing="4" Margin="0,0,16,16">
      <Line X2="100" Y2="60" Stroke="#0078D4" StrokeThickness="4" StrokeLineCap="Round" StrokeDashArray="0 2" Margin="6" />
      <TextBlock Text="Line" />
    </StackPanel>
    <StackPanel Spacing="4" Margin="0,0,16,16">
      <Polyline Points="0,60 30,0 60,45 100,10" Stroke="#0078D4" StrokeThickness="3" StrokeLineJoin="Round" Margin="6" />
      <TextBlock Text="Polyline" />
    </StackPanel>
    <StackPanel Spacing="4" Margin="0,0,16,16">
      <Polygon Points="0,60 50,0 100,60" Fill="#CCE4F7" Stroke="#0078D4" StrokeThickness="3" StrokeDashArray="2 1" Margin="6" />
      <TextBlock Text="Polygon" />
    </StackPanel>
    <StackPanel Spacing="4" Margin="0,0,16,16">
      <Path Data="M 50,88 C 20,64 4,44 4,26 C 4,10 16,2 28,2 C 38,2 46,8 50,16 C 54,8 62,2 72,2 C 84,2 96,10 96,26 C 96,44 80,64 50,88 Z"
            Width="100" Height="80" Stretch="Uniform" Fill="#D13438" Stroke="#7A1C1F" StrokeThickness="3" Margin="6" />
      <TextBlock Text="Path" />
    </StackPanel>
  </WrapPanel>
  <!-- Werkbank: die Schalter wirken auf die dicken Formen rechts. IsChecked vor Checked: der Startzustand meldet nichts. -->
  <StackPanel Orientation="Horizontal" Spacing="32">
    <StackPanel Spacing="12">
      <StackPanel Orientation="Horizontal" Spacing="32">
        <StackPanel Spacing="4">
          <TextBlock Text="Stretch (Herz)" FontWeight="SemiBold" />
          <RadioButton Content="None" GroupName="Stretch" IsChecked="False" Checked="Stretch_Checked" />
          <RadioButton Content="Fill" GroupName="Stretch" IsChecked="False" Checked="Stretch_Checked" />
          <RadioButton Content="Uniform" GroupName="Stretch" IsChecked="True" Checked="Stretch_Checked" />
          <RadioButton Content="UniformToFill" GroupName="Stretch" IsChecked="False" Checked="Stretch_Checked" />
        </StackPanel>
        <StackPanel Spacing="4">
          <TextBlock Text="StrokeLineJoin (Zickzack)" FontWeight="SemiBold" />
          <RadioButton Content="Miter" GroupName="Ecke" IsChecked="True" Checked="Ecke_Checked" />
          <RadioButton Content="Round" GroupName="Ecke" IsChecked="False" Checked="Ecke_Checked" />
          <RadioButton Content="Bevel" GroupName="Ecke" IsChecked="False" Checked="Ecke_Checked" />
        </StackPanel>
        <StackPanel Spacing="4">
          <TextBlock Text="StrokeLineCap (Zickzack, Balken)" FontWeight="SemiBold" />
          <RadioButton Content="Flat" GroupName="Ende" IsChecked="True" Checked="Ende_Checked" />
          <RadioButton Content="Round" GroupName="Ende" IsChecked="False" Checked="Ende_Checked" />
          <RadioButton Content="Square" GroupName="Ende" IsChecked="False" Checked="Ende_Checked" />
        </StackPanel>
      </StackPanel>
      <CheckBox Content="Strichmuster" Checked="Muster_Checked" Unchecked="Muster_Unchecked" />
    </StackPanel>
    <StackPanel Spacing="8">
      <Border Width="160" Height="100" Background="#FFFFFF" HorizontalAlignment="Left">
        <Path x:Name="Herz"
              Data="M 50,88 C 20,64 4,44 4,26 C 4,10 16,2 28,2 C 38,2 46,8 50,16 C 54,8 62,2 72,2 C 84,2 96,10 96,26 C 96,44 80,64 50,88 Z"
              Stretch="Uniform" Fill="#D13438" Stroke="#7A1C1F" StrokeThickness="3" />
      </Border>
      <Polyline x:Name="Zickzack" Points="0,40 30,0 60,40 90,0 120,40" Stroke="#0078D4" StrokeThickness="14" Margin="14" />
      <Line x:Name="Balken" X1="0" Y1="7" X2="120" Y2="7" Stroke="#0078D4" StrokeThickness="14" Margin="14,0,14,0" />
    </StackPanel>
  </StackPanel>
  <!-- Klickfläche: ein Klick auf die freie Fläche fügt der Polylinie einen Punkt hinzu (höchstens 64); die Kontur trifft,
       die Fläche zwischen den Abschnitten nicht, weil die Polylinie keine Füllung hat. -->
  <StackPanel Spacing="8">
    <Canvas x:Name="Flaeche" Width="320" Height="120" Background="#FFFFFF" HorizontalAlignment="Left" PointerPressed="Flaeche_PointerPressed">
      <Polyline x:Name="Linie" Points="20,100 90,20 160,90 230,30" Stroke="#0078D4" StrokeThickness="6" StrokeLineJoin="Round"
                PointerEntered="Linie_PointerEntered" PointerExited="Linie_PointerExited" />
    </Canvas>
    <StackPanel Orientation="Horizontal" Spacing="16">
      <Button Content="Zurücksetzen" Click="Zuruecksetzen_Click" />
      <TextBlock x:Name="Meldung" Text="Zeiger über die Linie bewegen, auf die Fläche klicken." />
    </StackPanel>
  </StackPanel>
</StackPanel>
FormenSeite.kxaml.cs
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Eingabe;
using Kuonen.UI.Ereignisse;
using Kuonen.UI.Media;

namespace Kuonen.UI.Galerie.Seiten;

public sealed partial class FormenSeite
{
    protected override Func<UIElement>? KxamlFabrik => () => new FormenVorfuehrung();
}

/// <summary>KXAML-Fassung der Formen-Seite: der Baum steht in FormenSeite.kxaml, hier nur das Verhalten.</summary>
public sealed partial class FormenVorfuehrung
{
    private const int MaxPunkte = 64;
    private const string Hinweis = "Zeiger über die Linie bewegen, auf die Fläche klicken.";

    private static readonly Point[] Anfang = [new Point(20, 100), new Point(90, 20), new Point(160, 90), new Point(230, 30)];

    private readonly DoubleCollection _muster = [1, 1];

    public FormenVorfuehrung() => InitializeComponent();

    private void Stretch_Checked(object sender, RoutedEventArgs e) =>
        Herz.Stretch = (string)((RadioButton)sender).Content! switch
        {
            "None" => Stretch.None,
            "Fill" => Stretch.Fill,
            "UniformToFill" => Stretch.UniformToFill,
            _ => Stretch.Uniform,
        };

    private void Ecke_Checked(object sender, RoutedEventArgs e) =>
        Zickzack.StrokeLineJoin = (string)((RadioButton)sender).Content! switch
        {
            "Round" => PenLineJoin.Round,
            "Bevel" => PenLineJoin.Bevel,
            _ => PenLineJoin.Miter,
        };

    private void Ende_Checked(object sender, RoutedEventArgs e)
    {
        var ende = (string)((RadioButton)sender).Content! switch
        {
            "Round" => PenLineCap.Round,
            "Square" => PenLineCap.Square,
            _ => PenLineCap.Flat,
        };
        Zickzack.StrokeLineCap = ende;
        Balken.StrokeLineCap = ende;
    }

    private void Muster_Checked(object sender, RoutedEventArgs e)
    {
        Zickzack.StrokeDashArray = _muster;
        Balken.StrokeDashArray = _muster;
    }

    private void Muster_Unchecked(object sender, RoutedEventArgs e)
    {
        Zickzack.StrokeDashArray = null;
        Balken.StrokeDashArray = null;
    }

    private void Linie_PointerEntered(object sender, PointerEventArgs e) => Meldung.Text = "Kontur getroffen.";

    private void Linie_PointerExited(object sender, PointerEventArgs e) => Meldung.Text = "Kontur nicht getroffen.";

    private void Flaeche_PointerPressed(object sender, PointerEventArgs e)
    {
        if (ReferenceEquals(e.Source, Linie)) return; // ein Druck auf die Kontur fügt nichts hinzu
        if (Linie.Points!.Count >= MaxPunkte)
        {
            Meldung.Text = $"Höchstens {MaxPunkte} Punkte.";
            return;
        }
        Linie.Points.Add(e.PositionIn(Flaeche));
    }

    private void Zuruecksetzen_Click(object sender, RoutedEventArgs e)
    {
        Linie.Points!.Clear();
        foreach (var punkt in Anfang) Linie.Points.Add(punkt);
        Meldung.Text = Hinweis;
    }
}

C#

FormenSeite.cs
using Kuonen.UI;
using Kuonen.UI.Controls;
using Kuonen.UI.Layout;
using Kuonen.UI.Media;
using Kuonen.UI.Shapes;
using Path = Kuonen.UI.Shapes.Path;

namespace Kuonen.UI.Galerie.Seiten;

/// <summary>Galerie-Seite: die Formen <see cref="Rectangle"/>, <see cref="Ellipse"/>, <see cref="Line"/>, <see cref="Polyline"/>, <see cref="Polygon"/> und <see cref="Path"/>.</summary>
public sealed partial class FormenSeite : GalerieSeite
{
    public override string Titel => "Formen";

    public override string Beschreibung =>
        "Rectangle, Ellipse, Line, Polyline, Polygon und Path zeichnen Fläche und Kontur (Strichmuster, Ecken, Enden). "
        + "Stretch passt die Form dem Platz an, ohne die Kontur zu strecken. Getroffen wird die Fläche nur mit Fill, "
        + "die Kontur nur mit Stroke: die Linie unten reagiert auf der Strichbreite, nicht im Umriss.";

    public override string Stichworte =>
        "Shape Rectangle Ellipse Line Polyline Polygon Path Form Rechteck Kreis Linie Pfad Kontur Muster Stretch ShapeDrawable VectorDrawable";

    public override string Datei => nameof(FormenSeite);

    public override UIElement Baue()
    {
        var blau = new SolidColorBrush(Color.FromRgb(0x00, 0x78, 0xD4));
        var hellblau = new SolidColorBrush(Color.FromRgb(0xCC, 0xE4, 0xF7));
        var rot = new SolidColorBrush(Color.FromRgb(0xD1, 0x34, 0x38));
        var dunkelrot = new SolidColorBrush(Color.FromRgb(0x7A, 0x1C, 0x1F));
        var weiss = new SolidColorBrush(Color.FromRgb(0xFF, 0xFF, 0xFF));

        // Die sechs Formen, je mit Namen darunter.
        var reihe = new WrapPanel { Orientation = Orientation.Horizontal };
        void Zelle(string name, Shape form)
        {
            form.Margin = new Thickness(6);
            var zelle = new StackPanel { Spacing = 4, Margin = new Thickness(0, 0, 16, 16) };
            zelle.Children.Add(form);
            zelle.Children.Add(new TextBlock { Text = name });
            reihe.Children.Add(zelle);
        }
        Zelle("Rectangle", new Rectangle { Width = 100, Height = 60, RadiusX = 8, RadiusY = 8, Fill = hellblau, Stroke = blau, StrokeThickness = 3 });
        Zelle("Ellipse", new Ellipse { Width = 100, Height = 60, Fill = hellblau, Stroke = blau, StrokeThickness = 3, StrokeDashArray = [2, 1] });
        Zelle("Line", new Line { X2 = 100, Y2 = 60, Stroke = blau, StrokeThickness = 4, StrokeLineCap = PenLineCap.Round, StrokeDashArray = [0, 2] });
        Zelle("Polyline", new Polyline
        {
            Points = [new Point(0, 60), new Point(30, 0), new Point(60, 45), new Point(100, 10)],
            Stroke = blau, StrokeThickness = 3, StrokeLineJoin = PenLineJoin.Round,
        });
        Zelle("Polygon", new Polygon
        {
            Points = [new Point(0, 60), new Point(50, 0), new Point(100, 60)],
            Fill = hellblau, Stroke = blau, StrokeThickness = 3, StrokeDashArray = [2, 1],
        });
        Zelle("Path", new Path
        {
            Data = PathGeometry.Parse("M 50,88 C 20,64 4,44 4,26 C 4,10 16,2 28,2 C 38,2 46,8 50,16 C 54,8 62,2 72,2 C 84,2 96,10 96,26 C 96,44 80,64 50,88 Z"),
            Width = 100, Height = 80, Stretch = Stretch.Uniform, Fill = rot, Stroke = dunkelrot, StrokeThickness = 3,
        });

        // Werkbank: die Schalter wirken auf die dicken Formen rechts.
        var herz = new Path
        {
            Data = PathGeometry.Parse("M 50,88 C 20,64 4,44 4,26 C 4,10 16,2 28,2 C 38,2 46,8 50,16 C 54,8 62,2 72,2 C 84,2 96,10 96,26 C 96,44 80,64 50,88 Z"),
            Stretch = Stretch.Uniform, Fill = rot, Stroke = dunkelrot, StrokeThickness = 3,
        };
        var rahmen = new Border { Width = 160, Height = 100, Background = weiss, HorizontalAlignment = HorizontalAlignment.Left, Child = herz };
        var zickzack = new Polyline
        {
            Points = [new Point(0, 40), new Point(30, 0), new Point(60, 40), new Point(90, 0), new Point(120, 40)],
            Stroke = blau, StrokeThickness = 14, Margin = new Thickness(14),
        };
        var balken = new Line { X1 = 0, Y1 = 7, X2 = 120, Y2 = 7, Stroke = blau, StrokeThickness = 14, Margin = new Thickness(14, 0, 14, 0) };
        var muster = new DoubleCollection { 1, 1 };

        StackPanel Gruppe(string titel)
        {
            var g = new StackPanel { Spacing = 4 };
            g.Children.Add(new TextBlock { Text = titel, FontWeight = 600 });
            return g;
        }
        var stretch = Gruppe("Stretch (Herz)");
        foreach (var (name, wert) in new[] { ("None", Stretch.None), ("Fill", Stretch.Fill), ("Uniform", Stretch.Uniform), ("UniformToFill", Stretch.UniformToFill) })
        {
            var feld = new RadioButton { Content = name, GroupName = "Stretch", IsChecked = wert == Stretch.Uniform };
            feld.Checked += (_, _) => herz.Stretch = wert;
            stretch.Children.Add(feld);
        }
        var ecke = Gruppe("StrokeLineJoin (Zickzack)");
        foreach (var (name, wert) in new[] { ("Miter", PenLineJoin.Miter), ("Round", PenLineJoin.Round), ("Bevel", PenLineJoin.Bevel) })
        {
            var feld = new RadioButton { Content = name, GroupName = "Ecke", IsChecked = wert == PenLineJoin.Miter };
            feld.Checked += (_, _) => zickzack.StrokeLineJoin = wert;
            ecke.Children.Add(feld);
        }
        var ende = Gruppe("StrokeLineCap (Zickzack, Balken)");
        foreach (var (name, wert) in new[] { ("Flat", PenLineCap.Flat), ("Round", PenLineCap.Round), ("Square", PenLineCap.Square) })
        {
            var feld = new RadioButton { Content = name, GroupName = "Ende", IsChecked = wert == PenLineCap.Flat };
            feld.Checked += (_, _) =>
            {
                zickzack.StrokeLineCap = wert;
                balken.StrokeLineCap = wert;
            };
            ende.Children.Add(feld);
        }
        var musterSchalter = new CheckBox { Content = "Strichmuster" };
        musterSchalter.Checked += (_, _) =>
        {
            zickzack.StrokeDashArray = muster;
            balken.StrokeDashArray = muster;
        };
        musterSchalter.Unchecked += (_, _) =>
        {
            zickzack.StrokeDashArray = null;
            balken.StrokeDashArray = null;
        };

        var schalter = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 32 };
        schalter.Children.Add(stretch);
        schalter.Children.Add(ecke);
        schalter.Children.Add(ende);
        var vorschau = new StackPanel { Spacing = 8 };
        vorschau.Children.Add(rahmen);
        vorschau.Children.Add(zickzack);
        vorschau.Children.Add(balken);
        var werkbank = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 32 };
        var links = new StackPanel { Spacing = 12 };
        links.Children.Add(schalter);
        links.Children.Add(musterSchalter);
        werkbank.Children.Add(links);
        werkbank.Children.Add(vorschau);

        // Klickfläche: ein Klick auf die freie Fläche fügt der Polylinie einen Punkt hinzu (höchstens 64); die Kontur
        // trifft, die Fläche zwischen den Abschnitten nicht, weil die Polylinie keine Füllung hat.
        const int MaxPunkte = 64;
        Point[] anfang = [new Point(20, 100), new Point(90, 20), new Point(160, 90), new Point(230, 30)];
        var linie = new Polyline { Points = [.. anfang], Stroke = blau, StrokeThickness = 6, StrokeLineJoin = PenLineJoin.Round };
        var flaeche = new Canvas { Width = 320, Height = 120, Background = weiss, HorizontalAlignment = HorizontalAlignment.Left };
        flaeche.Children.Add(linie);
        var meldung = new TextBlock { Text = "Zeiger über die Linie bewegen, auf die Fläche klicken." };
        linie.PointerEntered += (_, _) => meldung.Text = "Kontur getroffen.";
        linie.PointerExited += (_, _) => meldung.Text = "Kontur nicht getroffen.";
        flaeche.PointerPressed += (_, e) =>
        {
            if (ReferenceEquals(e.Source, linie)) return; // ein Druck auf die Kontur fügt nichts hinzu
            if (linie.Points!.Count >= MaxPunkte)
            {
                meldung.Text = $"Höchstens {MaxPunkte} Punkte.";
                return;
            }
            linie.Points.Add(e.PositionIn(flaeche));
        };
        var zuruecksetzen = new Button { Content = "Zurücksetzen" };
        zuruecksetzen.Click += (_, _) =>
        {
            linie.Points!.Clear();
            foreach (var punkt in anfang) linie.Points.Add(punkt);
            meldung.Text = "Zeiger über die Linie bewegen, auf die Fläche klicken.";
        };
        var klick = new StackPanel { Spacing = 8 };
        klick.Children.Add(flaeche);
        var klickZeile = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 16 };
        klickZeile.Children.Add(zuruecksetzen);
        klickZeile.Children.Add(meldung);
        klick.Children.Add(klickZeile);

        var seite = new StackPanel { Spacing = 16 };
        seite.Children.Add(reihe);
        seite.Children.Add(werkbank);
        seite.Children.Add(klick);
        return seite;
    }
}