Ismétlés (Grafikai lehetőségek WPF-ben) Visual utódok Programozás III. Ismétlés (Grafikai lehetőségek WPF-ben) Visual utódok
Grafikai lehetőségek WPF-ben A grafikai keretrendszerek között azonosítunk azonnali és visszatartott módúakat Retained (visszatartott) mód: A grafikai primitívekből (vonalak, alakzatok…) készített jelenetet valamilyen formában a memóriában tartja, minden frame rajzolásánál a tárolt modell alapján keletkeznek a rajzolási parancsok. Az alkalmazás alakzatokat adhat hozzá vagy vehet el. Immediate (azonnali) mód: A keretrendszer nem tárolja a jelenetet, minden frame rajzolásánál az alkalmazás közvetlenül adja ki a rajzolási parancsokat. Az összes kirajzolandó dolog számon tartása az alkalmazás feladata. A WPF visszatartott módú grafikai rendszert biztosít Immediate mode: GDI/GDI+, Direct2D, Direct3D Retained mode: WPF, Direct3D (3.0 után megszüntették a retained mode-ot)
Grafikai lehetőségek WPF-ben Shape-ek (System.Windows.Shapes.Shape leszármazottak) Egyszerű, előre elkészített grafikai alakzatok Toolboxban is szerepelnek FrameworkElement utódok: input, fókusz, események… Csak kevés számú (néhány 100) objektum esetén Drawing objektumok (System.Windows.Media.Drawing leszármazottak) Nincs belső támogatásuk input eseményekhez Nem képesek maguktól a megjelenésre, hosztoló objektumban kell őket elhelyezni Legtöbbször XAML-ból kezeljük Gyorsabb a Shape-eknél (max néhány 1000 objektumig) Visual objektumok (System.Windows.Media.Visual) Legbonyolultabb, leggyorsabb (max néhány 10000 objektumig) XAML lehetőségek korlátozottak, mindig kódból kezeljük
Visual Ahhoz biztosít szolgáltatásokat, hogy a leszármazott típus renderelhető legyen DrawingVisual (2D), Viewport3DVisual (3D) kifejezetten rajzoláshoz De minden ablakra rajzolódó objektum is Visual utód VisualChildrenCount, GetVisualChild() – rajzolódáskor a vizuális fát ezekkel követi végig a WPF Többféleképpen használják alacsony szintű rajzolásra GUI-elemben újabb Visualok tárolása (VisualChildrenCount, GetVisualChild() override, esetleg AddVisualChild() hívás) GUI elem kirajzolódásakor végrehajtandó plusz rajzolási parancsok definálása (OnRender() override) Eddigi cuccaink is ezt használták belül. Rectangle, Ellipse: ők is Visualok. Image+DrawingImage: az Image is (mint minden Uielement) – viszont itt a benne kirajzolt dolgok már alacsonyabb szinten (mint Drawingok) definiálódnak Ez most egy még alacsonyabb szint. A megjelenő UIElementben a kirajzolt dolgok már csak mint grafikai parancsok lesznek megadva (ez a 2. variációra vonatkozik).
DrawingContext A DrawingContext segítségével „tölthetünk fel” egy Visualt kirajzolandó tartalommal Nem azonnali rajzolás (retained mode) A Visualon belül Drawingok formájában vannak tárolva a rajzok (DrawingGroupban) A rajzolás időpontját a rendszer dönti el Pl. a következő függvények értelmezettek rajta: DrawEllipse() DrawRectangle() DrawGeometry() DrawImage() … The DrawingContext allows you to populate a Visual with visual content. When you use a DrawingContext object's draw commands, you are actually storing a set of render data that will later be used by the graphics you are not drawing to the screen in real-time. DrawingGroup.Open and DrawingVisual.RenderOpen. Ezekkel lehet DrawingContextet nyerni. A Visual describes its content as one or more Drawing objects contained within a DrawingGroup
Visual használat 1. variáció példa public partial class MainWindow : Window { //sokszor nem a MainWindow-ban DrawingVisual visualChild; //csináljuk, hanem egy FrameworkElement public MainWindow() //utódot származtatunk és azt rakjuk { //a MainWindow-ra majd InitializeComponent(); visualChild = new DrawingVisual(); using (DrawingContext context = visualChild.RenderOpen()) { context.DrawEllipse(Brushes.Red, null, new Point(50, 50),10,10); } AddVisualChild(visualChild); AddLogicalChild(visualChild); protected override int VisualChildrenCount get { return base.VisualChildrenCount + 1; } protected override Visual GetVisualChild(int index) if (index < base.VisualChildrenCount) return base.GetVisualChild(index); else return visualChild; AddVisualChild(), AddLogicalChild() – vizuális/logikai fához adja, események és találattesztelés támogatása miatt
Visual használat 2. variáció class MainWindow : Window //tipikusan ezt sem a MainWindow-ban, { //hanem pl. FrameworkElement utód... //... //- ablaknál Background=Transparent kell. protected override void OnRender(DrawingContext drawingContext) { drawingContext.DrawGeometry(Brushes.Blue, new Pen(Brushes.Red, 2), geometry); } UIElement-ben az OnRender() felülírásával a rajzoló rendszer által még végrehajtandó grafikai parancsokat adhatjuk ki Nem azonnali rajzolás (retained mode) Új rajzolás mindig kikényszeríthető az InvalidateVisual() függvénnyel
FrameworkElement szabályok Üres (nem rajzolt) területre nem lehet MouseDown eseményt elkapni Megoldás: a legelső rajzolt objektum egy nagy háttér-téglalap legyen Billentyűzet esemény nem rendelhető hozzá Megoldás1: az ablakhoz viszont rendelhető, a tartalmazó ablak referenciáját elérjük a Window.GetWindow(this) segítségével Megoldás2: Focusable=true és fókuszálni kell a FrameworkElement-et, ekkor a billentyűeseményt is megkapja
Transzformációk Transform utódokkal reprezentálódnak (transzformációs mátrix) Forgatás (RotateTransform) Skálázás (ScaleTransform) Nyújtás (SkewTransform) Mozgatás (MoveTransform) Általános transzformáció (MatrixTransform) Több transzformáció egymás után (TransformGroup)
Transzformáció alkalmazásai GUI-elemeken (FrameworkElement utódok): LayoutTransform (kihelyezés előtt transzformál) RenderTransform (kihelyezés után transzformál) Geometriákon: Transform Figyelem: nem módosít koordinátákat! A Transform-ot levéve (xxx.Transform = Transform.Identity) az eredeti koordinátákon marad az objektum Metszésnél esetleg gondot okoz
Geometriák metszése (érintésvizsgálat) Geometriák metsződésének vizsgálatához az elmetszéssel létrejött új geometriát létre kell hozni (Geometry.Combine()), majd a területét vizsgálni (GetArea()) Figyelem: LineGeometry-nak nincs kiterjedése, így a metszet területe 0 Megoldása a vonal „kiterjesztésével” geometry.GetWidenedPathGeometry(new Pen(Brushes.Blue, 2)) Geometry intersection = Geometry.Combine(geometry, otherGeometry, GeometryCombineMode.Intersect, null); return intersection.GetArea() != 0;
Feladat – Flappy Birds Turbo
Megoldási alternatíva Minden játékelem Geometry típusú adattagja úgy kerül beállításra, hogy a középpont koordinátája (0;0) legyen Kirajzolás/ütközésvizsgálat előtt transzformáljuk a megfelelő pozícióba TransformGroup tg = new TransformGroup(); tg.Children.Add(new TranslateTransform(cx, cy)); tg.Children.Add(new RotateTransform(degree, cx, cy)); Geometry copy = area.Clone(); copy.Transform = tg; return copy.GetFlattenedPathGeometry();
Feladat – Asteroids
Asteroids szabályok: Játékos csak foroghat és lőhet. Az aszteroida véletlenszerű irányba egyenletesen mozog, ha a képernyő szélére ér, visszatér a másik oldalon Ha a játékos lövése eltalál egy aszteroidát, az eltűnik. (Nehezebb verzió: két kisebb és gyorsabban mozgó részre válik szét, egy bizonyos mérethatárig.) Ha a játékos lövése a képernyő szélére ér, visszatér a másik oldalon. Viszont a lövés csak bizonyos ideig „él”, egy idő után eltűnik.