Grafikus C# – Windows Forms tanfolyam › 9.5 lecke Windows Forms · 9.5 lecke · koordináták Ahogy a rajzaid összetettebbek lesznek, felbukkan a kérdés: melyik koordinátarendszerben gondolkodom? A képernyő pixelei felülről lefelé számozódnak, a matematikai ábrákon az Y felfelé nő, egy játék világa méterekben vagy mezőkben mérhető, az egérkoordináta a vezérlőhöz viszonyított. Ebben a leckében rendet teszünk: megismered a Pontok, koordináták és méretezés
Point, PointF és Size típusokat, a képernyő- és a kliens-koordináta átszámítását, a világ–képernyő átszámítást (nagyítással és eltolással), és a Graphics transzformációit (TranslateTransform, RotateTransform, ScaleTransform), amelyekkel a forgás és a nagyítás egy sorra egyszerűsödik.
A lecke végére
- használod a
Point,PointF,SizeésSizeFtípusokat, és átalakítod őket (Point.Round); - megkülönbözteted a képernyő-, a kliens- és a világ-koordinátát (
PointToClient,PointToScreen); - világ–képernyő átszámítást írsz (méretezés, eltolás, az Y irányának megfordítása), és megfordítod egérkattintáshoz;
- a
Graphicstranszformációit használod (Translate,Rotate,Scale,Save,Restore); - polárkoordinátákkal számolsz (szög és távolság → pont);
- elkerülöd a tipikus hibákat (egész osztás, túl nagy koordináta, megmaradó transzformáció).
Mielőtt elkezded
Ehhez a leckéhez a 9.3–9.4 lecke (Paint, Graphics, Rectangle) és az 5.3 lecke (egéresemények) szükséges. A szögfüggvények (Math.Sin, Math.Cos) a matematikából ismertek, a szögek radiánban vannak.
Point, PointF, Size és az átalakítások
Point p = new Point(100, 50); // egész koordináták (képpont)
PointF q = new PointF(100.5f, 50.25f); // törtszám koordináták
Size m = new Size(200, 100);
SizeF mf = new SizeF(200.5f, 100f);
// műveletek
Point eltolt = Point.Add(p, new Size(10, 20)); // (110, 70)
Point kulonbseg = new Point(p.X - 30, p.Y - 10);
bool egyenlo = p == new Point(100, 50); // értékegyenlőség
// átalakítás: PointF → Point (kerekítéssel, nem levágással!)
Point kerekitett = Point.Round(q); // (100, 50) – a legközelebbi egész
Point levagott = Point.Truncate(q); // (100, 50) – a törtrész elhagyva
Point felfele = Point.Ceiling(q); // (101, 51) – felfelé
// két pont távolsága (Pitagorasz-tétel)
private double Tavolsag(PointF a, PointF b)
{
double dx = a.X - b.X;
double dy = a.Y - b.Y;
return Math.Sqrt(dx * dx + dy * dy);
}Point egész, a PointF törtszám koordinátájú. A Graphics metódusai általában mindkettőt elfogadják: számításnál a PointF pontosabb, a kerekítés a rajzolás pillanatáig elhalasztható.Point és a PointF struktúra (érték típus): az értékadás másolatot ad, és az X, Y tag csak a teljes pont cseréjével módosítható, ha a pont egy listában vagy tulajdonságban van (lista[0].X = 5 nem fordul le). Új pontot hozol létre.Point.Round a legközelebbi egészre, a Truncate a törtrész elhagyásával (0 felé), a Ceiling felfelé alakít. A (int) cast a Truncate-hez hasonlóan levág: egy képpontnyi eltérést okozhat.int / int egészet ad (7 / 2 == 3). Törtszám eredményhez legalább az egyik tag legyen double vagy float (7 / 2.0), vagy cast-old ((double)a / b).Jósolj!
A panel szélessége 301, és a középpontját így számolod: int kozep = rp.Width / 2; Mekkora az érték, és mi a következménye, ha a másik oldalról is így számolsz?
Jóslat ellenőrzése
A 301 / 2 egész osztás: 150 (a 150,5 helyett). A rajz fél képponttal balra tolódik; apró, de két szimmetrikus oldalnál látszik. Ha pontos közép kell, számolj törtszámmal: rp.Width / 2f.
Képernyő-, kliens- és világ-koordináták
| Rendszer | Origó | Hol találkozol vele? |
|---|---|---|
| Képernyő (screen) | a monitor bal felső sarka | Cursor.Position, Form.Location, Screen.WorkingArea. |
| Kliens (client) | a vezérlő belső bal felső sarka | Az egéresemények (e.X, e.Y), a Paint koordinátái, a Location a szülőhöz képest. |
| Világ (world) | a modell választott pontja | A saját adataid: méter, mező, matematikai koordináta, adatérték (grafikon). |
// az egér képernyő-koordinátáját a panel kliens-koordinátájára
Point kliens = pnlRajz.PointToClient(Cursor.Position);
// a panel egy pontját a képernyőre (például felugró ablak helyéhez)
Point kepernyo = pnlRajz.PointToScreen(new Point(10, 10));
// két vezérlő között: a gomb kliens-pontja a panel kliens-koordinátájában
Point panelben = pnlRajz.PointToClient(btnGomb.PointToScreen(Point.Empty));
// egéresemény: az e.Location mindig az eseményt kiváltó vezérlő kliens-koordinátája
private void pnlRajz_MouseMove(object sender, MouseEventArgs e)
{
lblPoz.Text = "Kliens: " + e.X + ", " + e.Y + " Képernyő: " + Cursor.Position.X + ", " + Cursor.Position.Y;
}PointToScreen, majd PointToClient).Location a szülő kliensterületéhez viszonyított; egy panelben lévő gomb Location értéke nem a Form koordinátája.vezerlo.ClientRectangle a rajzolható terület (0, 0, szélesség, magasság), keret és görgetősáv nélkül. Rajzoláshoz ezt használd, ne a Size értéket.Jósolj!
A Form_MouseMove eseményben a Cursor.Position értékét hasonlítod egy panelben lévő gomb Location értékéhez. Miért nem jó az összehasonlítás?
Jóslat ellenőrzése
A Cursor.Position képernyő-koordináta, a gomb Location értéke a panelhez viszonyított kliens-koordináta: két különböző rendszer. Előbb azonos rendszerbe kell hozni (például a gomb PointToScreen értéke).
Világ–képernyő átszámítás
Függvényábra, játéktér, adatgrafikon: a modell a saját mértékegységében él (például az x −5 és 5 között), a rajz pixelekben. Az átszámítás: méretezés (pixel per egység), eltolás (hol van az origó a panelen) és az Y megfordítása (matematikában felfelé nő). Két metódus elég, mindkét irányba:
private float skala = 40; // ennyi képpont jut egy világ-egységre
private PointF kozepVilag = new PointF(0, 0); // ez a világ-pont van a panel közepén
// világ → képernyő (a panel kliens-koordinátája)
private PointF Kepernyore(double x, double y)
{
return new PointF(
rp.Width / 2f + (float)((x - kozepVilag.X) * skala),
rp.Height / 2f - (float)((y - kozepVilag.Y) * skala)); // a mínusz: az Y felfelé nő a világban
}
// képernyő → világ (például kattintás helye)
private PointF Vilagba(Point p)
{
return new PointF(
(p.X - rp.Width / 2f) / skala + kozepVilag.X,
-(p.Y - rp.Height / 2f) / skala + kozepVilag.Y);
}
private void rp_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// tengelyek: az origó képernyő-pontján át
PointF origo = Kepernyore(0, 0);
g.DrawLine(Pens.Black, 0, origo.Y, rp.Width, origo.Y); // x tengely
g.DrawLine(Pens.Black, origo.X, 0, origo.X, rp.Height); // y tengely
// függvény: y = sin(x) – pontok gyűjtése, majd egy töröttvonal
List<PointF> pontok = new List<PointF>();
for (int px = 0; px <= rp.Width; px += 2)
{
double x = Vilagba(new Point(px, 0)).X; // a panel minden 2. oszlopához tartozó x
double y = Math.Sin(x);
pontok.Add(Kepernyore(x, y));
}
if (pontok.Count > 1)
{
using (Pen toll = new Pen(Color.SteelBlue, 2))
{
g.DrawLines(toll, pontok.ToArray());
}
}
}Kepernyore a rajzoláshoz, a Vilagba az egérhez kell. A kettő egymás fordítottja: Vilagba(Kepernyore(x, y)) visszaadja az eredeti értéket. Ezt érdemes ellenőrizni.- jel a képernyő-Y számításban fordítja meg. Ha elhagyod, az ábra tükörképe jelenik meg.skala (nagyítás) és a kozepVilag (eltolás) módosításával a nagyítás és a mozgatás megvalósul, a rajzoló kód változatlan. A képernyőméret változása (átméretezés) is magától működik, mert a rp.Width és rp.Height alapján számol.1/x) OverflowException vagy rajzolási hiba keletkezik. A float értékeket rajzolás előtt korlátozd (pl. −10 000 és 10 000 közé), és NaN, végtelen értéknél szakítsd meg a vonalat.Jósolj!
A skala 40, a panel 400 × 300, a kozepVilag (0, 0). Melyik képernyő-pontra esik a világ (2, 1) pontja?
Jóslat ellenőrzése
X: 400 / 2 + 2 · 40 = 280; Y: 300 / 2 − 1 · 40 = 110. A (2, 1) világ-pont a középtől jobbra és fölfelé van, ezért az Y képernyő-érték kisebb, mint a közép (150).
A Graphics transzformációi
Ahelyett, hogy minden pontot kézzel számolnál át, a Graphics objektumnak megadhatod az átalakítást: TranslateTransform (eltolás), RotateTransform (forgatás fokban), ScaleTransform (nagyítás). Utána a rajzolás a módosított koordinátarendszerben történik. A Save és Restore a beállításokat félre teszi és visszaállítja:
private float szog = 0; // a Timer növeli
private PointF[] Csillag(float kulso, float belso)
{
PointF[] pontok = new PointF[10];
for (int i = 0; i < 10; i++)
{
double fok = -90 + i * 36; // 36 fokonként: váltakozó külső és belső csúcs
float r = i % 2 == 0 ? kulso : belso;
double radian = fok * Math.PI / 180;
pontok[i] = new PointF((float)(r * Math.Cos(radian)), (float)(r * Math.Sin(radian))); // az origó körül
}
return pontok;
}
private void rp_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(Color.White);
GraphicsState alap = g.Save(); // az eredeti állapot elmentése
g.TranslateTransform(rp.Width / 2f, rp.Height / 2f); // az origó a panel közepére kerül
g.RotateTransform(szog); // elforgatás az (új) origó körül, fokban
using (Brush arany = new SolidBrush(Color.Gold))
using (Pen keret = new Pen(Color.DarkGoldenrod, 2))
{
PointF[] csillag = Csillag(80, 35);
g.FillPolygon(arany, csillag); // a csillag az origó körül, a forgatás elfordítja
g.DrawPolygon(keret, csillag);
}
g.Restore(alap); // vissza az eredeti koordinátarendszerhez
g.DrawString("Szög: " + szog.ToString("0") + "°", this.Font, Brushes.Black, 8, 8); // ez már forgatás nélkül
}
private void tmrForgat_Tick(object sender, EventArgs e)
{
szog = (szog + 2) % 360;
rp.Invalidate();
}Save() a transzformációt (és más beállításokat) menti, a Restore(állapot) visszaállítja. Egymásba ágyazható: egy kar a testhez képest, az ujj a karhoz képest forog.TranslateTransform viszi a panel közepére.ScaleTransform(2, 2) a tollak vastagságát is megkétszerezi, és a szöveg is nagyobb lesz. Ez lehet hasznos (zoom), de meglepő is.Jósolj!
A Paint-ben a transzformációk után nincs Restore, és a szöveget (DrawString) a végén rajzolod ki, ugyanazzal a Graphics objektummal. Hol és hogyan jelenik meg a szöveg?
Jóslat ellenőrzése
A szöveg a panel közepén, elforgatva jelenik meg (a (8, 8) a transzformált rendszerben értendő). A Restore vagy a ResetTransform() nélkül a transzformáció a Paint végéig megmarad. A következő Paint már új Graphicset kap, ezért a hiba csak a metóduson belül látszik.
Mit tegyél ide: a koordináta-felfedező: bal oldalon a függvényábra tengelyekkel és az egér világ-koordinátájával, jobb oldalon a forgó csillag.Javasolt fájlnév: winforms-9-5-koordinatak.webp · Alt szöveg: A koordináta-felfedező 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 függvényábra fejjel lefelé van
A parabola lefelé nyílik, pedig felfelé kellene. Az Y megfordítása hiányzik. A képernyő-Y számításban Ok és javítás
- jel kell: rp.Height / 2f - (y - közép) * skala.
2. A középpont fél képponttal elcsúszik
int kozepX = rp.Width / 2; // egész osztásHiba és javítás
Az egész osztás levágja a törtrészt. Használj rp.Width / 2f törtszámos osztást, ha pontos közép kell.
3. A forgó alakzat elszalad
g.RotateTransform(szog);
g.TranslateTransform(rp.Width / 2f, rp.Height / 2f); // fordított sorrendHiba és javítás
Előbb az eltolás, utána a forgatás kell: a transzformációk az aktuális rendszerre hatnak, a sorrend a hatást változtatja.
4. Elforgatott szöveg a rajz végén
A felirat a rajz után elforgatva jelenik meg. A transzformáció megmaradt. Használd a Ok és javítás
Save és Restore párost, vagy hívd a ResetTransform() metódust a transzformált rész után.
5. OverflowException az 1/x ábrázolásakor
pontok.Add(Kepernyore(x, 1 / x)); // x = 0-nál végtelenHiba és javítás
A 1 / 0.0 végtelen, amelyet a GDI+ nem tud kirajzolni. Ellenőrizd a double.IsNaN, double.IsInfinity értéket, korlátozd a képernyő-koordinátát (−10 000…10 000), és a nem rajzolható ponton szakítsd meg a vonalat.
6. Az egérkattintás rossz világ-pontot ad
A kattintás helye nem egyezik az ábra pontjával. A kattintás kliens-koordinátáját közvetlenül használod világ-koordinátaként. Alakítsd át a Ok és javítás
Vilagba metódussal (ami a skálát, az eltolást és az Y irányát is kezeli).
Mini projekt: koordináta-felfedező
Készíts programot két rajzpanellel, amely a világ–képernyő átszámítást és a transzformációkat mutatja:
- Felület: bal oldalon
rpAbra(RajzPanel, 9.3 lecke), jobb oldalonrpCsillag; alul állapotsor (tsslEgér,tsslVilag); fent:cmbFuggveny(sin(x), x², 1/x, sin(x)/x, √x),tbNagyit(TrackBar, 10–120: pixel / egység),nudKozepX,nudKozepY(eltolás), Alaphelyzet gomb. - Átszámítás: a
Kepernyoreés aVilagbametódusok askalaés akozepVilagmezőkkel; a tengelyek, a rács (egységenként) és a számozás (−5…5 körül) a világ-koordináták szerint. - Függvényábra: a függvény pontjai minden képernyő-oszlopra (
px += 2); a nem értelmezhető (NaN, végtelen) pontoknál a vonal megszakad, és a túl nagy értékeket korlátozod (±10 000 pixel); a vonal 2 pixel vastag,AntiAlias. - Egér: az
rpAbraMouseMoveeseménye kiírja a kliens- és a világ-koordinátát (két tizedesre); a kattintás pontot tesz le (List<PointF>a világ-koordinátákkal), amely a nagyítás és eltolás után is a helyén marad; a pont mellett a koordinátája látszik. - Forgó csillag (jobb panel): 10 csúcsú csillag az origó körül (
Csillag),TranslateTransform+RotateTransform,Save/Restore; atmrForgat20 ms-onként 2 fokkal forgat; a csillag közepén kis kör, a szög kiírva forgás nélkül; achkNagyitjelölőnégyzetScaleTransformsegítségével lüktetve (1,0–1,3) nagyítja. - Biztonság: a
Paintkezelők nem omlanak össze nulla méretű panelnél, végtelen értéknél; a transzformáció nem szivárog aPaintkezelő végén túl (Restore). - Teszteld: minden függvény, nagyítás és eltolás, a kattintott pont helye nagyítás után, az
1/xpólusa, átméretezés, az egér világ-koordinátája az origó és a tengelyek közelében, a csillag forgása és lüktetése.
Megoldás megnyitása (próbáld ki előbb önállóan!)
A pontokat világ-koordinátában tároljuk, ezért a nagyítás és az eltolás után is a helyükön maradnak: a Paint minden alkalommal átszámolja őket.
private float skala = 40;
private PointF kozepVilag = new PointF(0, 0);
private List<PointF> pontok = new List<PointF>();
private float szog = 0;
private float lukt = 0;
private bool beallit = false;
private void Form1_Load(object sender, EventArgs e)
{
cmbFuggveny.Items.AddRange(new object[] { "sin(x)", "x²", "1/x", "sin(x)/x", "√x" });
cmbFuggveny.SelectedIndex = 0;
tbNagyit.Minimum = 10;
tbNagyit.Maximum = 120;
tbNagyit.Value = 40;
tmrForgat.Interval = 20;
tmrForgat.Start();
}
private PointF Kepernyore(double x, double y)
{
return new PointF(rpAbra.Width / 2f + (float)((x - kozepVilag.X) * skala), rpAbra.Height / 2f - (float)((y - kozepVilag.Y) * skala));
}
private PointF Vilagba(Point p)
{
return new PointF((p.X - rpAbra.Width / 2f) / skala + kozepVilag.X, -(p.Y - rpAbra.Height / 2f) / skala + kozepVilag.Y);
}
private double F(double x)
{
switch (cmbFuggveny.SelectedIndex)
{
case 1: return x * x;
case 2: return 1 / x;
case 3: return x == 0 ? 1 : Math.Sin(x) / x;
case 4: return Math.Sqrt(x);
default: return Math.Sin(x);
}
}
private void rpAbra_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
g.SmoothingMode = SmoothingMode.AntiAlias;
if (rpAbra.Width < 10 || rpAbra.Height < 10) { return; }
// rács egységenként
float bal = Vilagba(new Point(0, 0)).X, jobb = Vilagba(new Point(rpAbra.Width, 0)).X;
float also = Vilagba(new Point(0, rpAbra.Height)).Y, felso = Vilagba(new Point(0, 0)).Y;
using (Pen racs = new Pen(Color.Gainsboro))
{
for (int x = (int)Math.Floor(bal); x <= jobb; x++) { PointF a = Kepernyore(x, 0); g.DrawLine(racs, a.X, 0, a.X, rpAbra.Height); }
for (int y = (int)Math.Floor(also); y <= felso; y++) { PointF a = Kepernyore(0, y); g.DrawLine(racs, 0, a.Y, rpAbra.Width, a.Y); }
}
PointF origo = Kepernyore(0, 0);
g.DrawLine(Pens.Black, 0, origo.Y, rpAbra.Width, origo.Y);
g.DrawLine(Pens.Black, origo.X, 0, origo.X, rpAbra.Height);
// a függvény: megszakítható töröttvonal
List<PointF> szakasz = new List<PointF>();
using (Pen toll = new Pen(Color.SteelBlue, 2))
{
for (int px = 0; px <= rpAbra.Width; px += 2)
{
double x = Vilagba(new Point(px, 0)).X;
double y = F(x);
bool ok = !double.IsNaN(y) && !double.IsInfinity(y);
PointF k = ok ? Kepernyore(x, y) : PointF.Empty;
if (ok && Math.Abs(k.Y) < 10000)
{
szakasz.Add(k);
}
else
{
if (szakasz.Count > 1) { g.DrawLines(toll, szakasz.ToArray()); }
szakasz.Clear();
}
}
if (szakasz.Count > 1) { g.DrawLines(toll, szakasz.ToArray()); }
}
// a kattintott pontok (világ-koordinátában tárolva)
foreach (PointF v in pontok)
{
PointF k = Kepernyore(v.X, v.Y);
g.FillEllipse(Brushes.Crimson, k.X - 4, k.Y - 4, 8, 8);
g.DrawString("(" + v.X.ToString("0.00") + "; " + v.Y.ToString("0.00") + ")", this.Font, Brushes.Black, k.X + 6, k.Y - 18);
}
}
private void rpAbra_MouseMove(object sender, MouseEventArgs e)
{
PointF v = Vilagba(e.Location);
tsslEger.Text = "Kliens: " + e.X + ", " + e.Y;
tsslVilag.Text = "Világ: " + v.X.ToString("0.00") + "; " + v.Y.ToString("0.00");
}
private void rpAbra_MouseClick(object sender, MouseEventArgs e)
{
pontok.Add(Vilagba(e.Location));
rpAbra.Invalidate();
}
private void Valtozas(object sender, EventArgs e)
{
if (beallit) { return; }
skala = tbNagyit.Value;
kozepVilag = new PointF((float)nudKozepX.Value, (float)nudKozepY.Value);
rpAbra.Invalidate();
}
private void btnAlap_Click(object sender, EventArgs e)
{
beallit = true;
tbNagyit.Value = 40;
nudKozepX.Value = 0;
nudKozepY.Value = 0;
beallit = false;
Valtozas(this, EventArgs.Empty);
}
private PointF[] Csillag(float kulso, float belso)
{
PointF[] p = new PointF[10];
for (int i = 0; i < 10; i++)
{
double radian = (-90 + i * 36) * Math.PI / 180;
float r = i % 2 == 0 ? kulso : belso;
p[i] = new PointF((float)(r * Math.Cos(radian)), (float)(r * Math.Sin(radian)));
}
return p;
}
private void rpCsillag_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Clear(Color.White);
g.SmoothingMode = SmoothingMode.AntiAlias;
GraphicsState alap = g.Save();
g.TranslateTransform(rpCsillag.Width / 2f, rpCsillag.Height / 2f);
g.RotateTransform(szog);
if (chkNagyit.Checked)
{
float s = 1.15f + 0.15f * (float)Math.Sin(lukt);
g.ScaleTransform(s, s);
}
PointF[] cs = Csillag(80, 35);
using (Brush arany = new SolidBrush(Color.Gold))
using (Pen keret = new Pen(Color.DarkGoldenrod, 2))
{
g.FillPolygon(arany, cs);
g.DrawPolygon(keret, cs);
g.FillEllipse(Brushes.White, -6, -6, 12, 12);
}
g.Restore(alap); // a szöveg már forgatás nélkül
g.DrawString("Szög: " + szog.ToString("0") + "°", this.Font, Brushes.Black, 8, 8);
}
private void tmrForgat_Tick(object sender, EventArgs e)
{
szog = (szog + 2) % 360;
lukt += 0.1f;
rpCsillag.Invalidate();
}Mit tegyél ide: a kész koordináta-felfedező a függvénnyel, a kattintott pontokkal és a forgó csillaggal.Javasolt fájlnév: winforms-9-5-kesz.webp · Alt szöveg: A kész koordináta-felfedező
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. Polárkoordináták
Rajzolj 12 pontot egy kör mentén (óra számlap). Hogyan számolod a pontokat szögből és sugárból? A pont: Egy lehetséges megoldás
(cx + r·cos α, cy + r·sin α); a 12-es állás a −90° (felfelé), a szög pozitív iránya az óramutató járásával megegyező (az Y lefelé nő).for (int i = 0; i < 12; i++)
{
double radian = (i * 30 - 90) * Math.PI / 180;
float x = cx + (float)(r * Math.Cos(radian));
float y = cy + (float)(r * Math.Sin(radian));
g.FillEllipse(Brushes.Black, x - 3, y - 3, 6, 6);
}
2. A kurzor irányába néző nyíl
Egy nyíl az egér felé mutat. Melyik függvénnyel számolod az irányt?Egy lehetséges megoldás
Math.Atan2(dy, dx) a szöget adja radiánban (a negyedekre is ügyel); fokba: * 180 / Math.PI, és ennyivel forgatod a nyilat (RotateTransform).float dx = eger.X - kozep.X;
float dy = eger.Y - kozep.Y;
float fok = (float)(Math.Atan2(dy, dx) * 180 / Math.PI);
g.TranslateTransform(kozep.X, kozep.Y);
g.RotateTransform(fok);
g.DrawLine(nyil, 0, 0, 60, 0); // a nyíl a +X irányba rajzolva, a forgatás irányítja
3. Relatív koordináták
A rajz mérete igazodjon az ablakhoz: a tárolt pontok 0–1 közötti relatív koordináták. Hogyan alakítod pixelekké? A pixel = relatív érték × a panel mérete: Egy lehetséges megoldás
new PointF(r.X * rp.Width, r.Y * rp.Height). Átméretezéskor a rajz arányosan nyúlik (torzulhat, ha az arány megmarad kell, a kisebb oldalt használd skálának).
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. Kerekítés
Mi a Point.Round, Truncate és Ceiling értéke a (2,5; 3,7) PointF pontra?Megoldás megnyitása
Truncate: (2, 3); Ceiling: (3, 4); Round: a .NET alapértelmezett kerekítése szerint a 2,5 a páros egészhez kerekít (2), a 3,7 pedig 4: (2, 4).
2. Melyik koordináta?
Melyik rendszerben van: (a) (a) kliens; (b) képernyő; (c) képernyő (a legfelső ablaknál); (d) világ (a modell).MouseEventArgs.X; (b) Cursor.Position; (c) Form.Location; (d) a játék „7. mező, 3. sor” pozíciója?Megoldás megnyitása
Közepes szint
3. Skála állítása a közép megtartásával
A nagyítás változtatásakor a panel közepe által mutatott világ-pont maradjon a helyén. Mit kell tenned? A Megoldás megnyitása
kozepVilag a közép világ-pontja, tehát nagyításkor nem változik: az átszámítás a középhez kötött. Ha az egér alatti pontot szeretnéd megtartani (egérkerekes zoom), a nagyítás előtt és után kiszámolod az egér világ-pontját, és a különbséggel eltolod a középpontot.
4. Elforgatott pont
Forgasd el a (50, 0) pontot 90 fokkal az origó körül képlettel (transzformáció nélkül). x' = x·cos α − y·sin α, y' = x·sin α + y·cos α. 90°-nál: x' = 0, y' = 50 (a képernyőn lefelé, mert az Y lefelé nő: az óramutató irányába forog).Megoldás megnyitása
private PointF Forgat(PointF p, double fok){ double r = fok * Math.PI / 180; return new PointF((float)(p.X * Math.Cos(r) - p.Y * Math.Sin(r)), (float)(p.X * Math.Sin(r) + p.Y * Math.Cos(r)));}
Kihívás szint
5. Kattintás transzformált rajzon
A rajzot A transzformáció mátrixát (TranslateTransform és RotateTransform forgatja. Hogyan találod meg, melyik alakzatra kattintottak?Megoldás megnyitása
g.Transform) megfordítod (Invert), és a kattintási pontot visszaalakítod a rajz saját koordinátájába (TransformPoints); ott már egyszerű a Contains vizsgálat.private PointF VisszaTranszformal(Point kattintas, float kozepX, float kozepY, float szog)
{
using (Matrix m = new Matrix())
{
m.Translate(kozepX, kozepY); // ugyanaz a sorrend, mint a rajzolásnál
m.Rotate(szog);
m.Invert();
PointF[] p = { kattintas };
m.TransformPoints(p);
return p[0];
}
}
6. Minimap
Egy kis panel a teljes világot mutatja, benne a fő nézet látható téglalapjával. Hogyan számolod a téglalapot? A fő panel bal felső és jobb alsó pontját a Megoldás megnyitása
Vilagba metódussal világ-koordinátára alakítod; ezt a két pontot a minimap saját átszámításával (más skála) képernyőre, és téglalapot rajzolsz belőle (PontokbolRect, 9.4 lecke).
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. A Az eseményt kiváltó vezérlő kliens-koordinátája (az origó a vezérlő belső bal felső sarka). A képernyőn lefelé nő az Y, a matematikában felfelé. Egymás után, az aktuális rendszerre hatnak: előbb eltolás, aztán forgatás. A transzformáció (állapot) elmentése és visszaállítása, hogy a további rajz ne torzuljon. A GDI+ 1. Mi a különbség a Point és a PointF között?
Point egész, a PointF törtszám koordinátájú; a Point.Round alakít.2. Mi az egéresemény koordinátája?
3. Hogyan alakítod át a képernyő- és kliens-koordinátát?
PointToClient és PointToScreen.4. Miért kell megfordítani az Y-t a függvényábrán?
5. Mi a transzformációk sorrendjének szabálya?
6. Mire való a Save és a Restore?
7. Miért veszélyes a nagyon nagy koordináta?
OverflowException kivételt vagy hibás rajzot ad; korlátozni kell, és a végtelen/NaN értékeknél szakítani a vonalat.
Mit tudsz már?
- Különbséget teszel a képernyő-, kliens- és világ-koordináta között, és átszámolod őket.
- Világ–képernyő átszámítást írsz nagyítással, eltolással és Y-fordítással, és fordítva egérkattintáshoz.
- Transzformációkkal forgatsz és nagyítasz (
Save/Restore). - Polárkoordinátákkal számolsz.
- Elkerülöd az egész osztás, a túl nagy koordináta és a megmaradó transzformáció hibáit.
Következő lecke: MouseDown, MouseMove, MouseUp
Egéres rajzolás: a három egéresemény együttműködése, húzás, szabadkézi vonal, gumivonal és az egér rögzítése.



