Grafikus C# – Windows Forms tanfolyam › 4.5 lecke Windows Forms · 4.5 · önálló gyakorlás Eddig a vezérlőelemeket helyeztük el az ablakon. Most te rajzolsz: egy kis programot készítesz, amelyben egérrel lehet vonalat, téglalapot és ellipszist húzni, színt és vastagságot választani, radírozni, visszavonni és a képet PNG fájlba menteni. A rajzoláshoz néhány új fogalom kell (Önálló feladat V.: Egyszerű rajzolóalkalmazás
Bitmap, Graphics, Pen, egéresemények), amelyeket az első szakasz röviden összefoglal, hogy a feladat önállóan is megoldható legyen. A 9. fejezet később mélyebben tárgyalja ezeket.
A feladat végére
- rajzfelületet készítesz
BitmapésPictureBoxsegítségével; - az egéreseményekből (
MouseDown,MouseMove,MouseUp) rajzolást építesz; - a
Graphicsobjektummal vonalat, téglalapot és ellipszist rajzolszPen-nel; - az alakzatokhoz előnézetet adsz húzás közben (
Paintesemény,Invalidate); - a rajzot PNG-be mented, és visszavonást készítesz;
- a rajzolási erőforrásokat (
Pen,Graphics,Bitmap) helyesen szabadítod fel.
Mielőtt elkezded
Ehhez a feladathoz az 1. fejezet vezérlőelemei (RadioButton, TrackBar, PictureBox), a 2.3 lecke (SaveFileDialog) és a 2.5 lecke (ColorDialog) kellenek. A rajzolás új, ezért az első szakasz elegendő háttérismeretet ad. Készíts új, tiszta projektet.
Amit tudnod kell a rajzoláshoz
A Windows Forms rajzolása a GDI+ technológiára épül. A legfontosabb fogalmak:
| Fogalom | Mi ez? | Példa |
|---|---|---|
Bitmap | Egy kép a memóriában, képpontok (pixelek) rácsa. Ez lesz a „vászon”, amelyre rajzolunk. | new Bitmap(600, 400) |
Graphics | A „rajzoló kéz”: egy felület (például egy Bitmap), amelyre alakzatokat rajzolhatsz. | Graphics.FromImage(rajz) |
Pen | A „toll”: a vonal színe és vastagsága. | new Pen(Color.Blue, 3) |
SolidBrush | A „festékes ecset” a kitöltéshez. | new SolidBrush(Color.Red) |
Point, Rectangle | Egy pont (X, Y) és egy téglalap (X, Y, szélesség, magasság). Az origó a bal felső sarok, az Y lefelé nő. | new Rectangle(10, 20, 100, 50) |
| Egéresemények | A MouseDown (lenyomás), MouseMove (mozgás) és MouseUp (felengedés) esemény megkapja az egér helyét (e.Location) és gombját (e.Button). | if (e.Button == MouseButtons.Left) |
Invalidate() és Paint | Az Invalidate() jelzi, hogy a vezérlőt újra kell rajzolni; ekkor fut le a Paint esemény. | pbRajz.Invalidate(); |
private Bitmap rajz;
private void Form1_Load(object sender, EventArgs e)
{
rajz = new Bitmap(pbRajz.Width, pbRajz.Height);
using (Graphics g = Graphics.FromImage(rajz))
{
g.Clear(Color.White);
}
pbRajz.Image = rajz;
}
private void btnProba_Click(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(rajz))
using (Pen kek = new Pen(Color.Blue, 3))
using (Pen zold = new Pen(Color.Green, 2))
{
g.DrawLine(kek, 20, 20, 200, 120);
g.DrawRectangle(zold, 50, 150, 120, 60);
}
pbRajz.Invalidate(); // a PictureBox újrarajzolja magát a megváltozott képpel
}Graphics.FromImage a bitmapre rajzol, ezért a rajz megmarad, akkor is, ha az ablakot lekicsinyítik vagy eltakarják. A PictureBox csak megjeleníti a bitmapot.Graphics, Pen és Brush operációs rendszer erőforrást foglal. A using blokk a végén felszabadítja őket. Ha elmarad, a program memóriát és rajzolási erőforrást veszít.PictureBox alapértelmezett Normal módban a bitmap képpontjai 1:1 arányban jelennek meg, így az egér helye (e.Location) megegyezik a bitmap koordinátájával. Zoom vagy StretchImage módban a koordináták elcsúsznának.PictureBox nem tudja magától, hogy újra kell rajzolnia magát. Az Invalidate() szól neki.Jósolj!
A gombnyomás után a kék vonal a bitmapra került. Lekicsinyíted az ablakot, majd visszanagyítod. Eltűnik-e a vonal? Mi lenne, ha a vonalat a pbRajz.CreateGraphics() metódussal, közvetlenül a PictureBox felületére rajzolnád?
Jóslat ellenőrzése
A bitmapra rajzolt vonal megmarad, mert maga a kép tartalmazza. A CreateGraphics() a vezérlő felületére rajzol, amelyet az operációs rendszer bármikor újrafest (lekicsinyítés, eltakarás után): a vonal ilyenkor eltűnik. Ezért a tartós rajzot mindig a bitmapra tesszük, és a vezérlőre csak az átmeneti dolgokat (például az előnézetet) a Paint eseményben.
A feladat leírása és a pontozás
Készíts Rajzoló nevű Windows Forms alkalmazást. Az ablak közepén egy fehér rajzfelület (PictureBox) áll, fölötte vagy mellette az eszköztár: eszközválasztó (toll, vonal, téglalap, ellipszis, radír), színválasztó gomb a kiválasztott szín előnézetével, vastagságcsúszka, kitöltés jelölőnégyzet, Törlés, Visszavonás és Mentés gomb.
| Rész | Követelmény | Pont |
|---|---|---|
| Alapok (40) | Fehér rajzfelület Bitmap-pal és PictureBox-szal; a rajz megmarad az ablak lekicsinyítése után is. | 10 |
| Szabadkézi rajz (toll) egérrel, folytonos vonallal (a gyors mozgásnál is összefüggő). | 15 | |
Színválasztás ColorDialog-gal (a kiválasztott szín látszik egy panelen) és vastagság TrackBar-ral (1–30). | 15 | |
| Eszközök (30) | Vonal, téglalap és ellipszis húzással, előnézettel húzás közben; az alakzat az egér felengedésekor kerül véglegesen a rajzra. | 15 |
| Radír (fehér vastag toll) és Törlés gomb (a teljes rajz fehérre). | 8 | |
| Kitöltés jelölőnégyzet: téglalap és ellipszis kitöltve vagy csak körvonallal. | 7 | |
| Kezelés (30) | Mentés PNG fájlba SaveFileDialog-gal, hibakezeléssel. | 10 |
| Visszavonás: legalább 10 lépés visszamenőleg. | 10 | |
Az erőforrások felszabadítása (using, Dispose), nincs villogás vagy elcsúszott koordináta. | 10 |
Összesen: 100 pont. Mit tegyél ide: egy papírra rajzolt terv a programról: felül az eszközsor (eszközök, szín, vastagság, kitöltés, gombok), alatta a nagy fehér rajzfelület.Javasolt fájlnév: winforms-4-5-terv.webp · Alt szöveg: A rajzolóalkalmazás elrendezési terve
Időbeosztás (150 perc)
| Perc | Mit csinálj? |
|---|---|
| 0–20 | Olvasd el az első szakaszt, próbáld ki a példakódot (kék vonal), és tervezd meg az ablakot. |
| 20–40 | Építsd fel a felületet: PictureBox (SizeMode: Normal, BorderStyle: FixedSingle), eszközök (RadioButton, Appearance: Button), színválasztó, csúszka, jelölőnégyzet, gombok. |
| 40–70 | Szabadkézi rajz: az egéresemények és a DrawLine, kerek vonalvég, szín és vastagság. |
| 70–105 | Alakzatok: kezdőpont, előnézet a Paint eseményben, véglegesítés az egér felengedésekor, kitöltés. |
| 105–125 | Radír, Törlés és Mentés. |
| 125–140 | Visszavonás. |
| 140–150 | Tesztlista végigpróbálása és javítás. |
Támpontok (kész megoldás nélkül)
Mit tárolj mezőben?
| Mező | Típus | Szerepe |
|---|---|---|
rajz | Bitmap | A vászon, amelyre rajzolsz. |
szin, vastagsag | Color, int | A toll jelenlegi színe és vastagsága. |
eszkoz | string | A kiválasztott eszköz neve ("toll", "vonal", "teglalap", "ellipszis", "radir"). |
rajzol | bool | Igaz, amíg az egér gombja le van nyomva. |
kezdo, elozo, aktualis | Point | A húzás kezdőpontja, az utolsó ismert pont (szabadkézi vonalhoz), és az egér jelenlegi helye (előnézethez). |
elozmenyek | List<Bitmap> | A visszavonáshoz elmentett korábbi állapotok. |
Egy eseménykezelő az eszközökhöz
Az egyes RadioButton Tag tulajdonságába írd az eszköz nevét a tervezőben (Properties → Tag). Mindegyik CheckedChanged eseménye ugyanazt a metódust hívja. A CheckedChanged kétszer fut (a régi eszköz kikapcsolásakor és az újé bekapcsolásakor), ezért csak akkor tedd el az új eszközt, ha rb.Checked igaz.
Szabadkézi vonal folytonosan
Az egér gyors mozgásánál a MouseMove események között nagy a távolság. Ezért ne pontokat rajzolj, hanem vonalat az előző ponttól az újig (DrawLine(toll, elozo, e.Location)), és az új pont lesz az elozo. A vonalvégeket kerekítsd (StartCap, EndCap = LineCap.Round), hogy vastag vonalnál ne legyen töredezett.
Előnézet az alakzatokhoz
Húzás közben az alakzatot ne a bitmapra rajzold (mert minden egérmozdulat egy újabb alakzatot hagyna), hanem a PictureBox Paint eseményében, az e.Graphics objektummal. Az egérmozgásnál csak az aktualis pontot frissítsd, és hívd az Invalidate() metódust. A bitmapra csak az egér felengedésekor (MouseUp) rajzold az alakzatot.
Téglalap fordított irányú húzásnál
Ha az egér balra vagy felfelé húzol a kezdőponttól, a szélesség vagy a magasság negatív lenne, és a DrawRectangle hibát ad. Készíts egy segédmetódust, amely két pontból szabályos Rectangle-t számol: a bal felső sarok a koordináták minimuma, a méret a különbségek abszolút értéke (Math.Min, Math.Abs).
Visszavonás
Minden új művelet (egér lenyomása) előtt tegyél egy másolatot a bitmapról egy listába ((Bitmap)rajz.Clone()). Ha a lista 10-nél hosszabb, dobd el a legrégebbit (Dispose és RemoveAt(0)). Visszavonáskor az utolsó másolat lesz az új rajz, és a PictureBox Image tulajdonságát is át kell állítani. A régi bitmapot szabadítsd fel.
Mintamegoldás
Előbb oldd meg önállóan!
A mintamegoldás csak az ellenőrzésre való, és nem az egyetlen helyes megoldás. Előbb próbálkozz saját kóddal, és csak elakadásnál vagy a végén nyisd meg.
Megoldás: a felület és a nevek
| Vezérlő | (Name) | Beállítás |
|---|---|---|
| Form | Form1 | FormBorderStyle: FixedSingle; MaximizeBox: False; Text: Rajzoló |
| PictureBox | pbRajz | Size: 760 × 440; SizeMode: Normal; BorderStyle: FixedSingle; BackColor: White |
| RadioButton ×5 | rbToll, rbVonal, rbTeglalap, rbEllipszis, rbRadir | Appearance: Button; Tag: toll, vonal, teglalap, ellipszis, radir; rbToll: Checked; mind a Eszkoz_CheckedChanged metódust hívja |
| Button, Panel | btnSzin, pnlSzin | pnlSzin: BackColor: Black, BorderStyle: FixedSingle |
| TrackBar, Label | tbVastagsag, lblVastagsag | Minimum: 1; Maximum: 30; Value: 3 |
| CheckBox | chkKitolt | Text: Kitöltés |
| Button ×3 | btnTorles, btnVissza, btnMentes | Text: Törlés, Visszavonás, Mentés |
| ColorDialog, SaveFileDialog | cdSzin, sfdMentes | sfdMentes: Filter: PNG kép (*.png)|*.png |
Megoldás: Form1.cs (teljes kód)
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace Rajzolo
{
public partial class Form1 : Form
{
private Bitmap rajz;
private Color szin = Color.Black;
private int vastagsag = 3;
private string eszkoz = "toll";
private bool rajzol = false;
private Point kezdo;
private Point elozo;
private Point aktualis;
private readonly List<Bitmap> elozmenyek = new List<Bitmap>();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
rajz = new Bitmap(pbRajz.Width, pbRajz.Height);
VaszonTorles();
pbRajz.Image = rajz;
lblVastagsag.Text = "Vastagság: " + vastagsag;
}
private void VaszonTorles()
{
using (Graphics g = Graphics.FromImage(rajz))
{
g.Clear(Color.White);
}
}
// ---- segédmetódusok ----
private static Rectangle Teglalap(Point a, Point b)
{
return new Rectangle(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Abs(a.X - b.X), Math.Abs(a.Y - b.Y));
}
private void Alakzat(Graphics g, Point a, Point b)
{
g.SmoothingMode = SmoothingMode.AntiAlias;
using (Pen toll = new Pen(szin, vastagsag))
{
if (eszkoz == "vonal")
{
toll.StartCap = LineCap.Round;
toll.EndCap = LineCap.Round;
g.DrawLine(toll, a, b);
}
else if (eszkoz == "teglalap")
{
Rectangle r = Teglalap(a, b);
if (chkKitolt.Checked)
{
using (SolidBrush ecset = new SolidBrush(szin))
{
g.FillRectangle(ecset, r);
}
}
else
{
g.DrawRectangle(toll, r);
}
}
else if (eszkoz == "ellipszis")
{
Rectangle r = Teglalap(a, b);
if (chkKitolt.Checked)
{
using (SolidBrush ecset = new SolidBrush(szin))
{
g.FillEllipse(ecset, r);
}
}
else
{
g.DrawEllipse(toll, r);
}
}
}
}
private bool AlakzatEszkoz()
{
return eszkoz == "vonal" || eszkoz == "teglalap" || eszkoz == "ellipszis";
}
private void ElozmenyMentese()
{
elozmenyek.Add((Bitmap)rajz.Clone());
if (elozmenyek.Count > 10)
{
elozmenyek[0].Dispose();
elozmenyek.RemoveAt(0);
}
}
// ---- eszközök és beállítások ----
private void Eszkoz_CheckedChanged(object sender, EventArgs e)
{
RadioButton rb = (RadioButton)sender;
if (rb.Checked)
{
eszkoz = rb.Tag.ToString();
}
}
private void btnSzin_Click(object sender, EventArgs e)
{
cdSzin.Color = szin;
if (cdSzin.ShowDialog() == DialogResult.OK)
{
szin = cdSzin.Color;
pnlSzin.BackColor = szin;
}
}
private void tbVastagsag_Scroll(object sender, EventArgs e)
{
vastagsag = tbVastagsag.Value;
lblVastagsag.Text = "Vastagság: " + vastagsag;
}
// ---- egér ----
private void pbRajz_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
{
return;
}
ElozmenyMentese();
rajzol = true;
kezdo = e.Location;
elozo = e.Location;
aktualis = e.Location;
}
private void pbRajz_MouseMove(object sender, MouseEventArgs e)
{
if (!rajzol)
{
return;
}
aktualis = e.Location;
if (AlakzatEszkoz())
{
pbRajz.Invalidate(); // az előnézetet a Paint rajzolja
return;
}
Color tollSzin = eszkoz == "radir" ? Color.White : szin;
using (Graphics g = Graphics.FromImage(rajz))
using (Pen toll = new Pen(tollSzin, vastagsag))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
toll.StartCap = LineCap.Round;
toll.EndCap = LineCap.Round;
g.DrawLine(toll, elozo, e.Location);
}
elozo = e.Location;
pbRajz.Invalidate();
}
private void pbRajz_MouseUp(object sender, MouseEventArgs e)
{
if (!rajzol)
{
return;
}
rajzol = false;
if (AlakzatEszkoz())
{
using (Graphics g = Graphics.FromImage(rajz))
{
Alakzat(g, kezdo, e.Location);
}
}
pbRajz.Invalidate();
}
private void pbRajz_Paint(object sender, PaintEventArgs e)
{
if (rajzol && AlakzatEszkoz())
{
Alakzat(e.Graphics, kezdo, aktualis); // előnézet, nem kerül a bitmapra
}
}
// ---- gombok ----
private void btnTorles_Click(object sender, EventArgs e)
{
ElozmenyMentese();
VaszonTorles();
pbRajz.Invalidate();
}
private void btnVissza_Click(object sender, EventArgs e)
{
if (elozmenyek.Count == 0)
{
return;
}
Bitmap utolso = elozmenyek[elozmenyek.Count - 1];
elozmenyek.RemoveAt(elozmenyek.Count - 1);
Bitmap regi = rajz;
rajz = utolso;
pbRajz.Image = rajz;
regi.Dispose();
}
private void btnMentes_Click(object sender, EventArgs e)
{
sfdMentes.FileName = "rajz";
if (sfdMentes.ShowDialog() != DialogResult.OK)
{
return;
}
try
{
rajz.Save(sfdMentes.FileName, ImageFormat.Png);
MessageBox.Show("A rajz mentve.");
}
catch (Exception ex)
{
MessageBox.Show("A mentés nem sikerült: " + ex.Message);
}
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
foreach (Bitmap b in elozmenyek)
{
b.Dispose();
}
rajz.Dispose();
}
}
}Megoldás: miért így épül fel?
A tartós rajz a rajz bitmapon él, az átmeneti előnézet a Paint eseményben jelenik meg, és nem kerül a bitmapra. A szabadkézi vonal minden MouseMove eseménynél az előző pontot köti össze az újjal, ezért folytonos. Az alakzatokat az egér felengedésekor rögzítjük. A Teglalap segédmetódus a fordított húzást is kezeli. A visszavonás a Clone() által készített másolatokat használja, a régi bitmapot pedig felszabadítja. A Graphics, a Pen és a SolidBrush mindig using blokkban él.
Tesztlista
| # | Teszt | Várt eredmény |
|---|---|---|
| 1 | A program indul. | Fehér rajzfelület, a toll az alapértelmezett eszköz. |
| 2 | Szabadkézi rajz lassan és nagyon gyorsan. | Folytonos vonal, nincsenek szakadások. |
| 3 | Szín- és vastagságváltoztatás. | Az új vonal az új színnel és vastagsággal jelenik meg, a korábbi vonalak nem változnak. |
| 4 | Vonal húzása. | Húzás közben előnézet látszik, felengedéskor a vonal a rajzon marad. |
| 5 | Téglalap húzása jobbra-le, majd balra-fel. | Mindkét irányban hibátlan, nincs kivétel. |
| 6 | Ellipszis kitöltéssel és anélkül. | A jelölőnégyzet szerint kitöltött vagy körvonalas. |
| 7 | Radír. | A rajz fehérre törlődik azon a helyen. |
| 8 | Az ablak lekicsinyítése és visszanagyítása. | A rajz megmarad. |
| 9 | Visszavonás 3-szor, majd 11 lépés után is. | A rajz lépésenként visszalép; 10 lépésnél nem megy tovább, nincs hiba. |
| 10 | Törlés, majd visszavonás. | A törölt rajz visszajön. |
| 11 | Mentés PNG fájlba, majd megnyitás képnézegetővel. | A kép megegyezik a rajzzal, a háttér fehér. |
| 12 | Mentés nem létező mappába vagy írásvédett helyre. | Érthető hibaüzenet, a program nem omlik össze. |
| 13 | Jobb egérgomb a rajzfelületen. | Nem rajzol, nem hiba. |
| 14 | Egér kihúzása a rajzfelületről húzás közben. | A program nem omlik össze. |
Gyakori hibák és önértékelés
Ezeken szoktak elcsúszni
Gondold végig, nálad előfordul-e valamelyik.
1. A rajz eltűnik, ha az ablakot eltakarják
Graphics g = pbRajz.CreateGraphics();
g.DrawLine(toll, elozo, e.Location);Hiba és javítás
A CreateGraphics() közvetlenül a vezérlő felületére rajzol, amelyet a rendszer újrafest, és a rajz elveszik. Rajzolj a Bitmap-ra (Graphics.FromImage), a PictureBox pedig jelenítse meg a képet.
2. A vonal eltér az egér helyétől
A rajzolt vonal nem ott jelenik meg, ahol az egér van. A Ok és javítás
PictureBox SizeMode tulajdonsága Zoom vagy StretchImage, ezért a kép méretezve látszik, és az egér koordinátája nem egyezik a bitmap koordinátájával. Állítsd Normal értékre, és a bitmap mérete egyezzen a PictureBox méretével.
3. Kivétel a téglalap húzásakor
g.DrawRectangle(toll, kezdo.X, kezdo.Y, aktualis.X - kezdo.X, aktualis.Y - kezdo.Y);Hiba és javítás
Ha az egér balra vagy felfelé húzol, a szélesség vagy a magasság negatív, és a rajzolás hibát jelez, vagy semmit sem rajzol. Számold ki a szabályos téglalapot: bal felső sarok = a koordináták minimuma, méret = a különbségek abszolút értéke.
4. Fogy a memória, a program lassul
Hosszabb rajzolás után a program lassú, és a Feladatkezelőben nő a memóriahasználata. A Ok és javítás
Pen, a Graphics és a Bitmap objektumokat nem szabadítottad fel. Használj using blokkot, és a visszavonás régi bitmapjait, valamint az ablak bezárásakor az összes bitmapot dobd el (Dispose).
5. Az eszköz kétszer vált
Eszközváltáskor a program néha a régi eszközt használja. A Ok és javítás
RadioButton.CheckedChanged esemény kétszer fut: az egyik gombnál kikapcsolásakor, a másiknál bekapcsolásakor. Ha nem vizsgálod a Checked értéket, a kikapcsolt gomb is beállítja az eszközt, és az utolsó futás dönt. Ellenőrizd: if (rb.Checked).
6. A mentés GDI+ hibát ad
// ExternalException: A generic error occurred in GDI+.
rajz.Save(utvonal, ImageFormat.Png);Hiba és javítás
Ez az általános GDI+ hiba gyakran azt jelenti, hogy az útvonal érvénytelen vagy nem írható (nincs jogosultság, nem létező mappa), vagy a fájlt egy másik program használja. Védd try–catch blokkal, és adj érthető üzenetet. Ha a képet ugyanabból a fájlból töltötted be, amelybe menteni akarsz, a betöltő bitmap zárolja a fájlt: új fájlnévre ments.
Az értékeléshez ellenőrizd az alábbi szempontokat is:
Graphics, Pen, Brush és Bitmap felszabadul.Bővítési ötletek, ha kész vagy
Ha maradt időd, próbáld ki:
- Pipetta: egy eszköz, amely a rajzon kattintott pont színét átveszi (
GetPixel). - Szaggatott vonal: a
Pen.DashStyletulajdonsággal, legördülő listából. - Tükrözés: a szabadkézi vonal tükörképét is megrajzolod a vászon függőleges tengelye mentén.
- Kép megnyitása: egy meglévő képre is rajzolhass (
OpenFileDialog). - Nagyítás: a kép nagyítása a
TrackBarsegítségével (haladó: a koordináták átszámolásával).
Gyakorlótár: 6 további feladat
Három szinten gyakorolhatsz: alap, közepes és kihívás. A feladatokhoz a kész rajzoló szükséges. Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. Színek gyorsgombjai
Három kis A három panel Panel (piros, kék, zöld) felett kattintva a rajzolószín az adott szín legyen, és a pnlSzin mutassa az új színt. Hogyan oldod meg három külön kód nélkül?Megoldás megnyitása
Click eseménye ugyanazt a metódust hívja. A sender a megkattintott panel, a BackColor tulajdonsága adja a színt.private void GyorsSzin_Click(object sender, EventArgs e)
{
Panel panel = (Panel)sender;
szin = panel.BackColor;
pnlSzin.BackColor = szin;
}
2. A rajzfelület háttérszíne
Egy Háttér gomb színválasztás után fesse be a teljes rajzfelületet a kiválasztott színnel (az eddigi rajz elvész, de visszavonható). A Megoldás megnyitása
Graphics.Clear(szín) az egész felületet az adott színnel tölti ki. A művelet előtt elmentjük az állapotot, hogy visszavonható legyen. A radír színét is érdemes ehhez igazítani (egy hatterSzin mezőben tárolva).private Color hatterSzin = Color.White;
private void btnHatter_Click(object sender, EventArgs e)
{
if (cdSzin.ShowDialog() != DialogResult.OK)
{
return;
}
ElozmenyMentese();
hatterSzin = cdSzin.Color;
using (Graphics g = Graphics.FromImage(rajz))
{
g.Clear(hatterSzin);
}
pbRajz.Invalidate();
}
// a radírnál: Color tollSzin = eszkoz == "radir" ? hatterSzin : szin;
Közepes szint
3. Pipetta
Egy Pipetta eszközzel ( A rbPipetta, Tag: pipetta) egy kattintással a rajzon lévő pont színét vedd át aktuális színnek, majd térj vissza a tollhoz.Megoldás megnyitása
Bitmap.GetPixel(x, y) egy képpont színét adja. A koordináta a bitmapon belül kell legyen, ezért ellenőrizzük. A pipettával kattintva nem kezdünk rajzolást: a MouseDown elején kezeljük, és kilépünk.if (eszkoz == "pipetta")
{
if (e.X >= 0 && e.Y >= 0 && e.X < rajz.Width && e.Y < rajz.Height)
{
szin = rajz.GetPixel(e.X, e.Y);
pnlSzin.BackColor = szin;
}
rbToll.Checked = true; // vissza a tollhoz
return;
}
4. Tükrözés
Egy A tükörkép chkTukor jelölőnégyzet bekapcsolt állapotában a szabadkézi vonal tükörképe is megjelenik a vászon függőleges tengelye mentén (mandala-hatás).Megoldás megnyitása
x koordinátája szélesség - 1 - x, az y változatlan. Mindkét szakaszt (az eredetit és a tükörképet) ugyanazzal a tollal rajzoljuk.g.DrawLine(toll, elozo, e.Location);
if (chkTukor.Checked)
{
int w = rajz.Width - 1;
g.DrawLine(toll, new Point(w - elozo.X, elozo.Y), new Point(w - e.X, e.Y));
}
Kihívás szint
5. Vonalstílus
Egy A cmbStilus lista (Folytonos, Szaggatott, Pontozott) szabja meg a vonal stílusát a vonal, téglalap és ellipszis eszközöknél (előnézetben is).Megoldás megnyitása
Pen.DashStyle tulajdonság felsorolás típusú (DashStyle.Solid, Dash, Dot), a System.Drawing.Drawing2D névtérből. A listaelem sorszámából választjuk ki. Az Alakzat metódus a tollat létrehozza, ezért ott állítjuk be, így az előnézet és a végleges rajz is ugyanazt használja.// az Alakzat metódusban, a Pen létrehozása után:
using (Pen toll = new Pen(szin, vastagsag))
{
if (cmbStilus.SelectedIndex == 1)
{
toll.DashStyle = DashStyle.Dash;
}
else if (cmbStilus.SelectedIndex == 2)
{
toll.DashStyle = DashStyle.Dot;
}
// ... a rajzolás változatlan ...
}
6. Kép megnyitása és rárajzolás
Egy Megnyitás gomb töltsön be egy képfájlt (PNG, JPG) a rajzfelületre (méretezve, a képarányt megtartva), és lehessen rárajzolni. A betöltés után a kép fájlja ne maradjon zárolva. A Megoldás megnyitása
new Bitmap(fájl) a fájlt addig zárolja, amíg a bitmap él. Ezért a betöltött képet csak átmásoljuk a vászonra (DrawImage), és a betöltött bitmapot a using blokk azonnal felszabadítja. A méretezés a képarányt tartja: a kisebbik skálát (Math.Min) használjuk.private void btnMegnyit_Click(object sender, EventArgs e)
{
ofdKep.Filter = "Képek (*.png;*.jpg;*.jpeg)|*.png;*.jpg;*.jpeg";
if (ofdKep.ShowDialog() != DialogResult.OK)
{
return;
}
try
{
ElozmenyMentese();
using (Bitmap kep = new Bitmap(ofdKep.FileName))
using (Graphics g = Graphics.FromImage(rajz))
{
g.Clear(Color.White);
double arany = Math.Min((double)rajz.Width / kep.Width, (double)rajz.Height / kep.Height);
int w = (int)(kep.Width * arany);
int h = (int)(kep.Height * arany);
g.DrawImage(kep, (rajz.Width - w) / 2, (rajz.Height - h) / 2, w, h);
}
pbRajz.Invalidate();
}
catch (Exception ex)
{
MessageBox.Show("A kép nem nyitható meg: " + ex.Message);
}
}
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. A bitmap tartalma megmarad. A vezérlő felületére ( Jelzi, hogy a vezérlő tartalma megváltozott, és újra kell rajzolni. Ekkor fut a Az előző pont és az új pont közé vonalat húzunk ( Hogy az előnézet ne kerüljön a bitmapra: minden egérmozdulat felülírná a korábbi előnézetet, és nem maradna szemét a rajzon. Csak az egér felengedésekor kerül az alakzat a bitmapra. Hogy a bal felső sarok és a pozitív méret akkor is helyes legyen, ha az egér balra vagy felfelé húzol a kezdőponttól. Operációs rendszer erőforrást foglalnak. A 1. Miért a Bitmapra rajzolunk, és nem közvetlenül a PictureBoxra?
CreateGraphics) rajzolt tartalom elveszik, amikor a rendszer újrafesti a vezérlőt.2. Mire való az Invalidate()?
Paint esemény, és a PictureBox frissíti a megjelenített képet.3. Hogyan lesz folytonos a szabadkézi vonal?
DrawLine), és az új pont lesz az előző. Egyes pontok rajzolása gyors mozgásnál szakadásokat adna.4. Miért kell az alakzat előnézetét a Paint eseményben rajzolni?
5. Miért kell a téglalapnál a Math.Min és Math.Abs?
6. Miért kell a Pen és a Graphics felszabadítása?
using blokk (Dispose) a végén felszabadítja őket, különben a program memóriát és rajzolási erőforrást veszít.
Mit tudsz már?
- Bitmapra rajzolsz, és a PictureBoxban megjeleníted.
- Az egéresemények alapján szabadkézi vonalat és alakzatokat építesz.
- Előnézetet adsz a Paint eseménnyel.
- Visszavonást és mentést készítesz, az erőforrásokat helyesen kezeled.
- Tesztterv alapján ellenőrzöd a munkád.
Következő feladat: címjegyzék vagy teendőlista
A rajzolóprogram kész. A következő önálló feladatban egy adatkezelő programot építesz: egy címjegyzéket vagy egy teendőlistát, adatbázissal vagy fájllal.



