7. díl - Kreslení na Graphics v C# .NET

C# .NET Windows Forms Kreslení na Graphics v C# .NET

V minulém dílu seriálu tutoriálů o tvorbě formulářových aplikací v C# .NET jsme dokončili tvorbu upomínače narozenin. Vyzkoušeli jsme si na něm základní formulářové prvky a také bindingy a práci s chybovými stavy. Již umíme vytvořit poměrně sofistikované aplikace. Dnešní lekci budeme věnovat kreslení.

Kreslení na Graphics

Vytvoříme aplikaci, která má za úkol zastřešit prodej vstupenek do kina. Jak víme, v sálu je mnoho sedadel a pracovník kina by měl v aplikaci vidět, která sedadla jsou již obsazená. Možná by vás napadlo naklikat pro sedadla PictureBoxy. Pokud by však kino mělo 15 řad a každá řada 30 sedadel, máme to 450 PictureBoxů. Asi tušíte, že existuje lepší cesta, než začít bušit PictureBox1, PictureBox2... A jak by se potom obsluhovaly? V případě, že potřebujeme vykreslit něco náročnějšího, než jen jeden nebo dva obrázky, využijeme Graphics. To spočívá v tom, že na formulář umístíme jeden PictureBox a na jeho plátno budeme vykreslovat to, co potřebujeme.

Aplikaci značně zjednodušíme, není třeba aby byla složitá. Bude umět zobrazit jen jeden sál, který bude zpočátku prázdný. Uživatel nakliká myší obsazená sedadla a poté stiskne tlačítko Uložit, které do zvolené lokace uloží jednoduchý txt soubor s informací o obsazenosit sálu. Ukládání si zkusíme proto, abychom se naučili pracovat s dialogy.

Návrh formuláře

Vytvořte si novou Windows Forms Aplikaci, formulář přejmenujeme na KinoForm, záhlaví třeba na "Evidence kinosálu". Přes většinu formuláře natahněte PictureBox, který pojmenujeme kinoPictureBox. Pod PictureBox přijde Button se jménem ulozitButton a textem "Uložit". Konečně na formulář přetáhněme i SaveFileDialog. Ten se nepřidá přímo na formulář, ale do lišty pod něj. Nejedná se totiž o formulářový prvek, ale pouze o pomocnou komponentu. Přejmenujeme ji na kinoSaveFileDialog. Můžete si pohrát s kotvami a podobně. Váš formulář by měl vypadat asi takto:

Formulář kinosálu v C# .NET

Logická vrstva

Asi vás nepřekvapí, že k aplikaci přidáme třídu Kinosal. Bude mít jeden privátní atribut, kterým bude dvourozměrné pole sedadel. Pokud jste s 2D polem ještě nepracovali, tak si ho můžete představit jako tabulku. Jednorozměrné (klasické) pole je vlastně jen jeden řádek. S 2D polem poté pracujeme úplně stejně, jako s jednorozměrným, jen musíme uvést dvě souřadnice (X a Y). V mnoha jazycích se dělá 2D pole jako pole polí, C# umí definovat přímo 2d pole a to takto:

class Kinosal
{
        private bool[,] sedadla = new bool[30, 15];

}

Sedadla jsou typu bool, protože nás zajímá jen jestli je volné nebo obsazené. 30 je šířka pole, 15 jeho výška.

Do třídy ještě přidáme 2 privátní konstanty, jedna udává velikost vykreslovaného sedadla v pixelech a druhá mezeru mezi sedadly v pixelech. Zvykněte si konstanty používat, až budete chtít sedadla zvětšit, stačí pouze přepsat jednu konstantu a nemusíte luštit vykreslovací kód.

private const int velikost = 16;
private const int mezera = 2;

Můžeme přejít k metodám.

Vykreslení

Kinosál by se měl umět vykreslit. Již jsme si zmínili, že budeme kreslit na plátno. Toto plátno je typu Graphics a necháme si ho přijít v parametru metody Vykresli(). Pro typ Graphics je třeba přidat using System.Drawing, ale to vás jistě nezaskočilo. Na plátno se potom kreslí pomocí jeho metod. Nás bude zatím zajímat jen metoda FillRectangle(), která vykreslí obdélník, vyplněný určitou barvou. Metod je tam obrovská spousta pro různé geometrické tvary, ať už vyplněné nebo nevyplněné. Můžete si je projet, některé si vyzkoušíme ještě v dalších lekcích.

V Graphics rozeznáváme dva typy barvy - barva výplně (brush = štětec) a barva obrysu (pen = pero). Pro vyplněný obdélník musíme zadat nějaký Brush. Hotové instance nastavené na určité barvy nalezneme na statické třídě Brushes (nebo pro obrysy Pens), stačí si jen vybrat. Štětce mohou kreslit i nějakými vzory nebo obrázky, ale to pro nás není důležité.

Pomocí dvou vnořených cyklů projedeme všechna sedadla v poli a na plátno vykreslíme buď zelený nebo červený čtverec. Vnějším cyklem budeme projíždět řádky, vnitřním sloupce v aktuálním řádku. Barvu (přesněji štětec) určíme podle toho, zda je sedadlo na dané souřadnici true nebo false. Kód metody bude následující:

public void Vykresli(Graphics g)
{
        Brush brush;
        for (int j = 0; j < sedadla.GetLength(1); j++)
        {
                for (int i = 0; i < sedadla.GetLength(0); i++)
                {
                        if (sedadla[i, j])
                                brush = Brushes.Red;
                        else
                                brush = Brushes.Green;
                        g.FillRectangle(brush, i * (velikost + mezera), j * (velikost + mezera), velikost, velikost);
                }
        }
}

Všimněte si, že v cyklech nepoužíváme hodnoty 30 a 15, ale používáme metodu GetLength s parametry 0 a 1. Tato metoda slouží pro získání velikosti dvourozměrného pole. 0 je šířka, 1 je výška (samozřejmě záleží na nás, kterou dimenzi si určíme jako výšku a kterou jako šířku). Pevnou velikost neuvádíme pochopitelně z důvodu, že v budoucnu můžeme pole zvětšit/zmenšit a museli bychom v kódu hledat kde všude jsme hodnoty 30 a 15 použili. Těmto problémům je vždy lepší se vyhnout a pracovat s délkou pole.

Za zmínku stojí i samotné vykreslení obdélníku. Prvním parametrem metody FillRectangle() je Brush, určující barvu výplně. Další dva parametry jsou souřadnice levého horního rohu obdélníku. Poslední dva parametry určují jeho výšku a šířku. Jelikož je každé sedadlo široké 16 pixelů + 2 pixely mezera, musíme jeho souřadnici touho hodnotou pronásobit. Pokud je v i např. hodnota 2 (kreslíme tedy 3. sloupec), kreslíme na X souřadnici 36, nikoli na 2 :) To samé platí pro souřadnici Y.

Později si můžete zkusit nahradit FillRectangle() metodou FillOval(). Funguje úplně stejně, ale vykreslní elipsu. Určitě si po dokončení aplikace zkuste vykreslit i další tvary.

Propojení formuláře s logickou vrstvou

Základ logiky máme hotový, pojďme ji propojit s formulářem. Přejdeme do kódu formuláře a ve třídě vytvoříme privátní instanci kinosálu:

private Kinosal kinosal = new Kinosal();

Nyní naklikneme PictureBoxu událost Paint. Musíte to udělat přes ikonu blesku v oknu Properties. Událost volá systém ve chvíli, kdy se má okno překreslit. To je samozřejmě v případě spuštění aplikace, ale také po obnovení z minimalizace, ve chvíli, kdy po okně aplikace přejedeme jiným oknem a podobně.

V obslužné metodě události zavoláme na instanci kinosálu metodu Vykresli(). Plátno nám přijde jako vlastnost parametru události. Pouze ho předáme dále metodě logiky, která nám na něj vykreslí.

private void kinoPictureBox_Paint(object sender, PaintEventArgs e)
{
        kinosal.Vykresli(e.Graphics);
}

Za výsledek se nemusíme stydět:

Kreslení tvarů na PictureBox v C# .NET

V příštím dílu si ukážeme, jak kliknutím na určité sedadlo změnit jeho stav a zprovozníme také ukládání. Projekt máte jako vždy ke stažení v příloze pro případ, že se vám něco nepodařilo.


 

Stáhnout

Staženo 721x (50.2 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

  Aktivity (1)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (6 hlasů) :
4444 4


 



 

 

Komentáře
Zobrazit starší komentáře (19)

Avatar
Jan Vargovský
Redaktor
Avatar
Jan Vargovský:

A mám snad věšteckou kouli ?

 
Odpovědět 7.12.2013 17:51
Avatar
RooBoo
Člen
Avatar
RooBoo:

to asi nie .. potrebujem aby sa obrazok pohyboval napriklad od [0,0].. potom [0,50].. a do [0,100] .. a od zaciatku potom .. ale neviem ako to spravit

 
Odpovědět 8.12.2013 8:56
Avatar
RooBoo
Člen
Avatar
Odpovídá na RooBoo
RooBoo:

mam to takto .. vytvoril som si triedu vykresli a tam som vytvoril metodu

public void vykresli_lisa(Graphics g,int i)
        {
            Image image = Image.FromFile("gamelisa.jpg");
            Point point = new Point(20, i);
            g.DrawImage(image, point);


        }

vo form1 som napisal

private void valecjedna_Paint(object sender, PaintEventArgs e)
        {
            for (int i = 0; i < 200; i++)
            {
                vykresli.vykresli_lisa(e.Graphics,i);
                valecjedna.Refresh();

            }

        }
Editováno 8.12.2013 9:19
 
Odpovědět 8.12.2013 9:18
Avatar
RooBoo
Člen
Avatar
Odpovídá na RooBoo
RooBoo:

prosim ta poradis ? :D

Editováno 8.12.2013 11:52
 
Odpovědět 8.12.2013 11:51
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na RooBoo
David Čápka:

Místo těch hloupých dotazů se nad tím kódem zamysli. Čím více takových dotazů a smajlíků, tím méně lidí ti pomůže. Já bych ti pomohl, kdyby jsi se choval normálně, ale takhle nemám chuť. Dotaz také patří do fóra, ne pod článek, kterého se netýká.

Odpovědět 8.12.2013 12:00
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
RooBoo
Člen
Avatar
Odpovídá na David Čápka
RooBoo:

Tak sa ospravedlňujem

 
Odpovědět 8.12.2013 12:13
Avatar
kamo20101
Člen
Avatar
kamo20101:

pred precitanim tohto clanku som si spravil program ktory vykresluje biele body na ciernom pozadi na aktualnych suradniciach mysky ktore pomalicky chaoticky padali dolu, teda bolo to snezenie :) ale vykreslovane boli priamo na form a ked ich bolo vela zavalo to sekat a blikat, ako som to prerobil na picturebox ide to bez problemov .)

 
Odpovědět 7.6.2014 15:28
Avatar
srdosm
Člen
Avatar
srdosm:

Ahoj. Dá se ta událost "kinoPictureBox_Pa­int(object sender, PaintEventArgs e)" zavolat z jiné třídy? Napadlo mě udělat si metodu, co bude public static, takže jí zavolám odkudkoliv. Ale nevím, co by v ní mělo být.

 
Odpovědět 3.2.2015 23:27
Avatar
Lukáš Křehula
Redaktor
Avatar
Odpovídá na srdosm
Lukáš Křehula:
kinoPictureBox.Invalidate();
 
Odpovědět 4.2.2015 11:07
Avatar
David Oczka
Redaktor
Avatar
Odpovídá na kamo20101
David Oczka:

Sice píši relativně pozdě, ale někomu kdo bude číst tento článek se to třeba bude hodit. Pokud vykresluješ skrze Graphics do nějaké komponenty složitější grafiku a aplikace se seká, doporučil bych pár věci:

  • Nastavit vlastnost formuláře DoubleBuffered = true
  • Použít namísto komponenty PictureBox komponentu Label (Z nespočtu testů mám ověřeno, že je pro to nejrychlejší)
  • Používat na místo metody control.Invali­date() metodu control.Refresh(), která zajišťuje okamžité vykreslení

Ovšem co se týče té metody Invalidate, hodí se pokud je třeba překreslit pouze část nějaké komponenty, jako příklad uvedu jednu položku v Listboxu, kde už není třeba překreslit celou komponentu, pak se do metody jako parametr uvádí třída Rectangle, která nese informaci o oblasti k překreslení.

 
Odpovědět 19. června 12:00
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 29. Zobrazit vše