PwT.N W10

Transkrypt

PwT.N W10
Budowa aplikacji w technologii .NET
wykład 10 – Kształty, pędzle, transformacje, ścieżki
Kształty
Najprostszy sposób rysowania własnej zawartości w WPF, to wykorzystanie kształtów.
Shapes – klasy reprezentujące linie, elipsy, prostokąty, wielokąty (prymitywy, kształty
podstawowe).
Wszystkie one dziedziczą z FrameworkElement, a zatem:
•
•
•
Odpowiadają za narysowanie samych siebie (również automatycznie reagują na
zmianę właściwości).
Są zorganizowane w ten sam sposób co inne elementy (umieszczane w kontenerze
układu, przeważnie Canvas).
Obsługują te same zdarzenia, co inne elementy (a także wspierają tooltipy, menu
kontekstowe, operacje drag-and-drop).
1/58
Klasy kształtów
Każdy kształt dziedziczy z abstrakcyjnej klasy System.Windows.Shapes.Shape:
• Rectangle – prostokąt
• Ellipse – elipsa
• Line – odcinek
• Polyline – połączony ciąg odcinków
• Polygon – zamknięty kształt z ciągu połączonych odcinków
• Path – pozwala na łączenie w jednym elemencie wszystkich innych kształtów
2/58
Własności klasy Shape:
•
•
•
•
•
•
•
•
•
•
Fill – pędzel do namalowania zawartości (wnętrza) kształtu
Stroke – pędzel do namalowania krawędzi kształtu
StrokeThickness – grubość krawędzi. Jest rozdzielana po równo na obie strony.
StrokeStartLineCap, StrokeEndLineCap – określają kształt końca i początku
odcinków
StrokeDashArray, StrokeDashOffset, StrokeDashCap – pozwalają na
zdefiniowanie linii przerywanych.
StrokeLineJoin, StrokeMiterLimit – określają kształt narożników (wierzchołków)
Stretch – określa, jak kształt ma dopasować się do dostępnego miejsca (można też
używać HorizontalAlignment i VerticalAlignment).
DefiningGeometry – dostarcza definicję geometrii kształtu (np. współrzędnych
punktów).
GeometryTransform – pozwala zastosować transformację (np. przesunięcie,
pochylenie, obrót, skalowanie).
RenderedGeometry – dostarcza geometrię ostatecznego ,renderowanego kształtu.
3/58
Rectangle and Ellipse
Wystarczy zdefiniować rozmiar kształtu (a także Fill lub/i Stroke, aby kształt stał się
widoczny):
<StackPanel>
<Ellipse Fill="Yellow" Stroke="Blue" Height="50"
Width="100" Margin="5" HorizontalAlignment="Left"/>
<Rectangle Fill="Yellow" Stroke="Blue" Height="50"
Width="100" Margin="5" HorizontalAlignment="Left"/>
</StackPanel>
4/58
Rectangle dodaje dwie własności: RadiusX i RadiusY (pozwalają na rysowanie
zaokrąglonych narożników).
5/58
Rozmieszczanie kształtów
Przeważnie rozmiar i położenie kształtów określa się ręcznie.
Możemy też pozwolić, by kształt dopasował się do dostępnego miejsca:
<Grid>
<Ellipse Fill="Yellow" Stroke="Blue"></Ellipse>
</Grid>
6/58
Określając wartość własności Stretch:
• Fill – dopasowanie wysokości i szerokości do dostępnego miejsca.
• None – bez dopasowania.
• Uniform – dopasowanie proporcjonalne (tak, by kształt mieścił się w kontenerze).
Jeśli określimy wysokość i szerokość, będą traktowane jako górne ograniczenie.
• UniformToFill – podobnie, ale każdy z wymiarów ma wypełnić dostępną
przestrzeń (część kształtu może zostać ucięta).
•
7/58
Położenie kształtów jest określane na tych samych zasadach, co innych elementów: rządzi
nim kontener. Największą kontrolę nad położeniem daje Canvas:
<Canvas>
<Ellipse Fill="Yellow" Stroke="Blue" Canvas.Left="100"
Canvas.Top="50" Width="100" Height="50"/>
<Rectangle Fill="Yellow" Stroke="Blue" Canvas.Left="30"
Canvas.Top="40" Width="100" Height="50"/>
</Canvas>
(uwaga: kolejność definicji ma znaczenie, gdy kształty nakładają się na siebie)
(uwaga: Cansa nie musi być elementem najwyższego poziomu – możemy umieścić go np.
w komórce Grida).
8/58
Viewbox
Jest sposobem na połączenie precyzyjnego rozmieszczania kształtów i skalowania do
dostępnego rozmiaru.
Viewbox to Decorator, przyjmujący pojedyncze dziecko (może nim być kontener z
dalszymi elementami), które skaluje, dopasowując do dostępnej przestrzeni. Najbardziej
opłacalne w wypadku grupy kształtów:
<Grid Margin="5">
<Viewbox Stretch="UniformToFill">
<Canvas Width="50" Height="50">
<Ellipse Fill="Yellow" Stroke="Blue" Canvas.Left="5"
Canvas.Top="5" Width="40" Height="40"/>
</Canvas>
</Viewbox>
</Grid>
9/58
(Viebox skaluje całą zawartość: nie tylko rozmiar samego kształtu, ale i np. grubość
krawędzi, a także kontrolki, które w nim umieścimy).
Własność Viewbox.Stretch domyślnie ustawiona jest na Uniform, ale można też użyć Fill i
UniformToFill, aby zmienić sposób skalowania zawartości, a także StretchDirection (poza
Both mamy UpOnly, aby tylko powiększać i DownOnly aby tylko zmniejszać).
Uwaga: element umieszczony w ViewBoxie musi mieć określony rozmiar, aby Viewbox
wiedział jak go przeskalować (tzn. jaki jest jego rozmiar bazowy).
10/58
Line
Własności X1, Y1 oraz X2, Y2 określają współrzędne początkowego i końcowego punktu
linii.
<Grid Margin="5">
<Line Stroke="Blue" X1="0" Y1="0" X2="100" Y2="10"/>
</Grid>
Dla linii określamy tylko właność Stroke (Fill nie jest brany pod uwagę).
Współrzędne są określane w odniesieniu do lewego, górnego narożnika kontenera (plus
Margin), w którym umieszczono kształt.
W przeciwieństwie do Rectangle i Ellipse, możemy rysować linie poza kontenerem (np.
podając ujemne współrzędne). Nie możemy za to ustawić dla nich marginesu i alignmentu.
Nadal możemy określić położenie punktu początkowego linii w Canvasie przy pomocy
Canvas.Top, Canvas.Left a rozmiar przy pomocy Width i Height.
11/58
Polyline
Pozwala narysować linię łamaną. Definiujemy ją przy pomocy listy współrzędnych X i Y.
Można podać ją jako kolekcję punktów:
<Polyline Stroke="MediumBlue" StrokeThickness="5">
<Polyline.Points>
<PointCollection>
<Point X="10" Y="110"/>
<Point X="30" Y="120"/>
<Point X="50" Y="100"/>
<Point X="70" Y="140"/>
<Point X="90" Y="80"/>
<Point X="110" Y="170"/>
<Point X="130" Y="50"/>
<Point X="150" Y="170"/>
<Point X="170" Y="80"/>
<Point X="190" Y="140"/>
</PointCollection>
</Polyline.Points>
</Polyline>
12/58
Albo w skróconej formie, jako wartości oddzielane spacjami (przecinek między X a Y jest
opcjonalny).
<Polyline Stroke="MediumBlue" StrokeThickness="5"
Points="10,90 30,100 50,80 70,120 90,60 110,150 130,30
150,150 170,60 190,120"/>
13/58
Polygon
Działa niemal tak samo jak Polyline. Jedyna różnica: Polygon jest krzywą zamkniętą (sam
dodaje ostatni segment, łączący punkt końcowy z początkowym). Może używać pędzla
wypełnienia (Fill).
<Polygon Stroke="MediumBlue" Fill="LightBlue"
StrokeThickness="5" Points="20,85 30,100 50,80 70,120 90,60
110,150 130,30 150,150 170,60 180,130"/>
14/58
Są dwa sposoby wypełniania Polygonu.
Domyślny:
<Polygon Stroke="MediumBlue" Fill="LightBlue"
FillRule="EvenOdd" StrokeThickness="5" Points="95,20 160,150
20,70 180,60 50,160"/>
15/58
<Polygon Stroke="MediumBlue" Fill="LightBlue"
FillRule="Nonzero" StrokeThickness="5" Points="95,20 160,150
20,70 180,60 50,160"/>
16/58
Line Caps, Line Joins
W wypadku rysowania linii możemy określić kształt zakończeń – StrokeStartLineCap i
StrokeEndLineCap: Flat (domyślne), Round, Square i Triangle (wszystkie zwiększają
długość linii o ½ szerokości).
<Canvas>
<Polyline StrokeEndLineCap="Flat"
Stroke="MediumBlue"
StrokeThickness="20"
Points="20,40 40,60 80,20 100,40 200,40"/>
<Polyline StrokeEndLineCap="Round" .../>
<Polyline StrokeEndLineCap="Square" .../>
<Polyline StrokeEndLineCap="Triangle" .../>
</Canvas>
17/58
StrokeLineJoin pozwala określić kształt łączeń (wierzchołków łamanej) – Miter
(domyślny), Bevel (ścięte narożniki), Round (zaokrąglone). Miter pozwala podać
StrokeMiterLimit, który ogranicza wydłużanie narożników (wartość 1 to maksymalnie ½
szerokości linii).
<Canvas>
<Polyline Stroke="MediumBlue"
StrokeThickness="20"
StrokeLineJoin="Miter"
Points="20,20 140,40 40,60 60,80"/>
<Polyline StrokeLineJoin="Bevel" .../>
<Polyline StrokeLineJoin="Round" .../>
<Polyline StrokeLineJoin="Miter"
StrokeMiterLimit="3".../>
</Canvas>
18/58
Dashes
<Canvas>
<Polyline
<Polyline
<Polyline
<Polyline
<Polyline
<Polyline
</Canvas>
StrokeDashArray="1" .../>
StrokeDashArray="1 2" .../>
StrokeDashArray="2 1" .../>
StrokeDashArray="3 2 1 2" .../>
StrokeDashArray="5 2 1" .../>
StrokeDashArray="2 2" StrokeDashCap="Round" .../>
StrokeDashArray pozwala zdefiniować dowolny
wzór linii przerywanej – podajemy wartości,
określające długość segmentu i przerwy między
segmentami. Liczba tych wartości nie musi być
parzysta. StrokeDashCap pozwala określić kształt
zakończeń segmentów (uwaga na długość
zwiększoną o ½ szerokości).
StrokeDashOffset pozwala zacząć przerywaną od
wybranej wartości wzorca.
19/58
Pixel Snapping
Uwaga: wymiary podajemy zawsze w jednostkach logicznych (1/96 cala). Mogą być to
liczby całkowite. W zależności od urządzenia nie muszą się one tłumaczyć na faktyczne
położenie pixeli. Domyślnie jest to niwelowane przez antyaliasing. Gdy nie jest to
pożądane, można włączyć opcję SnapsToDevicePixels (można to ustawić osobno dla
każdego kształtu) – powoduje ona zaokrąglenie wartości do faktycznych pikseli
urządzenia.
Brushes
Pędzle używane są zarówno do rysowania tła (background), pierwszego planu
(foreground), krawędzi (border) elementów, jak też wypełnienia (fill) oraz obwiedni
(stroke) kształtów.
• Pędzle obsługują powiadamianie o zmianie (gdy zmodyfikujemy pędzel, wszystkie
elementy, które go używają, powinny się odrysować).
• Obsługują półprzeźroczystość (własność Opacity).
• Klasa SystemBrushes udostępnia pędzle używające kolorów systemowych
(zdefiniowanych w ustawieniach użytkownika).
20/58
Dostępne rodzaje pędzli:
• SolidColorBrush – najprostszym rodzaj pędzla: wypełnia zawartość jednolitym
kolorem. Jest on stosowany domyślnie, gdy podajemy sam kolor jako wartość
własności w XAMLu.
• LinearGradientBrush, RadialGradientBrush – wypełnienie gradientowe.
• ImageBrush – wypełnienie przy pomocy obrazka.
• DrawingBrush – wypełnienie przy użyciu własnej grafiki (np. kształtów).
• VisualBrush – wypełnianie przy użyciu dowolnego obiektu typu Visual (a więc np.
również elementów interfejsu – przydatne do różnych efektów specjalnych).
LinearGradientBrush
Wymaga podania listy elementów GradientStop (każdy element to kolejny kolor).
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="LightBlue" Offset="0" />
<GradientStop Color="MediumBlue" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
21/58
Offset określa położenie każdego koloru, można w ten sposób sterować szerokością
przejścia:
<Rectangle Margin="3">
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="LightBlue" Offset="0.3" />
<GradientStop Color="MediumBlue" Offset="0.7" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
StartPoint i EndPoint pozwalają sterować kierunkiem gradientu.
<Rectangle Margin="3">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="LightBlue" Offset="0" />
<GradientStop Color="MediumBlue" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
22/58
<Rectangle Margin="3">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="LightBlue" Offset="0" />
<GradientStop Color="MediumBlue" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Margin="3">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,0.2" SpreadMethod="Reflect">
<GradientStop Color="LightBlue" Offset="0" />
<GradientStop Color="MediumBlue" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
23/58
<Rectangle Margin="3">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,0.2" SpreadMethod="Repeat">
<GradientStop Color="LightBlue" Offset="0" />
<GradientStop Color="MediumBlue" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
Można określić więcej kolorów składowych:
<Rectangle Margin="3">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
<GradientStop Color="Red" Offset="0" />
<GradientStop Color="White" Offset="0.5" />
<GradientStop Color="Black" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
(Uwaga: oczywiście, takiego pędzla możemy używać nie tylko do rysowania kształtów, ale
i np. tekstu, krawędzi, etc.)
24/58
RadialGradientBrush
Kolory definiuje się, jak w LinearGradientBrush. Różnica polega na określeniu pozycji.
<Ellipse Margin="3">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Margin="3">
<Ellipse.Fill>
<RadialGradientBrush Center="0.2,0.8">
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
25/58
<Ellipse Margin="3">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.2,0.8">
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Margin="3">
<Ellipse.Fill>
<RadialGradientBrush RadiusX="0.25" RadiusY="1">
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
26/58
ImageBrush
<Rectangle Margin="3">
<Rectangle.Fill>
<ImageBrush ImageSource="happy_face.png">
</ImageBrush>
</Rectangle.Fill>
</Rectangle>
Viewbox pozwala wskazać fragment obrazka do użycia w roli pędzla (koordynaty
względne).
<Rectangle Margin="3">
<Rectangle.Fill>
<ImageBrush ImageSource="happy_face.png"
Viewbox="0.5 0.1 0.4 0.4">
</ImageBrush>
</Rectangle.Fill>
</Rectangle>
27/58
Tiled ImageBrush
Viewport określa względny rozmiar pojedynczego obrazka (a tym samym – ile powtórzeń
znajdzie się w wypełnianym obszarze – niezależnie od jego rozmiaru).
<Ellipse Margin="3" Stroke="Black">
<Ellipse.Fill>
<ImageBrush ImageSource="happy_face.png"
TileMode="Tile" Viewport="0,0 0.4,0.5">
</ImageBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Margin="3" Stroke="Black">
<Ellipse.Fill>
<ImageBrush ImageSource="happy_face.png"
TileMode="FlipXY" Viewport="0,0 0.4,0.5">
</ImageBrush>
</Ellipse.Fill>
</Ellipse>
28/58
Ustawienie ViewportUnits jako Absolute spowoduje, że obrazek nie będzie skalowany, a
powtarzany ilość razy zależną od rozmiaru figury.
<Ellipse Margin="3" Stroke="Black">
<Ellipse.Fill>
<ImageBrush ImageSource="happy_face.png"
ViewportUnits="Absolute" TileMode="Tile"
Viewport="0,0 48,48">
</ImageBrush>
</Ellipse.Fill>
</Ellipse>
29/58
VisualBrush
Pozwala pobrać obraz dowolnego elementu i używać go jako pędzla. Uwaga: kopiuje to
tylko wygląd elementu, skopiowane kontrolki nie dadzą się używać. Pędzel reaguje na
zmianę wyglądu kontrolki (np. naciśnięcie przycisku, wpisanie wartości do pola
tekstowego).
<StackPanel>
<Button Margin="3" Name="ok">OK</Button>
<Rectangle Margin="3" Height="{Binding ElementName=ok,
Path=ActualHeight}">
<Rectangle.Fill>
<VisualBrush Visual="{Binding ElementName=ok}"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle Margin="3" Height="50">
<Rectangle.Fill>
<VisualBrush Visual="{Binding ElementName=ok}"/>
</Rectangle.Fill>
</Rectangle>
</StackPanel>
Jest to używane np. aby pokazać podgląd zawartości okna, wygenerować miniaturę, albo
do efektów specjalnych (odbicia, cienie, animacje).
30/58
Transforms
Transformacje afiniczne. Wszystkie klasy dziedziczą z System.Windows.Media.Transform.
TranslateTransform – translacja (własności X i Y)
RotateTransform – obrót (Angle, CenterX, CenterY)
ScaleTransform – skalowanie (ScaleX, ScaleY, CenterX, CenterY)
SkewTransform – pochylenie (AngleX, AngleY, CenterX, CenterX)
MatrixTransform – dowolne przekształcenie z wykorzystaniem własnej macierzy
przekształcenia (Matrix)
TransformGroup – łączy kilka transformacji (ich kolejność jest ważna)
Aby transformować kształt należy ustawić własność RenderTransform.
W obrocie określamy kąt:
<Canvas Margin="3">
<Rectangle Stroke="DarkBlue" Width="150" Height="20">
<Rectangle.RenderTransform>
<RotateTransform Angle="10"/>
</Rectangle.RenderTransform>
</Rectangle>
...
</Canvas>
31/58
Można też wybrać środek obrotu:
<Canvas Margin="3">
<Rectangle Stroke="DarkBlue" Width="150" Height="20">
<Rectangle.RenderTransform>
<RotateTransform Angle="10"
CenterX="75" CenterY="10"/>
</Rectangle.RenderTransform>
</Rectangle>
...
</Canvas>
Środek można też wskazać jako wartość względną:
<Canvas Margin="3">
<Rectangle Stroke="DarkBlue" Width="150" Height="20"
RenderTransformOrigin="0.2,0.5">
<Rectangle.RenderTransform>
<RotateTransform Angle="10" />
</Rectangle.RenderTransform>
</Rectangle>
...
</Canvas>
32/58
Transforming Elements
Te same transformacje mogą być stosowane również w stosunku do elementów:
<Button Margin="3" Name="ok" RenderTransformOrigin="0.5,0.5">
<Button.RenderTransform>
<TransformGroup>
<SkewTransform AngleX="-45" />
<RotateTransform Angle="30"/>
<TranslateTransform Y="50"/>
</TransformGroup>
</Button.RenderTransform>
OK</Button>
Dostępne jest jeszcze LayoutTransform, które jest aplikowane przed ułożeniem zawartości.
<StackPanel>
<Button Margin="3" Name="ok" RenderTransformOrigin="0.5,0.5">
<Button.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="45"/>
</TransformGroup>
</Button.LayoutTransform>
OK</Button>
</StackPanel>
33/58
Transparency
Sposoby na efekt przeźroczystości:
• Własność Opacity elementu (od 1 – nieprzejrzysty do 0 – całkowicie
przeźroczysty).
• Opacity pędzla – wówczas dotyczy tylko tego, co jest rysowane tym pędzlem.
• Użycie koloru o wartości alfa mniejszej niż 255 (często daje lepsze efekty niż
ustawienie Opacity).
•
<StackPanel Background="LawnGreen">
<Button Margin="3">
Nieprzejrzysty
</Button>
<Button Margin="3" Opacity="0.5" Background="Yellow">
Półprzeźroczysty
</Button>
<Button Margin="3"
Background="#80FFFF00">
Półprzeźroczysty kolor
</Button>
</StackPanel>
34/58
Opacity Masks
W odróżnieniu od Opacity, która ustawia jeden poziom przeźroczystości dla całego
elementu, Opacity Mask pozwala na bardziej urozmaicone efekty. Akceptuje dowolny
pędzel i używa jego kanału alfa (kolor nie jest ważny) by określić przezroczystość.
<StackPanel Background="LawnGreen">
<Button Margin="3" Padding="3">
<Button.OpacityMask>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#00000000" Offset="0"/>
<GradientStop Color="#ff000000" Offset="1"/>
</LinearGradientBrush>
</Button.OpacityMask>
Opacity Mask
</Button>
</StackPanel>
35/58
Wykorzystanie do efektu lustra:
<StackPanel>
<Button Margin="3" Name="ok">Naciśnij mnie</Button>
<Rectangle Margin="3" Height="{Binding ElementName=ok,
Path=ActualHeight}" RenderTransformOrigin="0,0.5">
<Rectangle.Fill>
<VisualBrush Visual="{Binding ElementName=ok}"/>
</Rectangle.Fill>
<Rectangle.RenderTransform>
<ScaleTransform ScaleY="-1"/>
</Rectangle.RenderTransform>
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="Transparent" Offset="0.3"/>
<GradientStop Color="#80000000" Offset="1"/>
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
</StackPanel>
36/58
Ścieżki i geometrie
Path to najbardziej złożony z dostępnych kształtów (shapes). Może zastąpić dowolne z
innych kształtów, a także umożliwia posługiwanie się krzywymi.
Klasa Path ma własność Data, pobierającą obiekt Geometry (klasa abstrakcyjna). Definiuje
on kształt lub kształty wchodzące w skład ścieżki. Używamy następujących klas
dziedziczących z Geometry:
• LineGeometry – linia prosta.
• RectangleGeometry – odpowiednik kształtu Rectangle.
• EllipseGeometry – elipsa.
• GeometryGroup – dodaje dowolną liczbę geometrii do jednej ścieżki.
• CombinedGeometry – łączy dwie geometrie w jedne kształt. Pozwala wybrać
sposób połączenia.
• PathGeometry – reprezentuje złożoną figurę (otwartą lub zamkniętą), składającą
się z sekwencji łuków, odcinków i krzywych.
• StreamGeometry – oszczędza pamięć nie przechowując całej geometrii, ale po
stworzeniu jest tylko do odczytu.
Geometry pozwala na opisanie geometrii dwuwymiarowych obiektów. Jest bardziej
uniwersalna od Shape, ale nie jest elementem okna (a zatem musi się znaleźć np. w Path).
37/58
Kształty można zapisać również za pomocą ścieżek:
<Rectangle Fill="Yellow"
Stroke="Blue" Width="100"
Height="50" />
<Path Fill="Yellow" Stroke="Blue">
<Path.Data>
<RectangleGeometry
Rect="0,0 100,50"/>
</Path.Data>
</Path>
<Line Stroke="Blue" X1="0"
Y1="0" X2="10" Y2="100"/>
<Path Fill="Yellow" Stroke="Blue">
<Path.Data>
<LineGeometry StartPoint="0,0"
EndPoint="10,100"/>
</Path.Data>
</Path>
<Ellipse Fill="Yellow"
Stroke="Blue"
Width="100" Height="50"
HorizontalAlignment="Left"/>
<Path Fill="Yellow" Stroke="Blue">
<Path.Data>
<EllipseGeometry RadiusX="50"
RadiusY="25" Center="50,25"/>
</Path.Data>
</Path>
W tej chwili jedyna widoczna korzyść to to, że nie musimy umieszczać ich w Canvas.
Prawdziwe możliwości pojawiają się gdy zechcemy połączyć kilka geometrii.
38/58
Łączenie kształtów przy pomocy GeometryGroup
To najprostszy sposób połączenie geometrii:
<Path Fill="Yellow" Stroke="Blue" Margin="5" Canvas.Top="10"
Canvas.Left="10" >
<Path.Data>
<GeometryGroup>
<RectangleGeometry Rect="0,0 100,100"/>
<EllipseGeometry Center="150,50" RadiusX="35"
RadiusY="25"/>
</GeometryGroup>
</Path.Data>
</Path>
Plusy:
• jeden obiekt zamiast dwóch – większa
wydajność (jeden element złożony działa
szybciej niż wiele elementów prostych)
• grupy kształtów można definiować w zasobach
i wykorzystywać w innych elementach
ścieżkach
39/58
W zasobach:
<Window.Resources>
<GeometryGroup x:Key="geometria">
<RectangleGeometry Rect="0 ,0 100 ,100"/>
<EllipseGeometry Center="150, 50" RadiusX="35"
RadiusY="25"/>
</GeometryGroup>
</Window.Resources>
<Canvas>
<Path Fill="Yellow"
Stroke="Blue" Margin="5"
Canvas.Top="10" Canvas.Left="10"
Data="{StaticResource geometria}">
</Path>
<Path Fill="Green"
Stroke="Blue" Margin="5"
Canvas.Top="50" Canvas.Left="50"
Data="{StaticResource geometria}">
</Path>
</Canvas>
40/58
Minusy geometrii:
• obsługa zdarzeń (np. myszy) możliwa tylko dla grupy elementów (dla Path) a nie
dla każdego z nich. Istnieje jednak możliwość manipulacji pojedynczymi obiektami
np. transformacja, pochylanie czy obrót
Ciekawsze rzeczy dzieją się, jeśli kształty się przecinają:
<GeometryGroup x:Key="geometria">
<RectangleGeometry Rect="0,0 100,100"/>
<EllipseGeometry Center="80, 10" RadiusX="35" RadiusY="25"/>
</GeometryGroup>
41/58
Bardziej urozmaicone sposoby łączenia kształtów oferuje CombinedGeometry
Może ona łączyć tylko dwie geometrie (własności Geometry1 i Geometry2).
GeometryCombineMode określa wykonywaną na nich operację:
• Union – suma
• Intersect – część wspólna
• Xor – alternatywa wykluczająca
• Exclude – różnica
<Window.Resources>
<RectangleGeometry x:Key="kwadrat"
Rect="0 ,0 100 ,100"/>
<EllipseGeometry x:Key="koło"
Center="80, 10" RadiusX="35"
RadiusY="35"/>
<CombinedGeometry x:Key="geometria"
Geometry1="{StaticResource kwadrat}"
Geometry2="{StaticResource koło}"
GeometryCombineMode="Union" />
</Window.Resources>
42/58
<CombinedGeometry x:Key="geometria"
Geometry1="{StaticResource kwadrat}"
Geometry2="{StaticResource koło}"
GeometryCombineMode="Intersect" />
<CombinedGeometry x:Key="geometria"
Geometry1="{StaticResource kwadrat}"
Geometry2="{StaticResource koło}"
GeometryCombineMode="Xor" />
<CombinedGeometry x:Key="geometria"
Geometry1="{StaticResource kwadrat}"
Geometry2="{StaticResource koło}"
GeometryCombineMode="Exclude" />
43/58
Bardziej złożone kształty należy składać z kilku CombinedGeometry.
<CombinedGeometry x:Key="geometria"
GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<CombinedGeometry GeometryCombineMode="Exclude">
<CombinedGeometry.Geometry1>
<EllipseGeometry Center="50,50" RadiusX="50"
RadiusY="50"/>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry Center="50,50" RadiusX="40"
RadiusY="40"/>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<RectangleGeometry Rect="0,45,100,10">
<RectangleGeometry.Transform>
<RotateTransform Angle="-45" CenterX="50"
CenterY="50"/>
</RectangleGeometry.Transform>
</RectangleGeometry>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
44/58
Efekt:
45/58
Krzywe i linie przy użyciu PathGeometry
PathGeometry to najbardziej zaawansowana z geometrii, może narysować wszystko to, co
poprzednie (i wiele więcej). Minusem jest trochę dłuższa i czasami bardziej
skomplikowana składnia.
Każdy obiekt PathGeometry zbudowany jest z jednego lub wielu obiektów PathFigure
(przechowywanych w kolekcji PathGeometry.Figures).
Każdy PathFigure jest zbiorem połączonych linii i krzywych mogących tworzyć zamknięte
lub otwarte obszary.
Własności PathFigure:
• StartPoint – punkt początkowy figury
• Segments – kolekcja obiektów PathSegment
• IsClosed – jeśli true, WPF dodaje linię łączącą punkt początkowy i końcowy )o ile
nie są takie same)
• IsFilled – jeśli true, wnętrze jest wypełniane przy użyciu pędzla Path.Fill
PathFigure to kształt który narysowany jest linią składającą się z wielu segmentów
(PathSegment). Kolejny segment zaczyna się w punkcie, w którym skończył się poprzedni.
(Uwaga: jedna PathGeometry może zawierać wiele osobnych PathFigure, czyli może
składać się z wielu osobnych figur.)
46/58
Istnieje kilka typów segmentów, dzięki czemu możemy narysować najróżniejsze kształty:
• LineSegment – linia prosta łącząca dwa punkty
• ArcSegment – łuk
• BezierSegment – krzywa Beziera
• QuadraticBezierSegment – prostsza forma krzywej Beziera, z jednym punktem
kontrolnym (szybsza)
• PolyLineSegment – ciąg odcinków
• PolyBezierSegment, PolyQuadraticBezierSegment – ciąg krzywych Beziera
LineSegment
<Path Stroke="Blue" StrokeThickness="5">
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="True"
StartPoint="20,20">
<LineSegment Point="90,50" />
<LineSegment Point="50,90"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
47/58
ArcSegment
Łuk to część elipsy o rozmiarze Size.
Dodatkowo możemy wybrać dłuższą lub krótszą część łuku, a także położenie krzywej.
<Path Stroke="Blue" StrokeThickness="3">
<Path.Data><PathGeometry>
<PathFigure IsClosed="False" StartPoint="20,20" >
<ArcSegment Point="150,150" Size="100,100"
IsLargeArc="False" SweepDirection="Clockwise" />
</PathFigure>
</PathGeometry></Path.Data>
</Path>
<Path Stroke="Green" StrokeThickness="3">
...<ArcSegment Point="150,150"
Size="190,110" IsLargeArc="False"
SweepDirection="Clockwise" /> ...
</Path>
<Path Stroke="Red" StrokeThickness="3">
...<ArcSegment Point="150,150"
Size="200,200" IsLargeArc="False"
SweepDirection="Counterclockwise" />
...
</Path>
48/58
Krzywe Beziera
<Path Stroke="Blue" StrokeThickness="5">
<Path.Data><PathGeometry>
<PathFigure StartPoint="10,10">
<BezierSegment Point1="130,30" Point2="40,140"
Point3="150,150" />
</PathFigure>
</PathGeometry></Path.Data>
</Path>
Pierwsze dwa punkty, to punkty kontrolne, trzeci –
koniec segmentu.
(punkty kontrolne i linie przerywane zostały dodane w
celu poglądowym i oczywiście nie pojawiają się
samoistnie na obrazie)
49/58
Geometry Mini-Language
Pozwala zdefiniować geometrię w skróconej formie: napisu zawierającego ciąg poleceń.
Każde polecenie to litera i wartości liczbowe oddzielane spacjami. Np. zamiast takiej
definicji:
<Path Stroke="Blue" StrokeThickness="5">
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="True" StartPoint="20,20">
<LineSegment Point="90,50" />
<LineSegment Point="50,90"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
Mamy to:
<Path Stroke="Blue" StrokeThickness="5"
Data="M 20,20 L 90,50 L 50,90 Z"/>
Przecinki są opcjonalne, ale poprawiają czytelność.
Tworzony jest (niemodyfikowalny) obiekt StreamGeometry, a nie PathGeometry.
50/58
Dostępne polecenia:
•
•
•
•
•
•
•
•
•
•
F value – ustawia własność Geometry.FillRule (0 oznacza EvenOdd, 1 – Nonzero).
Może pojawić się tylko na początku stringu.
M x,y – tworzy nową ścieżkę i ustawia punkt startowy. Musi pojawić się przed
jakąkolwiek inną (poza F), ale może pojawić się też później, by przemieścić punkt
startowy (M znaczy Move).
L x,y 0 – tworzy LineSegment
H x – tworzy poziomy LineSegment
V y – tworzy pionowy LineSegment
A radiusX, radiusY degrees isLargeArc, isClockwise x,y – tworzy ArcSegment
do wskazanego punktu
C x1,y1 x2,y2 x,y – tworzy krzywą Beziera z punktami kontrolnymi x1,y1 i x2,y2
Q x1, y1 x,y – QuadraticBezierSegment z jednym punktem kontrolnym
S x2,y2 x,y – tworzy kolejny segment Beziera, wykorzystując ostatni punkt
kontrolny z poprzedniej krzywej (smooth BezierSegment)
Z – kończy PathFigure i ustawia IsClosed na true (nieobowiązkowe)
Polecenie zapisane małymi znakami oznacza, że parametry są podane względem
poprzedniego punktu (a nie wartości absolutne).
51/58
Geometrie są używane nie tylko do definiowania ścieżek. Można ich użyć np. jako
własności Clip (określa obszar obcinania).
Przeważnie stosuje się to do obcinania obrazów i grafiki, ale może być też użyte do
elementów:
<StackPanel Margin="5">
<StackPanel.Clip>
<EllipseGeometry Center="100,100" RadiusX="80"
RadiusY="120" />
</StackPanel.Clip>
<Button>Jeden</Button>
<Button>Dwa</Button>
<Button>Trzy</Button>
<Button>Cztery</Button>
</StackPanel>
Minus - clipping nie bierze pod uwagę rozmiaru elementu
więc przy zmianie wielkości okna element się nie zmieni.
52/58
Drawings
Uzupełnieniem Geometry jest klasa Drawing. Dodaje do geometrii informacje o tym, jak
ją wyświetlić (np. kolor pędzla, grubość linii).
Drawing, podobnie jak geometria, nie jest elementem – nie można umieścić jej w oknie.
•
•
•
•
•
GeometryDrawing – uzupełnia geometrię (Geometry) o dane pędzla wypełnienia
(Brush) i „pióra” (Pen – określa ono wszystko, co dotyczy krawędzi: jej pędzel,
grubość, a także kształty końców, narożników, etc.)
DrawingGroup – kolekcja obiektów Drawing, pozwala stosować efekty na całej
grupie obiektów
ImageDrawing – obraz (ImageSource) wraz z prostokątem określającym rozmiar (Rect)
VideoDrawing – własności Player i Rect
GlyphRunDrawing – elementy tekstowe niskiego poziomu wraz z pędzlem
Wyświetlanie obiektów Drawing – muszą być umieszczone w jednym z dostępnych
elementów:
• DrawingImage – pozwala umieścić Drawing wewnątrz elementu Image
• DrawingBrush – pozwala wykorzystać Drawing jako Brush
•
DrawingVisual – pozwala umieścić Drawing w niskopoziomowym obiekcie Visual (więcej
na przyszłych wykładach)
53/58
Tak może wyglądać proste rozwiązanie umieszczające grafikę na przycisku:
<Button ... >
<Canvas ... >
<Polyline ... >
<Polyline ... >
<Rectangle ... >
<Ellipse ... >
<Polygon ... >
...
</Canvas>
</Button>
Można też zrobić to przy pomocy ścieżek (każda z własną geometrią) – bardziej wydajne:
<Button ... >
<Canvas ... >
<Path ... >
<Path ... >
...
</Canvas>
</Button>
54/58
Wykorzystując Drawing – geometria umieszczona jest w GeometryDrawing:
<GeometryDrawing Brush="...">
<GeometryDrawing.Geometry>
<EllipseGeometry .../>
<PathGeometry .../>
<RectangleGeometry .../>
</GeometryDrawing.Geometry>
</GeometryDrawing>
Drawing można umieścić w DrawingImage, który może służyć jako źródło obrazka Image:
<Image ...>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing ...>
...
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
55/58
Taki obrazek można umieścić w przycisku. Wiele GeometryDrawing można łączyć przy
pomocy DrawingGroup.
<Button ... >
<Image ... >
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing ... >
<GeometryDrawing ... >
<GeometryDrawing ... >
...
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<Image.Source>
</Image>
</Button>
Jeśli zamiast DrawingImage użyjemy DrawingBrush, możemy użyć go jako pędzla.
56/58
DrawingVisual – „najlżejszy” sposób rysowania, bez zdarzeń i układów, pozwala
renderować własny tekst i rysunki; nadaje się do wymagających zastosowań (np. aplikacje
graficzne)
hit test dostępny jako statyczna metoda HitTest() z klasy VisualTreeHelper
public partial class MainWindow : Window
{
// lista na DrawingVisual
private List<Visual> visuals = new List<Visual>();
public MainWindow()
{
InitializeComponent();
// tworzymy obiekt
DrawingVisual dv = new DrawingVisual();
// rozpoczynamy rysowanie
using (DrawingContext dc = dv.RenderOpen())
{
// i rysujemy
dc.DrawRectangle(Brushes.Red,
new Pen(Brushes.Green, 2.0),
new Rect(100, 100, 200, 100));
// ...
57/58
// ...
dc.DrawEllipse(Brushes.Green,
new Pen(Brushes.Yellow, 2.0),
new Point(200,100), 50, 100);
}
// dodajemy obiekt do drzewa logicznego i wizualnego
visuals.Add(dv);
AddVisualChild(dv);
AddLogicalChild(dv);
}
// wymagane nadpisanie – kontener udostępnia swoje dzieci
protected override int VisualChildrenCount
{
get { return visuals.Count; }
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
}
58/58

Podobne dokumenty