V jubilejním 13. díle našeho seriálu pro začátečníky se naučíme, jak vykreslovat základní geometrické obrazce a jednoduché grafické útvary. Jelikož jsme v tutoriálu pro začátečníky, nebudu zabíhat do podrobností. Těm se totiž věnujiv článcích ze seriálu Programujeme hry, který najdete také na tomto serveru. V tomto díle tedy ukážu jen úplné základy a vysvětlím trochu teorie. Ještě doplním, že budeme používat grafické rozhraní GDI+, což je základní grafické prostředí implementované v .NET frameworku.
Jmenné prostory
.NET framework má jednu velmi dobrou a užitečnou vlastnost - přehlednou strukturu tříd a funkcí. To zajišťují tzv. namespaces, česky bychom mohli říci jmenné prostory. Používali jsme již dříve, ale nevysvětloval jsem, jak fungují. Nyní je na to ta správná chvíle. Namespace sdružuje třídy, funkce, struktury apod., které spolu nějak logicky souvisí. V .NET frameworku je základním jmenným prostorem System. Ten se dělí na několik částí, z nichž zmíním ty nejpoužívanější:
- System
- Data - vše, co se týká databází a práce s daty
- Drawing - vše, co se týká grafiky a vykreslování
- IO - vše, co se týká práce se souborovým systémem
- Net - vše, co se týká síťové komunikace, např. TCP/IP, UDP, ale i HTTP, FTP protokoly, e-mail atd.
- Security - vše, co se týká zabezpečení
- Cryptography - funkce pro šifrování
- Text - vše, co se týká práce s textem (StringBuilder, regulární výrazy, kódování a znakové sady)
- Web - prakticky celé ASP.NET
- Windows
- Forms - komponenty, formuláře atd.
- Xml - vše, co se týká práce s dokumenty XML, jejich šablonami a schématy
Pokud chci použít funkci z nějakého namespace, mám několik možností. Vždy můžeme vypsat celou cestu (úrovně se oddělují tečkou) - tzn. např. System.Windows.Forms.Button. Takto bychom se ale asi nikam nedostali, pořád všechno vypisovat není ideální řešení.
Proto existuje v jazyce Visual Basic .NET klíčové slovo Imports, za kterým následuje cesta k nějakému namespace. Řádky Imports se píší do každého souboru úplně nahoru, před deklaraci jakékoliv třídy. Jakmile mám nějaký namespace naimportovaný, nemusím vypisovat celou cestu. Pokud tedy naimportuji System.Windows.Forms, můžu pak klidně napsat Dim b As New Button(). Visual Basic pak prohledá všechny naimportované jmenné prostory, pokud nikde datový typ či třídu nenajde, vyhodí chybu.
Možná je vám divné, že tohle fungovalo i bez klíčového slova Imports. Máte pravdu. Pokud otevřete vlastnosti projektu, na záložce References uvidíte dva seznamy.
V horním seznamu jsou knihovny, které naše aplikace vyžaduje. Tlačítkem Add můžeme přidat další knihovny a využívat i jejich funkce. To nás nyní ale moc nezajímá. Hlavní je spodní seznam Imported Namespaces, ve kterém jsou zaškrtnuty jmenné prostory, které jsou naimportovány automaticky. Pokud chceme naimportovat nějaký další, můžeme jej zaškrtnout zde. Je to vhodné zejména v případě, že jej chcete využívat ve více (téměř všech) souborech. Pokud jej potřebujeme použít pouze v jednom či dvou souborech, doporučuji spíše použít klíčové slovo Imports. Občas se totiž stane, že ve dvou jmenných prostorech jsou dvě třídy nebo funkce se stejným názvem. Při použití je tedy nutné použít celou cestu nebo její část (např. Forms.Button, ale jen pokud máme naimportováno System.Windows).
Kdokoliv si může vytvořit i svůj vlastní namespace, často se to využívá ve firmách (každá firma většinou používá svůj namespace, který mívá nějakou vnitřní strukturu, do nějž zařazují veškeré své knihovny a třídy). Deklaraci třídy můžeme "obklíčit" řádky Namespace XY.Z a End Namespace, čímž řekneme, že se třída nachází ve jmenném prostoru XY.Z. Samozřejmě není nutné, aby všechny třídy byly ve stejném souboru mezi řádky Namespace a End Namespace, stačí každou třídu, ať je kdekoliv, uzavřít do namespace se stejným názvem, kde ji poté také najdeme.
Dost už teorie, jdeme kreslit
Pokud jste přetrpěli nudnou část o jmenných prostorech, vězte, že odteď to již bude zábavnější a plné obrázků. Ještě před tím si ale povíme něco o objektu System.Drawing.Graphics, což je prakticky ten nejdůležitější objekt, pokud mluvíme o vykreslování. Tento objekt reprezentuje jakési kreslící plátno, na které můžeme vykreslovat jak základní geometrické tvary (čára, obdélník, elipsa, část obvodu elipsy apod.), ale i např. text. Celé plátno má daleko více funkcí, ty však v tomto článku probrat nestihneme. Ukážeme si tedy jen základní geometrické tvary a výplně.
Vytvořte si ve Visual Basic .NET nový projekt (jako vždy Windows Application) a ihned se přepněte do okna kódu (např. klávesovou zkratkou F7). V horních rozbalovacích seznamech nalistujte Form 1 Events a Paint, čímž vytvoříme proceduru události Paint formuláře. S touto událostí jsme ještě nepracovali, nastane vždy, když je potřeba obsah okna znovu vykreslit. Pokud máme normální okno, které je již na obrazovce, a nakreslíme na něj něco, ihned se informace zobrazí. Pokud ale otevřeme jiné okno a zakryjeme jím část prvního okna, po jejím odkrytí je potřeba obsah vykreslit znovu (Windows XP a nižší si totiž vykreslovaný obsah okna nepamatují, na Windows Vista se v tomto případě událost nevyvolá). Překreslení je také potřeba, pokud jsme okno minimalizovali a pak znovu zobrazili. Ve všech těchto případech se zavolá událost Paint, ve které bychom měli zajistit vykreslení obsahu okna.
Pokud se podíváme na záhlaví události Paint, můžeme si všimnout, že něco je jinak. Druhý parametr e není typu System.EventArgs, jak tomu bývá jinde, ale je typu System.Windows.Forms.PaintEventArgs. To proto, že obsahuje objekt Graphics, na který budeme vykreslovat, a pár dalších informací (pro nás zatím nepodstatných).
Zkuste tedy do procedury vepsat tento řádek:
e.Graphics.Clear(Color.Yellow)
Pokud program spustíte, obsah okna bude žlutý. Toto chování by se samozřejmě dalo nastavit i vlastností BackColor, ale já jsem chtěl demostrovat metodu Clear objektu Graphics. Ta smaže vše, co bylo vykresleno na okně, a obarví to předanou barvou. Jak vidíme, Color je struktura, která obsahuje spoustu již předdefinovaných barev, které můžeme využít. Stačí napsat Color.Yellow a máme žlutou.
Metody DrawLine, DrawRectangle a DrawEllipse
Pro vykreslení základních geometrických tvarů čára, obdélník a elipsa nám objekt Graphics nabízí metody DrawLine, DrawRectangle a DrawEllipse. Všechny tyto metody vyžadují jako první parametr hodnotu typu Pen, která reprezentuje pero, kterým tvar kreslíme. Máme opět předdefnovanou širokou paletu standardních kreslících per, můžeme si ale vytvořit své vlastní a specifikovat mu např. tloušťku a vzorek (plná čára, přerušované čárky, čerchovaně, tečičky atd.). Vytvoříme tedy modré pero tloušťky 3.
Dim p As New Pen(Color.Blue, 3)
Ještě než začneme kreslit tvary, musíme si něco říci o souřadnicích. Protože obrazovka je dvojrozměrná, máme dvě souřadnicové osy X a Y. Výsledný obraz na obrazovce se skládá z tzv. pixelů, česky obrazových bodů. Každý pixel může mít právě jednu barvu, protože pixely jsou malé a je jich hodně, ve výsledku dávají celistvý obraz.
Počátek souřadnickvé osy je v horním levém rohu formuláře. Osa X je vodorovná a roste směrem doprava, osa Y je svislá a roste směrem dolů. Každý pixel je jednoznačně určen souřadnicemi [X, Y], kde hodnoty X, Y jsou kladná celá čísla. Jednotkou je samozřejmě jeden pixel.
DrawLine
Pokud tedy do události Paint zapíšete tyto řádky, vykreslí se čára. První parametr je pero, druhý a třetí jsou souřadnice X a Y prvního konce úsečky a čtvrtý a pátý parametr jsou souřadnice druhého konec úsečky.
Dim p As New Pen(Color.Blue, 3)
e.Graphics.DrawLine(p, 20, 120, 250, 50)
DrawRectangle
Pokud potřebujeme vykreslit obdélník (čtverec je speciální druh obdélníku), máme na to funkci DrawRectangle. Prvním parametrem je opět pero, druhým a třetím jsou souřadnice horního levého rohu obdélníka a čtvrtým a pátým parametrem je šířka a výška obdélníka.
Dim p As New Pen(Color.Salmon, 3)
e.Graphics.DrawRectangle(p, 20, 20, 150, 100)
DrawEllipse
Touto metodou vykreslíme elipsu (kružnice je zvláštní druh elipsy). Prvním parametrem je klasicky pero, další parametry jsou stejné, jako u obdélníka. Vymezují tedy obdélník, do nějž se elipsa vepíše.
Dim p As New Pen(Color.Indigo, 3)
e.Graphics.DrawEllipse(p, 20, 20, 150, 100)
Odbočka - přetěžování metod
Možná jste si všimli, že když zapisujete tyto metody, Visual Studio zobrazuje nápovědu se seznamem parametrů a jejich popisem. Možná vám neunikla minitlačítka nahoru a dolů, které přepínají různé sady parametrů. Díky této pomůcce se dostáváme k termínu přetěžování. Je to v .NET frameworku poměrně využívaná záležitost.
Pokud deklarujete proceduru či funkci, můžete ji nadeklarovat vícekrát pro různé datové typy a počty argumentů. Jako příklad napíši proceduru pro dělení - pokud předáme datové typy Integer, vydělíme celočíselně, pokud předáme Double, vydělíme normálně (výsledkem bude desetinné číslo).
Overloads Function Vydel(ByVal a As Integer, ByVal b As Integer) As Integer
Return a \ b
End Function
Overloads Function Vydel(ByVal a As Double, ByVal b As Double) As Double
Return a / b
End Function
Všimněte si klíčového slova Overloads, které označuje, že se jedná o přetíženou metodu. Ve skutečnosti tam být nemusí, ale je dobrým programátorským zvykem jej používat, aby bylo hned jasné, že máme více možností, jak metodu použít.
Velmi často se přetížení používá tak, že máme funkční kód pouze v jedné variantě a ostatní varianty pouze něco provedou s argumenty (např. je převedou) a zavolají první metodu, která kód obsahuje. Bylo by totiž zbytečné vypisovat prakticky stejný kód na víc míst, vždy je lepší se tomu vyvarovat.
Point a Rectangle
V souvislosti s přetížením metod DrawLine, DrawEllipse a DrawRectangle je nasnadě ukázat si dva datové typy (struktury), které usnadňují práci s body a obdélníky. Jsou jimi Point a Rectangle. Nejde o nic jiného než o struktury, které obsahují několik vlastností. Point je bod a obsahuje vlastnosti X a Y, což jsou jeho souřadnice. Rectangle je obdélník, který má vlastnosti Left, Top (souřadnice X, resp. Y) a Width, Height (šířka a výška). Výhodou je, že je můžeme i snadno vytvořit - stačí napsat New Point(20, 20) či New Rectangle(10, 10, 30, 50). Tyto datové typy se hodí, pokud potřebujete např. ve funkci vracet právě obě souřadnice bodu, nebo 4 údaje pro obdélník.
Existují ještě datové typy PointF a RectangleF, které jsou téměř stejné, až na to, že podporují desetinná čísla.
Výplně a vybarvování
Tři metody, které jsme si právě ukázali, kreslily pouze tvar. Pokud chceme vybarvit obdélník či elipsu, máme k tomuto účelu metody FillRectangle a FillEllipse. Liší se pouze prvním parametrem - není jím Pen, ale Brush (štětec). Zde máme opět široké pole působnosti - buď můžeme využít fádní předdefinované štětce, které jednolitě vyplní celou plochu, nebo si můžeme vytvořit vlastní šrafovací štětce či štětce, které dané místo vyplní přechodem barev.
Jednoduchý štětec plné barvy je reprezentován třídou SolidBrush. Předáme jí akorát barvu štětce. Máme kromě toho také předdefinovanou sadu štětců (Brushes.Green je prakticky to samé).
Pokud budu tedy chtít vybarvit elipsu zeleně, stačí mi tento kód:
Dim b As New SolidBrush(Color.Green)
e.Graphics.FillEllipse(b, 20, 20, 150, 100)
Pokud chceme oblast vyšrafovat, použijeme Drawing2D.HatchBrush. Pokročilejší štětce jsou v namespace System.Drawing.Drawing2D. V konstruktoru zvolíme styl z výběru, barvu popředí a barvu pozadí, a pak jen vybarvíme elipsu.
Dim b As New Drawing2D.HatchBrush(Drawing2D.HatchStyle.DiagonalCross, Color.DarkSlateBlue, Color.Yellow)
e.Graphics.FillEllipse(b, 50, 50, 100, 100)
Jako poslední štětec si ukážeme LinearGradientBrush, který vyplní danou oblast plynulým přechodem dvou barev. V konstruktoru zadáme dva body (musíme použít datový typ PointF) a dvě barvy. a prvním bodu bude první barva, na druhém bude druhá barva, a ostatní body se vyplní příslušnou kombinací dvou barev ve správném poměru.
Dim b As New Drawing2D.LinearGradientBrush(New PointF(20, 20), New PointF(120, 120), Color.Yellow, Color.Green)
e.Graphics.FillRectangle(b, 20, 20, 100, 100)
To je pro dnešek vše, v příštím díle ještě budeme pokračovat s grafikou a napíšeme si jednoduchou aplikaci, která bude vykreslovat jednoduché sloupcové a čárové grafy.
Jsem hlavním softwarovým architektem ve společnosti Riganti. Mám dlouholeté zkušenosti s technologiemi ASP.NET, Silverlight, WPF a XNA. Působím též jako lektor ve společnosti Gopas a již třetím rokem jsem držitelem ocenění Microsoft Most Valuable Professional.