Hallo!
Kennt Jemand den Regex zur Aufteilung der Pfadmarkupsyntax eines Geometry-Objektes?
<Path Data="M 10,100 C 10,300 300,-200 300,100" />
Intern wird die PathFigureCollection ja auch aus der gesplitteten PfadMarkupSyntax erstellt.
Ein festes splitten ist jedoch nicht möglich. Siehe besonders: Anmerkungen zu Leerzeichen .
Oder kann man irgendwie direkt auf die PfadFigureCollection zugreifen?
In dem von dir geposteten Link steht aber auch, daß es zwei verschiedene Möglichkeiten zur Erzeugung von Pfaden gibt, per StreamGeometry oder PathFigureCollection.
Und bei letzterem solltest du dann ja auf Figures zugreifen können.
Regex solltest Du nicht verwenden, geht zwar, wäre aber vermutlich sehr komplex.
Am besten Du iterierst Zeichen für Zeichen über den Pfad-String und interpretierst das, das Format ist an sich ja ziemlich einfach aufgebaut.
Du kannst ja einfach auf die Command-Buchstaben prüfen und weißt dann jeweils, was darauf folgen muss, z.B. "L" gefolgt von einer Koordinate bestehend aus zwei Zahlen, ggf. durch Komma oder Leerzeichen getrennt.
NuGet Packages im Code auslesen
lock Alternative für async/await
Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.
Hallo Th69 !
Ziel meiner Bemühungen ist es "Längen-Angaben" des Verlaufes eines Grafik-Objektes zu errechnen.
Ich habe als Ausgangspunkt einen String, der eine Grafik in Form einer PfadMarkUp-Syntax enthält.
Eine PathGeometry kann man durch Parsen dieses Strings erstellen. Ich habe aber keine Möglichkeit gefunden, von diesem Geometry-Objekt aus auf die PathFigureCollection zuzugreifen. (Ich habe die PathFigureCollection also nicht.)
Deshalb habe ich den Weg, die einzelnen Segmente "per Hand", aus der MarkUp-Syntax, zu erstellen, gewählt.
Vielen Dank für deinen Hinweis!
Hallo Palladin007!
Die Erstellung der Einzel-Objekte der Grafik, letzendlich die PathFigureCollection, habe ich entsprechend deinem Hinweis umgesetzt.
Es gestaltet sich etwas aufwendiger als vermutet, aber prinzipiell funktioniert es.
Die syntaktische Umsetzung eines MarkUp-Befehls z.B. muss definiert werden:
/// <summary>
/// Erstellt eine Liste der fest definierten MarkUp-Befehle.
/// </summary>
/// <returns>Liste der fest definierten MarkUp-Befehle.</returns>
private static List<MarkUpBefehlsStruktur> BuildBefehlsListe()
{
List<MarkUpBefehlsStruktur> listeBefehle = new(); // Liste der MarkUp-Befehle initalisieren
#region Die fest definierten Befehle der Liste der MarkUp-Befehle hinzufügen.
listeBefehle.Add(new("Move", "m", 1, new() { new(1, typeof(Point), "StartPoint", "Der Ausgangspunkt einer neuen Figur.") }, null));
listeBefehle.Add(new("Line", "l", 1, new() { new(1, typeof(Point), "EndPoint", "Der Endpunkt der Linie.") }, typeof(LineSegment)));
listeBefehle.Add(new("Horizontal Line", "h", 1, new() { new(1, typeof(double), "x", "Die x-Koordinate des Endpunkts der Linie.") }, typeof(LineSegment)));
listeBefehle.Add(new("Vertikal Line", "v", 1, new() { new(1, typeof(double), "y", "Die y-Koordinate des Endpunkts der Linie.") }, typeof(LineSegment)));
listeBefehle.Add(new("Kubische Bézierkurve", "c", 3, new()
{
new(1, typeof(Point), "ControlPoint 1", "Der erste Kontrollpunkt der Kurve, der die beginnende Tangente der Kurve bestimmt."),
new(2, typeof(Point), "ControlPoint 2", "Der zweite Kontrollpunkt der Kurve, der die endende Tangente der Kurve bestimmt."),
new(3, typeof(Point), "EndPoint", "Der Punkt, bis zu dem die Kurve gezeichnet wird.")
}, typeof(BezierSegment)));
listeBefehle.Add(new("Quadratische Bézierkurve", "q", 2, new()
{
new(1, typeof(Point), "ControlPoint", "Der Kontrollpunkt der Kurve, der die beginnende und endende Tangente der Kurve bestimmt"),
new(2, typeof(Point), "EndPoint", "Der Punkt, bis zu dem die Kurve gezeichnet wird.")
}, typeof(QuadraticBezierSegment)));
listeBefehle.Add(new("Glatte Kubische Bézierkurve", "s", 2, new()
{
new(1, typeof(Point), "ControlPoint", "Der Kontrollpunkt der Kurve, der die endende Tangente der Kurve bestimmt"),
new(2, typeof(Point), "EndPoint", "Der Punkt, bis zu dem die Kurve gezeichnet wird.")
}, typeof(BezierSegment)));
listeBefehle.Add(new("Glatte Quadratische Bézierkurve", "t", 1, new() { new(1, typeof(Point), "EndPoint", "Der Punkt, bis zu dem die Kurve gezeichnet wird.") }
, typeof(BezierSegment)));
listeBefehle.Add(new("Elliptischer Bogen", "a", 5, new()
{
new(1, typeof(Size), "Size", "Der X- und Y-Radius des Bogens"),
new(2, typeof(double), "RotationAngle", "Die Drehung der Ellipse. [°]"),
new(3, typeof(int), "IsLargeArcFlag", "Ist auf 1 festgelegt, wenn der Winkel des Bogens 180 Grad oder größer sein soll; andernfalls wird 0 festgelegt"),
new(4, typeof(int), "SweepDirectionFlag", "Ist auf 1 festgelegt, wenn der Bogen in einer Richtung mit positivem Winkel gezeichnet wird; andernfalls wird 0 festgelegt"),
new(5, typeof(Point), "EndPoint", "Der Punkt, bis zu dem die Kurve gezeichnet wird.")
}, typeof(ArcSegment)));
listeBefehle.Add(new("Close", "z", 2, new()
{
new(1, typeof(double), "x", "Die x-Koordinate eines Punktes, an dem (0,0) die linke obere Ecke ist."),
new(2, typeof(double), "y", "Die y-Koordinate eines Punktes, an dem (0,0) die linke obere Ecke ist.")
}, null));
#endregion
return listeBefehle;
}
Die dazugehörige Struktur:
/// <summary>
/// Klasse zur Beschreibung der Struktur eines (Standard) MarkUp Befehl's.
/// </summary>
public class MarkUpBefehlsStruktur : MVVM_Base
{
/// <summary>
/// Name des Befehls
/// </summary>
public string Befehl { get; set; }
/// <summary>
/// Kennung des Befehls
/// </summary>
public string Kennung { get; set; }
/// <summary>
/// Anzahl der Parameter des Befehls
/// </summary>
public int AnzahlParameter { get; set; }
private List<ParameterType> _ParameterTypen;
/// <summary>
/// Liste der Parameter-Type(n)
/// </summary>
public List<ParameterType> ParameterTypen { get => _ParameterTypen; set { _ParameterTypen = value; OnChanged(nameof(ParameterTypen)); } }
/// <summary>
/// Der Type des Grafik-Segment-Objektes das zu dem Befehl adäquat ist.
/// </summary>
public Type GrafikSegmentType { get; set; }
#region Konstruktor
public MarkUpBefehlsStruktur(string befehl, string kennung, int anzahlParameter, List<ParameterType> parameter, Type grafikSegmentType)
{
Befehl = befehl; Kennung = kennung; AnzahlParameter = anzahlParameter; ParameterTypen = parameter;
GrafikSegmentType = grafikSegmentType;
}
#endregion
}
Oder die Zuordnung der MarkUp-Parameter zu den MarkUp-Befehlen usw. ...
/// <summary>
/// Erstellt die Grafiken aus den (Befehls) Parameter-Wert(en).
/// </summary>
private void BuildGrafikPath()
{
switch (Befehl.Kennung)
{
case "m": // Move
{
EndPunkt = new Point(((Point)ParameterWerte[0].Wert).X, ((Point)ParameterWerte[0].Wert).Y); // Den Endpunkt aktualisieren
} break;
...
case "c": // Kubische Bézierkurve
{
#region Relativ oder Absolut Positionierung
Point Para1 = (Point)ParameterWerte[0].Wert; // Parameter 1 als Point
Point Para2 = (Point)ParameterWerte[1].Wert; // Parameter 2 als Point
Point Para3 = (Point)ParameterWerte[2].Wert; // Parameter 3 als Point
if (PositionsBezug == EnumPositionsBezug.Offset) // Bei einem relativem Bezug müssen die absoluten Positionen bestimmt werden
{
Para1 = new Point(Para1.X + StartPunkt.X, Para1.Y + StartPunkt.Y);
Para2 = new Point(Para2.X + StartPunkt.X, Para2.Y + StartPunkt.Y);
Para3 = new Point(Para3.X + StartPunkt.X, Para3.Y + StartPunkt.Y);
}
BezierSegment segment = new(Para1, Para2, Para3, true); // BezierSegment mit den Parameter-Wert(en) instanzieren
GrafikFigure = new(); // PathFigure-Objekt instanzieren
GrafikFigure.StartPoint = PositionsBezug == EnumPositionsBezug.Offset ? StartPunkt : new Point(0, 0);
GrafikFigure.Segments.Add(segment); // Dem PathFigure-Objekt das Bezier-Segment hinzufügen
GrafikGeometry = new(); // PathGeometry-Objekt instanzieren
GrafikGeometry.FillRule = FillRule.Nonzero;
GrafikGeometry.Figures.Add(GrafikFigure); // Dem PathGeometry-Objekt das PathFigure-Objekt zuordnen
#endregion
EndPunkt = new Point(Para3.X, Para3.Y); // (Neuen) End-Punkt aktualisieren
} break;
default: break; // Bei unbekanntem Befehl nichts machen.
}
}
Ich habe nun zu jedem MarkUp-Befehl ein GrafigFigure-Objekt (siehe Anhang).
Daran schließt sich aber wieder die nächste Frage an ... gibt es für die Bezier-Kurven (besonders für die kubische Bezierkurve, da sie vorwiegend von den Vektor-Grafikprogrammen, Adobe Illustrator z.B. ) genutzt werden, fertige Formeln für die Berechnung eines Punktes auf der Bezierkurve zum Zeitpunkt 0 <= t <= 1? Keine mathematische Beschreibung, sondern einen Algorithmus, den man ausführen kann! (Newtonische Näherungsverfahren)
Hast du jetzt Geometry.Parse nachprogrammiert?
Bei Verwendung der 2. Path.Data
- Syntax (mittels PathGeometry
) kann man doch einfach auf Path.Data zugreifen und erhält die Geometry
bzw. mittels passendes Casting auf PathGeometry kommt man an die PathGeometry.Figures.
Hallo Th69!
Ja und das ist mir auch bewußt, ich programmiere die Geometry.Parse()-Methode nach, um an die/eine PathFigureCollection heranzukommen.
Leider verstehe ich deinen Ansatz:
Bei Verwendung der 2. Path.Data - Syntax ...
nicht!
Ein Geometry-Objekt habe ich durch die Geometry.Parse()-Methode.
Ein einfaches casten (PathGeometry)MyGeometry wirft eine Exception:
Unable to cast object of type 'System.Windows.Media.StreamGeometry' to type 'System.Windows.Media.PathGeometry'
Da dieses Problem leicht darzustellen ist, habe ich mal einen 3zeiler entworfen.
Vielleicht übersehe ich ja etwas ...
Als Ausgangspunkt habe ich eine PfadMarkUp-Syntax, als Ziel möchte ich die PathFigureCollection die aus dem PfadMarkUp resultiert.
Test()
{
Geometry MyGeometry = Geometry.Parse("M1.25,0c67.654,0,122.5,54.845,122.5,122.5c0,67.654-54.846,122.5-122.5,122.5");
PathGeometry MyPathGeometry = (PathGeometry)MyGeometry;
PathFigureCollection MyPathFigures = MyPathGeometry.Figures;
}
Vielen Dank für deine Hinweise!
Es gibt eine PathGeometry.CreateFromGeometry(geometry)
Methode - tut die, was Du brauchst?
NuGet Packages im Code auslesen
lock Alternative für async/await
Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.
Hallo Palladin007!
Ich bin doch immer wieder erstaunt, wie du nur diese statischen Methoden findest! 😃)
Die Methode sollte ja genau das machen was ich benötige, aber ... das macht sie im Detail nicht.
Geometry MyGeometry = Geometry.Parse(SchalterGrafik_Kängeru);
PathGeometry MyPathGeometry = PathGeometry.CreateFromGeometry(MyGeometry);
PathFigureCollection MyPathFigures = MyPathGeometry.Figures;
int AnzFigures = MyPathFigures.Count;
Die Geometry wird in eine PathGeometry umgewandelt, jedoch werden die einzelnen Figures-Objekte zusammengefaßt. Die PathFigureCollection enthält nur 2 Figures-Objekte. Beim Kängeru müssten es aber 99 sein. Ich benötige auch die einzelnen Figures-Objekte um es mathematisch nachbilden zu können.
Trotzdem vielen Dank für deinen Hinweis!
Sorry, hatte nicht näher bei Geometry.Parse
geschaut, was dort genau erzeugt wird (anscheinend also nur eine StreamGeometry
und keine PathGeomtry
). Aber auch dafür gibt es eine statische Methode: PathFigureCollection.Parse (wie sollte sonst der XAML-Parser die Daten aus dem PathMarkup-String erzeugen?)
Und bzgl.
Bei Verwendung der 2. Path.Data - Syntax ...
meinte ich die XAML-Syntax (wie in deinem ersten Beitrag, nur eben die andere Syntax mittels explizitem PathGeomtry
).
Hallo Th69!
Leider erzeugen die statischen Methoden immer nur zusammengefasste PathFigureCollection!
Bei meinem Beispiel, Känguru, werden 2 statt 95 Figure Objekte (Move und Close fallen raus) in der PathFigureCollection erzeugt !
Wonach sie zusammengefasst werden ist mir noch unklar, vielleicht nach der Länge.
Mein Geometry.Parse Clone arbeitet stabil und macht genau das was ich benötige, er erzeugt pro PfadMarkUp-Befehl ein Figure-Objekt (und speichert alle relevanten Daten).
Ich habe jetzt eigentlich nur noch 2 Probleme.
1. Eine Anomalie (siehe oberer Teil des Anhanges)
Tritt immer bei Line-Segmenten auf die sehr klein sind.
Zur Erklärung. Im Beispiel sollte eine Linie gezeichnet werden, die horizontal von X: 91,025 nach 90,879 (Delta X: 0,146) und vertikal von Y: 1,505 nach 1,53 (Delta: Y 0,025) geht. Eigentlich eine (sehr) kleine Linie die von oben rechts nach unten links geht. Wie man sieht, wird aber eine Linie zum 0 Punkt dargestellt. Ich kann solche Linien ausblenden. Mich würde nur interessieren ob jemand weiss, ob dies an der Darstellung (Grafikkarte, Auflösung des Bildschirms...) oder an der Verarbeitung in WPF (SnapToDevicePixels -Eigenschaft des UI-Elements, Rundungsproblem ect.) liegen könnte?
Im unteren Teil des Anhanges sieht man eine korrekte Markierung des Grafik-Kursor's, der pro MarkUp-Grafik das Geometry-Objekt rot umrandet.
2. Bei der mathematischen Approximation der Bezier-Kurven habe ich ein Definitionsproblem. Was versteht Microsoft unter einer: Glatte Kubische Bézierkurve oder einer Glatte Quadratische Bézierkurve??? Bei all meinen MarkUp-Beschreibungen ist
s/S oder t/T nie aufgetreten ich hätte nur den Unterschied zur normalen Kubischen oder Quadratischen Bézierkurve gern gewusst.
Trotzdem vielen Dank! für deine Unterstützung.
Ich könnte mir vorstellen, dass es das, was Du suchst, gar nicht gibt.
Vielleicht gibt es die Objektstruktur, die Du suchst, nur für die einfachere Nutzbarkeit, wenn er einen String parst, wird aber eine interne Darstellung verwendet.
Wenn öffentlich angebotene Methoden nicht das liefern, was Du suchst, suchst Du vielleicht das falsche.
Ich hab mir mal angeschaut, was der Debugger bei deinem Halbkreis-Pfad ausspuckt - im Anhang das Ergebnis.
Das ist nicht das gleiche, wie wenn man manuell aufbaut, aber es sieht aus, als könnte man damit trotzdem alles herausfinden, was man möchte - wenn auch nicht ganz so einfach.
NuGet Packages im Code auslesen
lock Alternative für async/await
Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.
Hallo!
Noch einen kleinen Nachtrag...
Ich habe jetzt herausgefunden, nach welchem Prinzip die PathFigure-Einträge bei den statischen Methoden zusammengefasst werden:
1 Figure-Objekt pro MarkUp-Bereich von Move Befehl1 ... Befehl(n) Close.
Figure1: Move1 ... Close1
Figure2: Move2 ... Close2
Hallo Palladin007!
Danke für deine Überlegungen!
Die Werte die du im Debugger siehst, sind genau die Werte der berechneten Grafik Parameter.
Bei dem Halbkreis funktioniert auch alles perfekt. Je nachdem auf welchem MarkUp-Befehl ich stehe, markiert der Grafik-Kursor genau den Teil der Grafik. (Ist aber auch nur ein! Bereich (Move ... 2xBefehle ... Close)
Die Anomalien beim Känguru sind beseitigt und basierten auf einem Fehler den ich bei der Erstellung (Positionierung) der einzelnen Figure-Objekte gemacht habe. Die Werte des Figure-Segment's sind korrekt, aber ich hatte bei Objekten die keinen Offset des Start-Punktes hatten den Startpunkt fest auf 0,0 gesetzt, anstatt den Start-Punkt des Figure-Objektes immer auf den Endpunkt des vorhergehenden Figure-Objektes zu setzen.
Korrigiertes Beispiel: Kubische Bezierkurve:
case "c": // Kubische Bézierkurve
{
Point Para1 = (Point)ParameterWerte[0].Wert; // Parameter 1 als Point
Point Para2 = (Point)ParameterWerte[1].Wert; // Parameter 2 als Point
Point Para3 = (Point)ParameterWerte[2].Wert; // Parameter 3 als Point
if (PositionsBezug == EnumPositionsBezug.Offset) // Bei einem relativem Bezug müssen die absoluten Positionen bestimmt werden
{
Para1 = new Point(Para1.X + StartPunkt.X, Para1.Y + StartPunkt.Y);
Para2 = new Point(Para2.X + StartPunkt.X, Para2.Y + StartPunkt.Y);
Para3 = new Point(Para3.X + StartPunkt.X, Para3.Y + StartPunkt.Y);
}
OffsetParameterWerte.Add(new(0, Para1)); // Offset-Parameter-Werte aktualisieren
OffsetParameterWerte.Add(new(1, Para2));
OffsetParameterWerte.Add(new(2, Para3));
BezierSegment segment = new(Para1, Para2, Para3, true); // BezierSegment mit den Parameter-Wert(en) instanzieren
GrafikFigure = new(); // PathFigure-Objekt instanzieren
// Hier ...
GrafikFigure.StartPoint = StartPunkt; // Position des Figure-Objektes an den Startpunkt des Befehles (=Endpunkt des vorhergehenden Figure-Objektes) setzen
// Ende
GrafikFigure.Segments.Add(segment); // Dem PathFigure-Objekt das Bezier-Segment hinzufügen
GrafikGeometry = new(); // PathGeometry-Objekt instanzieren
GrafikGeometry.FillRule = FillRule.Nonzero;
GrafikGeometry.Figures.Add(GrafikFigure); // Dem PathGeometry-Objekt das PathFigure-Objekt zuordnen
EndPunkt = new Point(Para3.X, Para3.Y); // (Neuen) End-Punkt aktualisieren
Jetzt kann ich problemlos durch die angezeigte und neu zusammengesetzte Grafik mit dem Grafik-Kursor navigieren. Im Moment jedoch nur in Schritten, die durch die Befehle der MarkUp-Syntax vorgegeben sind. Deshalb im letztem Schritt noch die Berechnung von Zwischenwerten der Position auf dem Verlauf der einzelnen Grafik-Objekte.
Jetzt noch eine Anmerkung. Du hast schon recht, wenn öffentlich angebotene Methoden nicht... es kann sein, dass ich die Zerlegung der MarkUp-Syntax pro Bereich (Move ... Befehle ... Close) machen muss, da intern auch so ein Figure-Objekt zugeordnet und gezeichnet wird. Ich muss also das Ergebnis (Figure-Objekt) der statischen Methode weiter in seine Einzel-Befehle aufteilen und am Ende wieder zusammensetzen. Das sieht man daran, dass der zweite Bereich (Mund Känguru) im Moment bei der zusammengefassten Grafik nicht dargestellt wird. 😉
Das muss ich mir in Ruhe noch einmal ansehen.
Vielen Dank!