XAML.cz Magazín moderních technologií založených na XAML

Geometrie ve WPF - PathGeometry

Napsáno pro WPF od Jirka Pénzeš [02.09.2010]

Z předešlého článku o geometrii ve WPF jsme se dozvěděli, jak nakreslit základní geometrické obrazce a jak je lze dále seskupovat. Pokud bychom ale chtěli vykreslit námi myšlený libovolný tvar či jen pouhou křivku – musíme hledat řešení jinde -> PathGeometry.

PathGeometry spadá mezi standardní třídy odvozené z Geometry. U této třídy je pro nás velmi důležitým pojmem vlastnost Figures, která typu PathFigureCollection. Do češtiny lze Figure přeložit jako obraz – skutečně tomu tak i je. Jedná se o soubor křivek a úseček, které jako celek tvoří zmiňovaný obraz. PathFigure je tedy pouze jeden segment – čili pro nás je to buď jedna úsečka, nebo křivka.

Výplň obrazců

Jednotlivé obrazce mohou a nemusí být uzavřené. Obecně platí, že otevřené obrazce nelze vyplnit – ve WPF tomu tak ale není, můžete tedy vyplnit i otevřenou cestu. Pakliže chcete mít uzavřenost obrazce zaručenou (tedy implicitní), použijte vlastnost IsClosed. Tato vlastnost je defaultně nastavena na hodnotu False. Pokud ale tento fakt změníte – obraz se bude automaticky uzavírat (jeli to samozřejmě nutné). Uzavření obrazce proběhne spojením počátečního a koncového bodu rovnou spojnicí (úsečkou). Další zajímavou vlastností, která ovlivňuje vyplňování obrazce je IsFilled. Ta nám zas říká, zda mají být vybarvené vnitřní plochy či nikoliv. Defaultně je nastavena na True.

Kreslení

Nyní víme, že výsledný obrazec (Figure) se skládá z dílčích segmentů. Tyto segmenty jsou odvozeny od třídy PathSegment. Tato třída je ale abstraktní – musíme se tedy smířit s použitím odvozených tříd. WPF jich nabízí hned sedm.

PathSegment

  • ArcSegment
  • BezierSegment
  • LineSegment
  • PolyBezierSegment
  • PolyLineSegment
  • PolyQuadraticBezierSegment
  • QuadraticBezierSegment

Každý obraz musí logicky začít v nějakém bodě, tento bod se nazývá startovací a určuje se na úrovni objektu PathFigure (vlastnost StartPoint). Je to počátek kreslení. Někdy se také můžete setkat s pojmem Move Point. Pokud tento bod opomenete – za počátek je považován levý horní roh [0, 0]. Jednotlivé části (segmenty) obrazu mohou a nemusí být spojeny.

<Path Fill="Orange" Stroke="Orange" StrokeThickness="2">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>
                <PathFigure IsClosed="False" IsFilled="False" StartPoint="10, 10">
                    <PathFigure.Segments>
                        <LineSegment Point="100,100" />
                        <LineSegment Point="200,100" />
                        <ArcSegment Point="200, 200"  Size="50,50" RotationAngle="45" 
                                    IsLargeArc="True" SweepDirection="Counterclockwise" />
                        <BezierSegment Point1="500, 100" Point2="100, 100" Point3="400,200"/>
                        <LineSegment Point="400, 100" />
                    </PathFigure.Segments>
                </PathFigure>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>
</Path>

PathGeometry1

Všimněte si, že na prvním řádku je nastavena barva výplně. Obrazec ale vyplněn není. Tento fakt je vynucen vlastností IsFilled. Zkusme tuto vlastnost zaměnit.

PathGeometry2

Nyní vidíme, že i když obrázek není uzavřen, WPF ho dokázal vyplnit. Ověřili jsme si tedy možnost vybarvení neuzavřených obrazců. Zkusme ještě vynutit uzavření.

PathGeometry3

Z obrázku je velmi pěkně vidět, jak je spojen počáteční bod s koncovým bodem. Tato spojnice, která uzavírá cestu v jeden celek, je tam dosazena automaticky (IsClosed="True"). Tímto způsobem WPF vybarvuje i neuzavřené obrazce – jednoduše si spojí první a počáteční bod, tak získá uzavřený obrazec, který již snadno vybarví.

Pokud se podíváme blíže na jednotlivé segmenty (například LineSegment, tedy úsečka), vidíme, že je uváděn jen jeden bod, přičemž úsečka vyžaduje vždy body dva. Toto tvrzení je obhájeno tak, že počáteční bod se přebírá od předchozího segmentu (jeho konce).

...
<PathFigure IsClosed="True" IsFilled="False" StartPoint="10, 10">
    <PathFigure.Segments>
        <LineSegment Point="100,100" />
        <LineSegment Point="200,100" />
...

V tomto jednoduchém případě vidíme dvě úsečky. Po rozebrání kódu vyčteme následující body:

S = Startovací bod [10, 10]
A = Bod prvního LineSegmentu [100, 100]
B = Bod druhého LineSegmentu [200, 100]

Máme dvě úsečky – první je označena body SA a druhá body AB. V kostce řečeno - první kreslení má první bod ve startovacím bodě. Další kreslení již přebírá svůj počáteční bod z koncového bodu předešlého segmentu.

Malý typ při kreslení například aproximačních křivek. Chcete-li mít viděny jednotlivé body, které se podílí na vykreslení cesty – můžete tak učinit pomocí elips (body), které seskupíte s objektem PathFigure pomocí GeometryGroup. Souřadnice středů získáte pomocí Bindingu dat. Mějme například Bezierovu kubiku, u které chceme mít vidět všechny řídící body, včetně těch, kterými samotná křivka nemusí procházek.

<Path Fill="Orange" Stroke="Orange" StrokeThickness="2">
    <Path.Data>
        <GeometryGroup>
            <PathGeometry>
                <PathFigure x:Name="Bezier" StartPoint="50 250" IsClosed="True" IsFilled="False">
                    <BezierSegment Point1="500 25" Point2="25 25" Point3="400 250" />
                </PathFigure>
            </PathGeometry>
            <!-- Body -->
            <EllipseGeometry Center="{Binding ElementName=Bezier, Path=StartPoint}" RadiusX="3" RadiusY="3" />
            <EllipseGeometry Center="{Binding ElementName=Bezier, Path=Segments[0].Point1}" RadiusX="3" RadiusY="3" />
            <EllipseGeometry Center="{Binding ElementName=Bezier, Path=Segments[0].Point2}" RadiusX="3" RadiusY="3" />
            <EllipseGeometry Center="{Binding ElementName=Bezier, Path=Segments[0].Point3}" RadiusX="3" RadiusY="3" />
        </GeometryGroup>
    </Path.Data>
</Path>

PathGeometry4

 

PathGeometry Markup Syntax

Pokud bychom chtěli kreslit často a složité útvary – asi bychom se při vypisování jednotlivých segmentů časem upsali a kód by se natahoval a natahoval. Microsoft ale na nás myslel a vytvořil tzv. PathGeometry Markup Syntax (v Čechách znám jako také Mini-jazyk pro geometrické cesty). Co se za těmito slovy skrývá? Jedná se o textový řetězec, který se skládá z posloupnosti znaků a číslic. Tyto znaky dávají celkem přesný popis jednotlivých dílčích segmentů cesty. Řetězec se nejčastěji zapisuje do vlastnosti Data na úrovni objektu Path. Můžeme jej ale zapsat do jakékoliv jiné vlastnosti, která je typu Geometry.

Podívejme se na následující příklad:

<Path Stroke="Orange" StrokeThickness="2">
     <Path.Data>
         <PathGeometry Figures="M 10 10 L 100 100 200 100" />
    </Path.Data>
</Path>

Tento kousek kódu lze zapsat i následovně.

<Path Stroke="Orange" StrokeThickness="2" Data="M 10 10 L 100 100 200 100">
</Path>

V praxi se spíše používá druhý zápis, kvůli své kompaktnosti.

Podívejme se na vlastnost Data – obsahuje řetězec M 10 10 L 100 100 200 100. Tento sled znaků a písmen je právě zmiňovaný Mini-jazyk. Písmenko M znamená Move (posun) - jedná se o klasický startovací bod (StartPoint). Startovací bod je umístěn na souřadnicích [10, 10]. Dále následuje písmenko L, které znamená Line (přímka). Dalšími parametry jsou již jen souřadnice jednotlivých bodů. Kód tedy lze chápat jako M [x, y] L [x, y] [x, y]. Přeložíme-li kód a spustíme aplikaci, dostaneme následující dvojici přímek.

PathGeometry5

Chceme-li obraz zakončit a započíst nový - vložíme do příkazu písmenko Z – tento znak WPF indikuje jako uzavření obrazu. Obecně platí, že každý nový obraz by měl začít znakem M a končit znakem Z, ovšem pokud opomeneme znak M, každý další obraz začíná implicitně za znakem Z. Pakliže vložíte znak Z – obraz se implicitně stane uzavřeným (obdoba vlastnosti IsClosed).

Pouze u přímek ale tento mini-jazyk nekončí, to by u vývojářů moc nepochodil. Krom písmenek M a L je zde celá řada dalších, pomocí kterých můžeme vykreslit například Beziérovu křivku či oblouky.

Pokusme se po načerpaných znalostech, o tomto Mini-jazyku pro cesty, vykreslit stejný obrazec, na kterém jsme si ukazovali vybrané segmenty v úvodu článku.

<Path Stroke="Orange" StrokeThickness="2" 
        Data="M 10 10 
L 100 100
L 200 100
A 50 50 45 1 0 200 200
C 500 100 100 100 400 200
L 400 100"
> </Path>

Kód výše nám vykreslí stejný výsledek jako zdlouhavý kód v úvodu článku. Mini-jazyk oceníte velmi při psaní cest kódově (C#.NET nebo VB.NET), kde pomocí pouhého sestavování stringu kreslíte křivky a přímky.

Ve většině případů lze zaměnit velká písmenka za malá. Kompletní výpis toho, co PathGeometry Markup Syntax umí, nalezne v dokumentaci na MSDN.

Závěr

Třída PathGeometry toho umí opravdu spousty. Dokážete pomocí ní nakreslit takřka libovolný obrazec (cestu). Nabízí nám dvojí zápis jednotlivých dílčích částí, buďto pomocí klasického „xaml“ zápisu, kde každý segment představuje jeden element, či můžete používat elegantní zápis v podobě mini-jazyku pro geometrické cesty.

Komentáře

ukládám komentář, vyčkejte prosím..
  1. Buďte první, kdo napíše komentář.

@xamlcz

  • RT @jvanrhyn: XAML, It's a bit like olives. Takes a while to get used to. But once you're used to it. It is actually pretty good. <3 XAML
  • RT @moser_christian: WPF Inspector 0.9.7 is released. It supports .NET 3.5 and 4.0 The project is now open source and available on CodeP ...
  • Jeff Handley oznámil vydání WCF RIA Services v.1.0 SP1 RTM http://bit.ly/gOgckn ke stažení na http://bit.ly/gVAXdK
  • jedna výzva pro Brno. Byl někdo z vás na přednášce o RIA v MS Akvárku? Dejte o sobě vědět. Děkuji
  • také jste uvažovali o tom, že zkusíte na projekt použít Caliburn Micro nebo naopak Prism 4? A co tak obojí, šlo by to nebo ne? Již brzy