Grafikus C# – Windows Forms tanfolyam › 9.3 lecke

Windows Forms · 9.3 lecke · saját rajzolás

Paint esemény és Graphics objektum

Eddig kész vezérlőket (PictureBox, Button) használtál. Ebben a leckében magad rajzolsz a képernyőre: vonalakat, téglalapokat, köröket, szöveget. A Windows Formsban a rajzolás szabálya más, mint amire elsőre gondolnál: a rajz nem „marad ott” magától, a felület tetszőleges pillanatban újrarajzolódhat (ablak elmozdítása, kicsinyítés), ezért a rajzolást a Paint eseményben végezzük, mindig a tárolt adatokból. Megtanulod a Graphics objektum használatát, az Invalidate és a Refresh különbségét, a villogást megszüntető kettős pufferelést, és a Bitmapra rajzolás módját.

  • kb. 110 perc
  • közepes szint
  • Paint és Graphics
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • érted, miért kell a Paint eseményben rajzolni, és miért nem a CreateGraphics() hívásban;
  • használod a PaintEventArgs (Graphics, ClipRectangle) és a Graphics alapműveleteit;
  • megkülönbözteted az Invalidate, a Refresh és az Update hívást;
  • bekapcsolod a kettős pufferelést (DoubleBuffered), és megszünteted a villogást;
  • a Graphics.FromImage segítségével Bitmapra rajzolsz;
  • tudod, mikor kell felszabadítani a Graphics objektumot, és mikor nem.

Mielőtt elkezded

Ehhez a leckéhez az 5.1 lecke (események), az 1.8 lecke (Timer) és a 9.1 lecke (Bitmap) szükséges. Az 5.9 leckében a kígyójátékban már használtál egyszerű rajzolást: itt az elvek rendszerét tanuljuk meg.

9.2 Képek méretezése →

A Paint esemény: a rajz „elfelejthető”

Képzeld el, hogy egy Panel felületére egyszer vonalat húzol (a CreateGraphics() segítségével). Ha az ablakot egy másik ablak eltakarja, majd újra előkerül, vagy kicsinyíted és visszaállítod, a vonal eltűnik: a Windows nem őrzi a vezérlő felületét. Ehelyett azt kéri a vezérlőtől, hogy rajzolja újra magát: ez a Paint esemény. Ezért a rajz szabálya: az adatot tárold, a Paint esemény rajzolja ki belőle:

Form1.cs (using System.Drawing;)C#
// az adat (a modell): hol van a kör és mekkora
private Point kozep = new Point(120, 90);
private int sugar = 50;

// a Paint eseményt a Panel kéri minden újrarajzoláskor: itt rajzolunk
private void pnlRajz_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics; // a rajzolófelület (nem kell felszabadítani!)

g.Clear(Color.White);

using (Pen toll = new Pen(Color.SeaGreen, 3))
{
g.DrawEllipse(toll, kozep.X - sugar, kozep.Y - sugar, 2 * sugar, 2 * sugar);
}
}

// az adat módosítása után kérjük az újrarajzolást
private void btnNagyit_Click(object sender, EventArgs e)
{
sugar += 10;
pnlRajz.Invalidate(); // a Windows hamarosan meghívja a Paint eseményt
}
PaintEventArgsKét tagja van: a Graphics (az a felület, amelyre rajzolsz) és a ClipRectangle (az a terület, amely ténylegesen újrarajzolandó; egyszerű programban figyelmen kívül hagyható).
Adat és rajz szétválasztásaA kozep és a sugar a modell, a Paint metódus a nézet. Ha az adat változik, csak az adatot módosítod és az újrarajzolást kéred: a rajzolás kódja egy helyen marad.
Melyik vezérlő?Bármelyik vezérlőnek van Paint eseménye. Rajzfelületnek a Panel vagy a PictureBox (a Paint a kép fölé rajzol) a szokásos, a Form saját felületére is rajzolhatsz.
Gyors metódusA Paint gyakran fut (átméretezés, takarás, animáció). Ne tegyél bele lassú műveletet (fájlbeolvasás, hálózat, hosszú számítás), és ne módosíts benne vezérlőket.

Jósolj!

A programban a gombnyomás a pnlRajz.CreateGraphics().DrawLine(...) hívással húz egy vonalat a panelre. A felhasználó megnyomja a gombot (a vonal megjelenik), majd kicsinyíti és visszaállítja az ablakot. Mi látszik?

Jóslat ellenőrzése

A vonal eltűnik: a CreateGraphics csak a pillanatnyi képre rajzol, az újrarajzolás (Paint) ezt letörli. Ezért a vonalat az adatok között kell tárolni, és a Paint eseményben kell kirajzolni.

A Graphics objektum alapjai

A Graphics a „rajzvászon és rajzeszközök” egyben. A koordináta-rendszer origója a bal felső sarok, az X jobbra, az Y lefelé nő, a mértékegység a képpont (pixel). Az alakzatokat a megfelelő metódus rajzolja (Draw… a körvonalat tollal, Fill… a kitöltést ecsettel; a tollakat és ecseteket a 9.4 lecke részletezi):

MetódusMit rajzol?
Clear(szín)Az egész felületet a megadott színre festi (háttér).
DrawLine(toll, x1, y1, x2, y2)Vonal két pont között.
DrawRectangle, FillRectangleTéglalap körvonala, illetve kitöltve (x, y, szélesség, magasság).
DrawEllipse, FillEllipseEllipszis (kör): a befoglaló téglalap adja meg.
DrawArc, DrawPieKörív, kimetszés (kezdőszög és ív fokban).
DrawPolygon, FillPolygonSokszög pontok tömbjéből (Point[]).
DrawString(szöveg, betű, ecset, x, y)Szöveg rajzolása.
DrawImage(kép, x, y)Kép kirajzolása (9.1–9.2 lecke).
Form1.csC#
private void pnlRajz_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; // éldzsimítás: szebb körök, ferde vonalak

g.Clear(Color.White);

using (Pen toll = new Pen(Color.Black, 2))
using (Brush sarga = new SolidBrush(Color.Gold))
using (Font betu = new Font("Segoe UI", 14, FontStyle.Bold))
{
g.FillEllipse(sarga, 20, 20, 120, 120); // a fej: a befoglaló téglalap bal felső sarka és mérete
g.DrawEllipse(toll, 20, 20, 120, 120);
g.FillEllipse(Brushes.Black, 55, 55, 14, 14); // szemek
g.FillEllipse(Brushes.Black, 95, 55, 14, 14);
g.DrawArc(toll, 45, 60, 70, 60, 20, 140); // száj: kezdőszög 20°, ív 140°
g.DrawString("Szia!", betu, Brushes.DarkSlateBlue, 160, 70);
}
}
A kör a befoglaló téglalapjávalA DrawEllipse(toll, x, y, w, h) a kört a befoglaló téglalap bal felső sarkával adja meg (nem a középpontjával). A középpont (cx, cy) és a sugár (r) esetén: cx - r, cy - r, 2 * r, 2 * r.
SzögekA DrawArc szöge fokban van: 0° a jobb oldal (3 óra), pozitív irányban az óramutató járásával megegyezően nő (mert az Y lefelé nő).
using és a PenA Pen, Brush és Font objektumok rendszererőforrást használnak: mindegyiket felszabadítjuk (using). A Brushes.Black és a többi előre elkészített ecset közös, ezeket ne szabadítsd fel.
AntiAliasAz éldzsimítás nélkül a ferde vonalak és körök recések. Bekapcsolva szebbek, kicsit lassabbak. Állítsd be a Paint elején.

Jósolj!

A g.FillEllipse(Brushes.Red, 100, 100, 50, 50) hívás hová rajzol kört: a (100, 100) pont a kör középpontja vagy a bal felső sarka? Mekkora a kör?

Jóslat ellenőrzése

A (100, 100) a befoglaló téglalap bal felső sarka, a kör átmérője 50 (sugara 25), középpontja a (125, 125). Ha középpontot adsz meg, a bal felső sarok: cx - sugar, cy - sugar.

Invalidate, Refresh és a villogás elleni pufferelés

HívásMit tesz?
Invalidate()Érvénytelennek jelöli a vezérlő felületét: a Windows a következő alkalommal (nem azonnal) újrarajzoltatja. A leggyakoribb választás, több hívás összevonódik.
Invalidate(Rectangle)Csak a megadott téglalapot jelöli érvénytelennek (kisebb rajz, kevesebb villogás).
Update()Az érvénytelen részt azonnal kirajzoltatja.
Refresh()Invalidate() és Update() együtt: azonnali, teljes újrarajzolás. Ritkábban kell (lassabb).
RajzPanel.csC#
// a Panel DoubleBuffered tulajdonsága védett: saját osztályban állítható
public class RajzPanel : Panel
{
public RajzPanel()
{
this.DoubleBuffered = true; // előbb a háttérpufferbe rajzol, utána egyszerre mutatja: nincs villogás
this.ResizeRedraw = true; // átméretezéskor teljes újrarajzolás
}

public bool Pufferelt
{
get { return this.DoubleBuffered; }
set { this.DoubleBuffered = value; }
}
}

// a Formon a Panel helyett a RajzPanel használható:
// Build után megjelenik a Toolbox tetején (vagy kódból: Controls.Add(new RajzPanel()))
Miért villog a rajz?Alapból a vezérlő először letörli a hátterét, azután rajzolja a tartalmat: a kettő között a felhasználó a törölt (üres) képet is látja. Animációnál ez erős villogás.
Kettős pufferelésA DoubleBuffered = true mellett a rajz előbb egy rejtett pufferbe készül el, és csak a kész kép kerül a képernyőre. A villogás megszűnik. Animációnál és gyakori újrarajzolásnál mindig kapcsold be.
Form és saját osztályA Form.DoubleBuffered közvetlenül beállítható (this.DoubleBuffered = true). A Panel, PictureBox tulajdonsága védett, ezért kell a saját leszármazott osztály.
ResizeRedrawA ResizeRedraw = true az átméretezéskor az egész felületet újrarajzolja. Nélküle a rajz egy része (pl. a jobb szélhez igazított) hibásan marad.

Jósolj!

Egy időzítő 20 ms-onként mozgat egy labdát, és minden lépésben a pnlRajz.Refresh() metódust hívja, kettős pufferelés nélkül. Mit látsz?

Jóslat ellenőrzése

A labda mozgása közben a kép villog, és a program lassabb is, mert a Refresh minden lépésben azonnal, teljesen újrarajzol. A DoubleBuffered = true és az Invalidate() használata sima mozgást ad.

Rajzolás Bitmapra: két megközelítés

A. Rajz a Paint eseményben (vektoros)

Az alakzatokat adatként (List<Alakzat>) tárolod, és a Paint minden alkalommal újrarajzolja őket. Előny: bármikor módosíthatók, törölhetők, mozgathatók (a 9.7 lecke). Hátrány: sok alakzatnál lassabb.

B. Rajz Bitmapra (raszteres)

Egy Bitmapra rajzolsz (Graphics.FromImage), és a Paint csak a kész képet mutatja. Előny: a rajz „megmarad”, gyors, a kép menthető (festőprogram). Hátrány: a már lerajzolt dolog nem módosítható (csak ráfestéssel).

Form1.csC#
private Bitmap vaszon;   // a rajz tárolója (festőprogram módszer)

private void Form1_Load(object sender, EventArgs e)
{
vaszon = new Bitmap(pnlRajz.Width, pnlRajz.Height);

using (Graphics g = Graphics.FromImage(vaszon)) // ez a Graphics a miénk: using kell
{
g.Clear(Color.White);
}
}

private void Rajzol(Point a, Point b)
{
using (Graphics g = Graphics.FromImage(vaszon))
using (Pen toll = new Pen(Color.Black, 3))
{
g.DrawLine(toll, a, b);
}

pnlRajz.Invalidate();
}

private void pnlRajz_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(vaszon, 0, 0); // a Paint csak kirajzolja a kész képet
}
Graphics.FromImageA Bitmapra rajzoláshoz külön Graphics kell. Ezt te hozod létre, ezért te is szabadítod fel (using), ellentétben az e.Graphicsszel.
Melyik Graphicset kell felszabadítani?Amit te hozol létre (FromImage, CreateGraphics, FromHwnd): igen. Az e.Graphics a Paint eseményé: nem, a keretrendszer szabadítja fel.
A vászon méreteÁtméretezhető ablaknál a vászon fix méretű: nagyobb ablaknál új vászonra kell másolni a régit (a 9.10 lecke projektjében).
CreateGraphicsA vezerlo.CreateGraphics() a vezérlő felületére közvetlenül rajzol, de a rajz az első újrarajzoláskor elveszik: ne használd tartós rajzra. Csak ideiglenes, villanásnyi visszajelzéshez való, és using kell hozzá.

Jósolj!

A Paint esemény kezelője a végén g.Dispose(); hívást tartalmaz, ahol g = e.Graphics. Mi történik?

Jóslat ellenőrzése

A keretrendszer még használná az e.Graphics objektumot (például a további vezérlők rajzolásához), ezért a felszabadított Graphics hibát (kivételt, vagy hiányzó rajzot) okozhat a következő használatnál. Az e.Graphics nem a te tulajdonod: ne szabadítsd fel.

📷 Képernyőkép helye

Mit tegyél ide: a Paint-labor ablak: rács, pattogó labda, mosolygó arc, a „Paint hívások” számláló és a beállító jelölőnégyzetek.Javasolt fájlnév: winforms-9-3-paint-labor.webp · Alt szöveg: A Paint-labor program

Hibakeresés: te vagy a hibadetektív

Mi a baj, és hogyan javítod?

Gondold végig az okot, és csak utána nyisd le a megoldást.

1. A rajz eltűnik az ablak mozgatásakor

private void btnRajz_Click(object sender, EventArgs e)
{
using (Graphics g = pnlRajz.CreateGraphics())
{
g.FillEllipse(Brushes.Red, 10, 10, 50, 50); // az újrarajzolás letörli
}
}
Hiba és javítás

A rajz nincs tárolva. Tárold az adatot (a kör helyét), és a Paint eseményben rajzold ki; a gomb csak az adatot módosítsa és hívja az Invalidate() metódust.

2. Villog a rajz animációnál

A mozgó labda villog.

Ok és javítás

Nincs kettős pufferelés. Használj DoubleBuffered = true beállítású panelt (saját osztály), és Invalidate() hívást a Refresh() helyett.

3. Végtelen újrarajzolás

private void pnlRajz_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.White);
pnlRajz.Invalidate(); // a Paint újabb Paint-et kér: a processzor 100%-on fut
}
Hiba és javítás

A Paint kezelőben ne hívj Invalidate metódust (hurok). Az újrarajzolást az adat változásakor (esemény, időzítő) kérd.

4. ArgumentException vagy memória-szivárgás a Pen miatt

g.DrawLine(new Pen(Color.Black, 2), 0, 0, 100, 100);   // minden Paint új Pen-t hoz létre, felszabadítás nélkül
Hiba és javítás

A Pen rendszererőforrás. Használj using blokkot (vagy egyszer létrehozott, a Form zárásakor felszabadított tollat). A gyakori Paint miatt a szivárgás gyorsan nő (9.8 lecke).

5. A rajz csak az ablak egy részén frissül

Átméretezéskor a rajz jobb széle hibás marad.

Ok és javítás

A ResizeRedraw nincs bekapcsolva. Állítsd true értékre (a saját panelosztályban), vagy kezeld a Resize eseményt és hívj Invalidate() metódust.

6. A kör nem ott van, ahol vártam

A (100, 100) középpontú kör máshol jelenik meg.

Ok és javítás

A DrawEllipse a befoglaló téglalap bal felső sarkát várja. Számold át: (cx - r, cy - r, 2r, 2r).

Mini projekt: Paint-labor

Készíts programot, amely a lecke összes fogalmát kísérletezhetővé teszi:

  1. RajzPanel osztály: a Panel leszármazottja DoubleBuffered és ResizeRedraw beállítással, a Pufferelt tulajdonsággal (ki- és bekapcsolható). A Formon rp néven, Dock = Fill elhelyezéssel.
  2. Modell: a labda (labdaX, labdaY, vx, vy, R = 20), amely a panel széléről visszapattan; az tmrAnim 20 ms-onként lépteti és Invalidate() hívással kéri az újrarajzolást.
  3. Paint: fehér háttér; 40 pixelenként halvány rács (Pen using blokkban); a labda (FillEllipse, DrawEllipse); egy mosolygó arc a bal alsó sarokban (ellipszisek és DrawArc); a DrawString kiírja a Paint hívások számát és az e.ClipRectangle méretét.
  4. Beállítók: chkSima (Éldzsimítás, SmoothingMode), chkPuffer (Kettős pufferelés), chkRefresh (Refresh Invalidate helyett), tbSebesseg (TrackBar, a labda sebessége), Start/Szünet gomb.
  5. Gombok: Új vonal a CreateGraphics-szal (a vonal eltűnik az első újrarajzoláskor: a hibás módszer bemutatása) és Új vonal a modellbe (List<Point[]>: két pontból álló tömbök, amelyeket a Paint rajzol ki).
  6. Bitmap-vászon: egy kis pbVaszon panel, amelyre az egér húzásával rajzolhatsz egy Bitmapra (a 9.6 lecke előkészítése): most elég a gombra kattintás: Véletlen vonal a vászonra.
  7. Teszteld: a két gomb viselkedése ablak kicsinyítése után, villogás pufferrel és anélkül (Refresh módban is), az éldzsimítás hatása, a Paint-számláló növekedése átméretezéskor, a program memóriahasználata hosszú futás után.
Megoldás megnyitása (próbáld ki előbb önállóan!)

A labda mozgását az időzítő végzi (a Paint csak rajzol). A mosolygó arc és a rács a Paint-ben készül, a tollak using blokkban.

RajzPanel.cs és Form1.cs (using System.Drawing.Drawing2D; using System.Collections.Generic;)C#
public class RajzPanel : Panel
{
public RajzPanel()
{
this.DoubleBuffered = true;
this.ResizeRedraw = true;
}

public bool Pufferelt
{
get { return this.DoubleBuffered; }
set { this.DoubleBuffered = value; }
}
}

// Form1.cs
private float labdaX = 60, labdaY = 60, vx = 4, vy = 3;
private const int R = 20;
private int paintDb = 0;
private List<Point[]> vonalak = new List<Point[]>();
private Random veletlen = new Random();

private void Form1_Load(object sender, EventArgs e)
{
tmrAnim.Interval = 20;
tmrAnim.Start();
chkPuffer.Checked = true;
chkSima.Checked = true;
}

private void tmrAnim_Tick(object sender, EventArgs e)
{
float seb = tbSebesseg.Value / 4f;

labdaX += vx * seb;
labdaY += vy * seb;

if (labdaX - R < 0) { labdaX = R; vx = Math.Abs(vx); }
if (labdaX + R > rp.Width) { labdaX = rp.Width - R; vx = -Math.Abs(vx); }
if (labdaY - R < 0) { labdaY = R; vy = Math.Abs(vy); }
if (labdaY + R > rp.Height) { labdaY = rp.Height - R; vy = -Math.Abs(vy); }

if (chkRefresh.Checked) { rp.Refresh(); } else { rp.Invalidate(); }
}

private void rp_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
paintDb++;

g.SmoothingMode = chkSima.Checked ? SmoothingMode.AntiAlias : SmoothingMode.None;
g.Clear(Color.White);

using (Pen halvany = new Pen(Color.Gainsboro))
{
for (int x = 0; x < rp.Width; x += 40) { g.DrawLine(halvany, x, 0, x, rp.Height); }
for (int y = 0; y < rp.Height; y += 40) { g.DrawLine(halvany, 0, y, rp.Width, y); }
}

using (Pen vonal = new Pen(Color.SteelBlue, 2))
{
foreach (Point[] v in vonalak) { g.DrawLine(vonal, v[0], v[1]); } // a modellből
}

using (Brush labda = new SolidBrush(Color.FromArgb(104, 64, 165)))
using (Pen perem = new Pen(Color.Black, 2))
{
g.FillEllipse(labda, labdaX - R, labdaY - R, 2 * R, 2 * R);
g.DrawEllipse(perem, labdaX - R, labdaY - R, 2 * R, 2 * R);
}

// mosolygó arc a bal alsó sarokban
int ax = 20, ay = rp.Height - 100;

using (Brush sarga = new SolidBrush(Color.Gold))
using (Pen toll = new Pen(Color.Black, 2))
{
g.FillEllipse(sarga, ax, ay, 80, 80);
g.DrawEllipse(toll, ax, ay, 80, 80);
g.FillEllipse(Brushes.Black, ax + 22, ay + 24, 10, 10);
g.FillEllipse(Brushes.Black, ax + 48, ay + 24, 10, 10);
g.DrawArc(toll, ax + 18, ay + 28, 44, 36, 20, 140);
}

using (Font betu = new Font("Segoe UI", 11))
{
g.DrawString("Paint hívások: " + paintDb + " ClipRectangle: " + e.ClipRectangle.Width + " × " + e.ClipRectangle.Height, betu, Brushes.Black, 8, 8);
}
}

private void chkPuffer_CheckedChanged(object sender, EventArgs e)
{
rp.Pufferelt = chkPuffer.Checked;
}

private void btnCreateGraphics_Click(object sender, EventArgs e)
{
using (Graphics g = rp.CreateGraphics())
using (Pen toll = new Pen(Color.Red, 3))
{
g.DrawLine(toll, 0, 0, rp.Width, rp.Height); // az animáció következő képkockája letörli
}
}

private void btnModellVonal_Click(object sender, EventArgs e)
{
vonalak.Add(new Point[] { new Point(veletlen.Next(rp.Width), veletlen.Next(rp.Height)), new Point(veletlen.Next(rp.Width), veletlen.Next(rp.Height)) });
rp.Invalidate();
}

private void btnStart_Click(object sender, EventArgs e)
{
tmrAnim.Enabled = !tmrAnim.Enabled;
}
📷 Képernyőkép helye

Mit tegyél ide: a kész Paint-labor, a labdával, a rácsal és a számlálóval.Javasolt fájlnév: winforms-9-3-kesz.webp · Alt szöveg: A kész Paint-labor

Három önálló gyakorlófeladat

Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.

1. Analóg óra

Rajzolj analóg órát: számlap, 12 jelölés, három mutató. Hol tárolod az adatot, és mikor rajzol újra?

Egy lehetséges megoldás

Az adat az idő (DateTime.Now), amelyet másodpercenként egy Timer frissít (Invalidate()). A mutató végpontját szögfüggvényekkel számolod (9.5 lecke): a szög másodperc * 6° (a 12-től indulva).

Form1.csC#
private PointF Vegpont(PointF kozep, double fok, float hossz)
{
double radian = (fok - 90) * Math.PI / 180; // 0°: a 12-es állás (felfelé)

return new PointF(kozep.X + (float)(hossz * Math.Cos(radian)), kozep.Y + (float)(hossz * Math.Sin(radian)));
}

// a Paintben: másodpercmutató
DateTime most = DateTime.Now;
PointF v = Vegpont(kozep, most.Second * 6, 90);
g.DrawLine(toll, kozep, v);

2. Rajz a Form felületére

A Form felületére (nem panelre) rajzolj háttérként átmenetes sávot. Melyik eseményt használod, és mit állítasz be?

Egy lehetséges megoldás

A Form Paint eseményét, DoubleBuffered = true és ResizeRedraw = true beállítással. Az átmenetet a LinearGradientBrush adja (9.4 lecke).

3. Az újrarajzolás számlálója

Mérd meg, hány Paint fut másodpercenként (FPS). Hogyan?

Egy lehetséges megoldás

A Paint minden hívása növel egy számlálót; egy másodperces Timer kiírja és nullázza.

Form1.csC#
private int kepkocka = 0;private void rp_Paint(object sender, PaintEventArgs e) { kepkocka++; /* ... rajz ... */ }private void tmrFps_Tick(object sender, EventArgs e){
lblFps.Text = kepkocka + " FPS";
kepkocka = 0;
}

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Hová rajzol?

A g.DrawRectangle(Pens.Black, 10, 20, 100, 50): hol a bal felső sarok, mekkora a téglalap?

Megoldás megnyitása

Bal felső sarok: (10, 20), szélesség 100, magasság 50; a jobb alsó sarok (110, 70). Az Y lefelé nő.

2. Invalidate vagy Refresh?

Melyiket használod: (a) egérmozgásra frissülő rajz; (b) hosszú számítás közben egyszeri azonnali kép?

Megoldás megnyitása

(a) Invalidate(); (b) Refresh() (vagy Update()), mert a felület szála a számítás alatt nem rajzolna újra. (Hosszú műveletet a 11. fejezet szerint háttérszálon végezz.)

Közepes szint

3. Középre igazított szöveg

A panel közepére írj szöveget. Hogyan számolod a pozíciót?

Megoldás megnyitása

A g.MeasureString a szöveg méretét adja; a pozíció: (panelSzeles - m.Width) / 2, (panelMagas - m.Height) / 2. Vagy a StringFormat Alignment = Center, LineAlignment = Center beállítása és a téglalapba rajzolás.

Form1.csC#
using (StringFormat sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
using (Font betu = new Font("Segoe UI", 16))
{
g.DrawString("Középen", betu, Brushes.Black, new RectangleF(0, 0, rp.Width, rp.Height), sf);
}

4. Rajz a Paint eseményben vagy Bitmapra?

Döntsd el: (a) festőprogram szabadkézi vonallal; (b) tervezőprogram mozgatható alakzatokkal; (c) óra.

Megoldás megnyitása

(a) Bitmap (a vonalak megmaradnak); (b) a Paint-ben, adatokból (módosíthatók); (c) a Paint-ben, az idő adatából.

Kihívás szint

5. Csak a változott rész újrarajzolása

A labda mozgásakor csak a régi és az új helyét érvénytelenítsd. Hogyan, és mire kell figyelned?

Megoldás megnyitása

Két Invalidate(Rectangle) hívás (a régi és az új befoglaló téglalap, kis ráhagyással). A Paint-nek ilyenkor a ClipRectangle miatt a teljes jelenetet rajzolnia kell (a rendszer csak a kijelölt részt festi). Összetett jelenetnél hibás, a rács ilyenkor torzulhat: egyszerűbb a teljes Invalidate().

6. Saját vezérlő rajzzal

Készíts Jelzolampa vezérlőt (Control leszármazott), amely egy színes kört rajzol az OnPaint metódusban, és a színe tulajdonsággal állítható. Mit kell tenned a szín módosításakor?

Megoldás megnyitása

A tulajdonság set ágában Invalidate(); a rajzolás az OnPaint(PaintEventArgs e) felülírásában, és a konstruktorban DoubleBuffered = true.

Jelzolampa.csC#
public class Jelzolampa : Control
{
private Color szin = Color.Green;

public Jelzolampa()
{
this.DoubleBuffered = true;
this.Size = new Size(40, 40);
}

public Color LampaSzin
{
get { return szin; }
set { szin = value; this.Invalidate(); }
}

protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

using (Brush b = new SolidBrush(szin))
{
e.Graphics.FillEllipse(b, 2, 2, this.Width - 5, this.Height - 5);
}
}
}

Gyors önellenőrzés

Válaszolj fejben, és csak utána nyisd le a választ.

1. Miért kell a Paint eseményben rajzolni?

A vezérlő felülete elveszhet (takarás, kicsinyítés); a Windows a Paint eseménnyel kéri az újrarajzolást, ezért a rajzot a tárolt adatokból ott kell elkészíteni.

2. Hol az origó, és merre nő az Y?

Az origó a bal felső sarok, az Y lefelé nő.

3. Mi a különbség az Invalidate és a Refresh között?

Az Invalidate kéri az újrarajzolást (később, összevonva), a Refresh azonnal, teljesen újrarajzol.

4. Mi a kettős pufferelés haszna?

Előbb a háttérpufferbe rajzol, és a kész képet mutatja: nincs villogás.

5. Melyik Graphics-et kell felszabadítani?

Amit te hozol létre (FromImage, CreateGraphics); az e.Graphicset nem.

6. Hogyan adod meg a kört a DrawEllipse-ben?

A befoglaló téglalap bal felső sarkával és méretével: (cx - r, cy - r, 2r, 2r).

7. Miért ne hívj Invalidate-et a Paint-ben?

Végtelen újrarajzolási hurkot okoz (a processzor tele lesz).

Mit tudsz már?

  • A Paint eseményben, tárolt adatokból rajzolsz.
  • Használod a Graphics alapműveleteit, és ismered a koordináta-rendszert.
  • Az Invalidate és a DoubleBuffered segítségével sima animációt készítesz.
  • Bitmapra rajzolsz, és tudod, melyik Graphicset kell felszabadítani.
  • Felismered a tipikus rajzolási hibákat.

Következő lecke: Pen, Brush, Color, Rectangle

A rajzeszközök: tollak vonalstílussal, ecsetek (egyszínű, átmenetes, mintás), színek átlátszósággal, téglalap-műveletek.