Grafikus C# – Windows Forms tanfolyam › 1.8 lecke

Windows Forms · 1.8 lecke · önálló gyakorlás

ProgressBar és Timer – a felület ne fagyjon be

A folyamatjelző csík (ProgressBar) azt mutatja, hol tart egy művelet. A kézenfekvő megoldás, a ciklus és a várakozás azonban lefagyasztja az ablakot. Ebben a leckében megnézzük, miért, és megtanuljuk a helyes megoldást: az időzítőt (Timer).

  • kb. 60 perc
  • kezdő szint
  • folyamatjelző és időzítő
Diáklány C# programot készít laptopon

A lecke végére

  • a ProgressBar Minimum, Maximum és Value tulajdonságát használod;
  • érted, miért fagyaszt be egy ablakot a ciklus és a Thread.Sleep;
  • a Timer komponenst Interval, Start(), Stop() és Tick segítségével használod;
  • az Increment() metódussal biztonságosan növeled a folyamatjelzőt;
  • megállítható, újraindítható folyamatot készítesz;
  • a gombot a futás idejére letiltod, utána visszakapcsolod.

Mielőtt elkezded

Ehhez a leckéhez kell az 1.7 lecke és az if–else ismerete. A for ciklust a lecke közben, egy kísérletnél egyszer használjuk, magyarázattal.

1.7 MenuStrip →

A felület és a ProgressBar

Tegyél az ablakra négy elemet:

pgbFolyamatEgy ProgressBar (folyamatjelző). Az előtag pgb.
btnInditasEgy gomb, a Text: Indítás.
lblAllapotEgy Label, a Text: Készen áll.
tmrFolyamatEgy Timer komponens (Toolbox, Components csoport). Nem látható: az ablak alatti komponenssávban jelenik meg. Az előtag tmr.

A ProgressBar legfontosabb tulajdonságai:

Minimum és MaximumA tartomány kezdete és vége. Állítsd 0 és 100 közé.
ValueAz aktuális érték (egész szám). Ez határozza meg, mennyi a csík kitöltött része.
Increment(n)Az értéket n-nel növeli, és ha túllépné a Maximumot, megáll a Maximumnál.
StyleBlocks (blokkos), Continuous (folytonos), Marquee (végtelenített, nem mutat százalékot).
📷 Képernyőkép helye

Mit tegyél ide: a tervezőnézet a ProgressBarral, az Indítás gombbal és a felirattal. Az ablak alatt a komponenssávban a tmrFolyamat Timer látszik.Javasolt fájlnév: winforms-1-8-felulet.webp · Alt szöveg: Az ablak ProgressBarral, gombbal és a Timer komponenssel a tervezőben

Kísérlet: a ciklus lefagyasztja az ablakot

Először nézzük meg, mi történik a kézenfekvő, de hibás megoldással. A for ciklus ismétli a blokkot: itt a számláló 0-tól 100-ig megy, és minden körben egy kicsit vár a program (Thread.Sleep, milliszekundumban). A fájl elejére add hozzá a using System.Threading; sort.

Hibás megoldás, csak kísérletnekC#
private void btnInditas_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 100; i++)
{
pgbFolyamat.Value = i;
Thread.Sleep(50);
}
}

Jósolj, majd próbáld ki!

Mit gondolsz: a gombra kattintás után a csík szépen, fokozatosan telik meg? Mozgatható közben az ablak?

Mi történik valójában?

A csík nem telik meg fokozatosan: az ablak körülbelül öt másodpercre befagy (a címsorban „Nem válaszol” jelenhet meg), és csak a ciklus végén frissül egyszerre. Az ablak közben nem mozgatható. Ennek az az oka, hogy ugyanaz a szál rajzolja újra az ablakot, amelyik a ciklust futtatja. Amíg a ciklus fut, az ablak nem tud frissülni.

Miért baj ez?

A felhasználó azt hiszi, hogy a program lefagyott. A hosszú, várakozást vagy sok számolást végző kódnak ezért nem szabad az eseménykezelőben egyetlen ciklusban futnia. Később, a 11. fejezetben tanuljuk meg a háttérben futó műveleteket. Most egy egyszerűbb megoldás következik.

A Timer: egy lépés egy időzítésnél

A Timer komponens meghatározott időközönként kiváltja a Tick eseményt. Ahelyett, hogy egy ciklus várakozna, a Timer minden időzítéskor csak egyetlen kis lépést végrehajt, és visszaadja a vezérlést az ablaknak, amely közben frissül és mozgatható marad.

IntervalAz időköz milliszekundumban. Az 1000 egy másodperc. Állítsd 50 vagy 100 értékre.
Start()Elindítja az időzítőt: ettől kezdve a Tick eseményt időnként kiváltja.
Stop()Leállítja az időzítőt.
TickAz esemény, amely minden időköz letelte után lefut. Kattints duplán a Timerre a komponenssávban, és létrejön az eseménykezelője.
Form1.csC#
private void btnInditas_Click(object sender, EventArgs e)
{
pgbFolyamat.Value = 0;
lblAllapot.Text = "Folyamatban...";
tmrFolyamat.Start();
}

private void tmrFolyamat_Tick(object sender, EventArgs e)
{
if (pgbFolyamat.Value < pgbFolyamat.Maximum)
{
pgbFolyamat.Increment(2);
lblAllapot.Text = $"Folyamatban: {pgbFolyamat.Value}%";
}
else
{
tmrFolyamat.Stop();
lblAllapot.Text = "Kész!";
}
}
btnInditas_ClickNullázza a csíkot, kiírja az állapotot, és elindítja az időzítőt.
tmrFolyamat_TickMinden időköznél egy lépést tesz: növeli a csíkot és frissíti a feliratot.
Increment(2)Kettővel növeli a csík értékét. Ha túllépne a maximumon, megáll a 100-nál.
else ágHa a csík megtelt, leállítja az időzítőt, és kiírja, hogy kész.

Jósolj!

Az Interval értéke 100, az Increment értéke 2. Körülbelül mennyi ideig tart, amíg a csík 0-ról 100-ra tölt?

Jóslat ellenőrzése

A 100-at 2-es lépésekkel 50 lépésben éri el, és mindegyik lépés 100 ezredmásodperc, vagyis 0,1 másodperc: ez összesen kb. 5 másodperc. Az ablak közben mozgatható és reagál.

📷 Képernyőkép helye

Mit tegyél ide: két kép a futó programról: félig kitöltött csík a „Folyamatban: 48%” felirattal, majd a teljes csík a „Kész!” felirattal.Javasolt fájlnév: winforms-1-8-folyamatban.webp · Alt szöveg: A folyamatjelző félig, majd teljesen kitöltve

Leállítás, újraindítás és a gomb letiltása

Ha a folyamat közben a felhasználó újra rákattint az Indítás gombra, az újraindítja a csíkot. Ez zavaró lehet. Két megoldás van: egy Leállítás gomb, és az Indítás letiltása futás közben:

Form1.csC#
private void btnInditas_Click(object sender, EventArgs e)
{
pgbFolyamat.Value = 0;
lblAllapot.Text = "Folyamatban...";
btnInditas.Enabled = false;
tmrFolyamat.Start();
}

private void btnLeallitas_Click(object sender, EventArgs e)
{
tmrFolyamat.Stop();
lblAllapot.Text = "Megállítva.";
btnInditas.Enabled = true;
}

private void tmrFolyamat_Tick(object sender, EventArgs e)
{
if (pgbFolyamat.Value < pgbFolyamat.Maximum)
{
pgbFolyamat.Increment(2);
lblAllapot.Text = $"Folyamatban: {pgbFolyamat.Value}%";
}
else
{
tmrFolyamat.Stop();
lblAllapot.Text = "Kész!";
btnInditas.Enabled = true;
}
}
btnInditas.Enabled = falseA gomb szürke lesz, és nem lehet rákattintani.
Enabled = trueA folyamat végén vagy leállításkor visszakapcsoljuk.
btnLeallitasEgy új gomb Leállítás felirattal, amely megállítja a Timert. A csík ott marad, ahol tartott.

Szünet és folytatás

Ha a Leállítás gomb csak a Stop() metódust hívja, a Start() hívás ott folytatja, ahol a csík tartott (ha nem nullázod az értéket). Így egy Szünet és egy Folytatás gombot is könnyű készíteni.

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. Befagy az ablak

A gombnyomás után az ablak nem reagál, a csík csak a végén telik meg.

Ok és javítás

A for ciklus és a Thread.Sleep az ablakot kezelő szálat várakoztatja. Ismétlődő felületfrissítéshez Timert használj, amely minden Tick-nél csak egy kis lépést tesz.

2. Nem indul el a csík

A gombra kattintasz, de a csík nem mozdul.

Ok és javítás

Lehetséges okok: nem hívtad meg a tmrFolyamat.Start() metódust, vagy a Tick eseménykezelő nincs összekötve a Timerrel (kézzel gépelted be). Kattints duplán a Timerre a komponenssávban, hogy létrejöjjön az összekötött eseménykezelő.

3. Túl nagy érték

pgbFolyamat.Value = pgbFolyamat.Value + 5;
// az érték 98-ról 103-ra lépne, a Maximum 100
Hiba és javítás

Futás közbeni hiba (ArgumentOutOfRangeException): a Value nem lehet nagyobb a Maximum értéknél. Használd az Increment() metódust, amely a maximumnál megáll: pgbFolyamat.Increment(5);

4. Soha nem áll le

A csík megtelik, de az időzítő tovább fut, és a felirat sem változik „Kész!” szövegre.

Ok és javítás

Nincs meghívva a tmrFolyamat.Stop() a Tick eseménykezelő else ágában, vagy az if feltétel rossz (például <= helyett < kell). A leállítás nélkül az időzítő akkor is dolgozik, amikor már nincs mit csinálni.

5. Ismétlődő kattintás

A folyamat közben többször rákattintasz az Indítás gombra, és a csík újra és újra elölről indul.

Ok és javítás

Az Indítás gomb futás közben is használható. Tiltsd le a btnInditas.Enabled = false; sorral, és kapcsold vissza, amikor a folyamat véget ér vagy leáll.

Mini projekt: letöltés-szimulátor

Készíts egy „letöltés” programot:

  1. ProgressBar (0–100), Indítás és Leállítás gomb, állapotfelirat, és egy Timer (Interval: 50).
  2. Az Indítás nullázza a csíkot, letiltja magát, és elindítja a Timert.
  3. A Tick 2-vel növeli a csíkot, és kiírja a százalékot.
  4. A csík megtelte után a program kiírja: A letöltés kész!, leállítja a Timert, és visszakapcsolja az Indítás gombot.
  5. A Leállítás gomb megállítja a folyamatot, és kiírja: Megszakítva.

A teljes kód a 4. szakaszban látható. Próbáld ki mozgatás közben is az ablakot: a csík fut, az ablak pedig nem fagy be.

📷 Képernyőkép helye

Mit tegyél ide: a kész letöltés-szimulátor futás közben: félig kitöltött csík, „Folyamatban: 62%” felirat, szürke (letiltott) Indítás gomb és aktív Leállítás gomb.Javasolt fájlnév: winforms-1-8-letoltes.webp · Alt szöveg: A letöltés-szimulátor futás közben, letiltott Indítás gombbal

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. Gyorsabb folyamat

Növeld a csíkot Tick-enként 5-tel. Mi történik a vége felé?

Egy lehetséges megoldás
pgbFolyamat.Increment(5);

Az Increment a maximumnál megáll, ezért nem kell külön védened a túllépést. A pgbFolyamat.Value = pgbFolyamat.Value + 5 hibát okozna.

2. Százalék a címkén

Minden Tick után írd ki a százalékot, például 37%.

Egy lehetséges megoldás
lblAllapot.Text = pgbFolyamat.Value + "%";

3. Szünet és folytatás

Készíts egy Szünet és egy Folytatás gombot, amelyek megállítják, illetve ott folytatják a folyamatot, ahol tartott.

Egy lehetséges megoldás
private void btnSzunet_Click(object sender, EventArgs e)
{
tmrFolyamat.Stop();
lblAllapot.Text = "Szünet";
}

private void btnFolytatas_Click(object sender, EventArgs e)
{
tmrFolyamat.Start();
}

A Folytatás nem nullázza a csíkot, ezért ott folytatódik, ahol megállt.

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. A Timer vezérlőt a Toolbox Components csoportjából húzd az ablakra (a komponenssávba kerül). Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Másodpercszámláló

Egy tmrFolyamat Timer (Interval: 1000) másodpercenként növeljen egy számlálót, amelyet a lblSzam felirat mutat. A btnStart gomb indítsa a Timert.

Megoldás megnyitása

A számláló értékét a metódusok között is meg kell őrizni, ezért mezőben (a metóduson kívül) tároljuk. A Tick esemény az Interval eltelte után fut le.

Form1.csC#
private int szamlalo = 0;

private void btnStart_Click(object sender, EventArgs e)
{
tmrFolyamat.Start();
}

private void tmrFolyamat_Tick(object sender, EventArgs e)
{
szamlalo = szamlalo + 1;
lblSzam.Text = szamlalo.ToString();
}

2. Léptetés gombbal

A btnLeptet gomb minden megnyomásra 10-zel növelje a pgbFolyamat ProgressBar értékét. A maximumnál (100) ne lépje túl.

Megoldás megnyitása

A Value nem lehet nagyobb a Maximum-nál, különben futás közben hibát kapsz. Ezért a növelés előtt ellenőrizzük a határt.

Form1.csC#
private void btnLeptet_Click(object sender, EventArgs e)
{
if (pgbFolyamat.Value + 10 <= pgbFolyamat.Maximum)
{
pgbFolyamat.Value = pgbFolyamat.Value + 10;
}
else
{
pgbFolyamat.Value = pgbFolyamat.Maximum;
}
}

Közepes szint

3. Töltősáv indítással és leállítással

A Start gomb indítsa el a Timert (Interval: 200), amely 5-tel növeli a pgbFolyamat értékét. A 100 elérésekor álljon le, és jelenjen meg: Kész! A Stop gomb szakítsa meg.

Megoldás megnyitása

A Stop() megállítja a Timert. Az ellenőrzést a Tick eseményben végezzük, mert ott derül ki, hogy elértük-e a végét.

Form1.csC#
private void btnStart_Click(object sender, EventArgs e)
{
pgbFolyamat.Value = 0;
lblAllapot.Text = "Folyamatban...";
tmrFolyamat.Start();
}

private void btnStop_Click(object sender, EventArgs e)
{
tmrFolyamat.Stop();
lblAllapot.Text = "Megszakítva.";
}

private void tmrFolyamat_Tick(object sender, EventArgs e)
{
if (pgbFolyamat.Value < pgbFolyamat.Maximum)
{
pgbFolyamat.Value = Math.Min(pgbFolyamat.Value + 5, pgbFolyamat.Maximum);
}

if (pgbFolyamat.Value >= pgbFolyamat.Maximum)
{
tmrFolyamat.Stop();
lblAllapot.Text = "Kész!";
}
}

4. Visszaszámláló

A gomb indítson egy 10 másodperces visszaszámlálást (Interval: 1000). A lblSzam mutassa a hátralévő másodperceket, és a 0 elérésekor jelenjen meg: Lejárt az idő!

Megoldás megnyitása

A hátralévő időt mezőben tároljuk. Indításkor 10-re állítjuk, a Tick minden másodpercben csökkenti. Nullánál megállítjuk a Timert, és csak ekkor jelzünk.

Form1.csC#
private int hatralevo = 10;

private void btnIndit_Click(object sender, EventArgs e)
{
hatralevo = 10;
lblSzam.Text = hatralevo.ToString();
tmrFolyamat.Start();
}

private void tmrFolyamat_Tick(object sender, EventArgs e)
{
hatralevo = hatralevo - 1;
lblSzam.Text = hatralevo.ToString();

if (hatralevo == 0)
{
tmrFolyamat.Stop();
MessageBox.Show("Lejárt az idő!");
}
}

Kihívás szint

5. Tizedmásodperces stopper

Készíts stoppert Start, Stop és Nulláz gombbal. A Timer Interval értéke 100, az idő formátuma 12.3 mp.

Megoldás megnyitása

A tizedmásodperceket számoljuk (egész számként). A másodperc az osztás hányadosa (/ 10), a tizedes a maradék (% 10).

Form1.csC#
private int tizedek = 0;

private void Kijelzes()
{
lblIdo.Text = $"{tizedek / 10}.{tizedek % 10} mp";
}

private void tmrFolyamat_Tick(object sender, EventArgs e)
{
tizedek = tizedek + 1;
Kijelzes();
}

private void btnStart_Click(object sender, EventArgs e)
{
tmrFolyamat.Start();
}

private void btnStop_Click(object sender, EventArgs e)
{
tmrFolyamat.Stop();
}

private void btnNullaz_Click(object sender, EventArgs e)
{
tmrFolyamat.Stop();
tizedek = 0;
Kijelzes();
}

6. Mozgó felirat

Egy lblMozgo felirat vándoroljon balról jobbra (Interval: 30). Ha elhagyta az ablak jobb szélét, jelenjen meg újra a bal oldalon.

Megoldás megnyitása

A Left a vezérlő bal szélének helye az ablakban. A ClientSize.Width az ablak használható szélessége. Ha a felirat bal széle túlment, a negatív szélességre állítjuk, így balról csúszik vissza.

Form1.csC#
private void tmrMozgas_Tick(object sender, EventArgs e)
{
lblMozgo.Left = lblMozgo.Left + 4;

if (lblMozgo.Left > this.ClientSize.Width)
{
lblMozgo.Left = -lblMozgo.Width;
}
}

Gyors önellenőrzés

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

1. Mit csinál a Timer Tick eseménye?

Minden időköz letelte után lefut, így ismétlődően végrehajthatunk egy kis lépést.

2. Mit jelent a ProgressBar Maximum tulajdonsága?

A csík legnagyobb értékét. Ha a Value eléri, a csík teljesen kitöltött.

3. Miért veszélyes a Thread.Sleep az eseménykezelőben?

Az ablakot kezelő szálat várakoztatja, ezért az ablak nem tud frissülni, és lefagyottnak tűnik.

4. Mi a különbség a Value = Value + 5 és az Increment(5) között?

Az első hibát okoz, ha az érték túllépi a maximumot. Az Increment a maximumnál megáll.

5. Miért tiltjuk le az Indítás gombot futás közben?

Hogy a felhasználó ne indíthassa újra véletlenül a folyamatot, miközben még fut.

6. Mire való az Interval?

A Tick események közötti időt adja meg milliszekundumban.

Mit tudsz már?

  • Használod a ProgressBar tulajdonságait.
  • Érted, miért fagy be az ablak ciklusnál és Thread.Sleep esetén.
  • Használod a Timer Interval, Start, Stop és Tick elemeit.
  • Biztonságosan növeled a csíkot az Increment segítségével.
  • Letiltod és visszakapcsolod a gombot a folyamat idejére.

Következő lecke: TextBox, MaskedTextBox és NumericUpDown

A folyamatjelzést már ismered. A következő leckében a beviteli vezérlőket nézzük meg részletesebben.