Většina aplikací má více formulářů. V jazyce Visual Basic 6 a ve starších verzích se s formuláři pracovalo trochu jinak, Visual Basic .NET používá trochu jiné metody (které jsou podle mě logičtější a použitelnější, je to ale spíše věc zvyku). Právě kvůli tomu na Visual Basic .NET mnoho programátorů ze starších verzí nadává, ale my se tím nenecháme odradit a vysvětlíme si, jak se s formuláři pracuje ve Visual Basic .NET a proč je to právě takto.
Třídy
Staré programovací jazyky používaly takzvané strukturované programování, což je prakticky to, co jsme se naučili až dosud, ovšem bez objektů. Ještě tam patří funkce a procedury, které se naučíme v díle příštím. Později se však ukázalo, že by se hodilo používat objekty, čemuž se říká objektově orientované programování. Tento styl programování používají prakticky všechny běžně používané jazyky. Abychom pochopili, jak objekty fungují, musíme si vysvětlit, co jsou to třídy. A začneme pěkně na příkladu.
.NET Framework obsahuje třídu Button (tlačítko), což je obecná komponenta, kterou můžeme "plácnout" na formulář. Tato třída není nic jiného, než několik vlastností, funkcí, procedur a událostí, ke kterým můžeme přistupovat zvenku, tato třída ale obsahuje také proměnné, které jsou pouze vnitřní a není žádoucí, aby k nim mohl přistupovat někdo jiný, než právě toto tlačítko.
Vlastnosti již známe. Upravují a mění vlastnosti komponenty, většinou slouží k nastavení hodnot vnitřních proměnných, ale nemusí to platit vždy. Události jsou takové "záchytné body", na které můžeme "pověsit" nějakou proceduru, která se spustí, jakmile dojde k té události. Například pokud klikneme na tlačítko, událost Click zavolá proceduru Button1_Click, pokud vytvoříme na formuláři tlačítko a dvojklikem vytvoříme proceduru této události. Funkce je úsek kódu, který může dostat nějaké vstupní parametry. Pokud funkci zavoláme a předáme jí potřebné parametry, tento úsek kódu se provede (většinou něco spočítá) a vrátí výsledek. Procedura je vlastně funkce, která výsledek nevrací (jenom něco udělá).
A naše třída Button je právě soubor těchto vlastností, událostí, funkcí, procedur a vnitřních proměnných, který je někde popsán a který nám umožňuje vytvořit libovolné množství objektů. Tyto objekty, které jsou od této třídy (je to vlastně takov šablona) odvozené, mají stejné vlastnosti, události atd. jako tato třída a tyto vlastnosti, události atd. mění pouze ten konkrétní exemplář (instanci) daného objektu nezávisle na objektech ostatních. Třída jako taková nikde fyzicky neexistuje, je to pouze jakási šablona, podle které se vytváří objekty.
Pokud tedy na formulář přidáme 5 tlačítek, každé z nich je objekt odvozený od třídy Button, všechny mají stejné vlastnosti, události atd. Ale pokud klikneme na jedno tlačítko, spustí se pouze jeho události Click, ostatním ne. Pokud jednomu zmníme text, ostatním text zůstane. Zkrátka všechna tlačítka vypadají stejně a dělají to samé, ale jsou na sobě nezávislá.
Samozřejmě třídy se používají téměř všude, ne pouze pro nějaké vizuální objekty. Již jsme používali StreamReader a StreamWriter, což jsou třídy, jejichž objekty slouží k práci se soubory. Nijak nevypadají a nikde se nezobrazují. Jen základní princip je stejný.
A jak to souvisí s druhým formulářem?
Každé tlačítko je objekt nějaké třídy. A objekt může obsahovat i další objekty. Formulář je totiž také objekt, který má mimo jiné vlastnost Controls, což je kolekce (něco jako pole, podrobněji se o kolekcích budeme bavit později) komponent, které na něm jsou.
Když navrhujeme ve Visual Studiu formulář, nevytváříme ale objekt. Vytváříme definici třídy. Pokud ale máme standardní nastavení projektu, někde v pozadí se nám vytvoří objekt třídy Form1 a zobrazí se. Jakmile jej ukončíme, ukončí se celý program. To ale není jen tak samo od sebe, Visual Basic .NET na pozadí vygeneruje nějaký kód, který ovšem nevidíme. Pokud bychom ale chtěli, mohli bychom si vytvořit další objekt třídy Form1 a pokud bychom ho zobrazili, uvidíme dva identické formuláře Form1 i s komponentami, které jsme navrhli. Pokud ale jeden změníme, druhý se nezmění. Jak správně tušíte, můžeme si tedy vytvořit druhý formulář a v okamžiku, kdy jej chceme zobrazit, vytvoříme nový objekt třídy druhého formuláře. Takže je to poměrně jednoduché a logické, pokud se smíříme s třídami a objekty.
Otevřete si tedy projekt z minula a v průzkumníkovi projektu klikněte pravým tlačítkem na název tohoto projektu a vyberte Add / New Item. Ze seznamu objektů vyberte Dialog a potvrďte tlačítkem OK. Tím se nám do projektu přidá druhý formulář, který bude sloužit k přidávání a úpravě záznamů. Mohli bychom vybrat Windows Form, ale nám se více hodí Dialog, má již připravená tlačítka. Ale jinak je to normální formulář.
Přidejte na něj komponenty podle obrázku:
Komponentě NumericUpDown nastavte vlastnost Minimum na -100000, Maximum na 100000 a DecimalPlaces na 2. Tato komponenta je určena pro čísla a kontroluje nám, jestli je číslo ze zadaného rozsahu. DateTimePicker slouží k pohodlnému výběru data.
To je celé, teď se nahoře v záložkách přepněte zpět na první formulář.
Přidávání nového záznamu
Dvakrát tedy klikněte na tlačítko Přidat a napíšeme kód pro zobrazení okna, získání hodnot, které uživatel zadal, a nakonec vložení do seznamu. Vytvoříme tedy nový objekt třídy Dialog1, což je naše dialogové okno, které jsme vytvořili. Každý formulář má funkci (někdy se jí také říká metoda) Show a ShowDialog. Pokud totiž objekt vytvoříme, neukáže se na obrazovce hned. Musíme právě zavolat jednu z těchto metod, abychom jej zobrazili. Každá se ovšem chová trochu jinak.
Show zobrazí formulář, se kterým můžeme pracovat nezávisle na formuláři prvním. Další příkazy za zavoláním této metody se provedou ihned. To se nám ale v tomto případě příliš nehodí.
ShowDialog zobrazí druhé okno a čeká, než jej zavřeme. První okno je zamknuté a nemůžeme s ním manipulovat, dokud druhé nezavřeme. Jakmile druhé okno zavřeme, provedou se teprve příkazy za zavoláním této metody v prvním okně. Provádění kódu v prvním okně se tedy pozastaví do doby, než dialog zavřeme. A právě to potřebujeme.
Okna navíc mohou i vracet výsledky. Nejčastěji se používá hodnota DialogResult.OK (pokud jsou změny v okně platné) a DialogResult.Cancel (pokud byly změny stornovány). Protože jsme přidali okno Dialog, o toto se již nemusíme starat. Pokud se podíváte na procedury událostí u tlačítek v tomto dialogu, Visual Basic už vygeneroval vracení hodnoty za nás a my se o něj nemusíme starat. Ale opět za tím stojí nějaký kód, není to jen tak samo sebou. Výhoda je, že tento kód nemusíme psát my.
Výsledek, který okno vrátilo, je zároveň návratová hodnota volání metody ShowDialog. Vytvoříme tedy podmínku - pokud je tato návratová hodnota rovna hodnotě DialogResult.OK, znamená to, že jsme kliknuli na tlačítko OK a že tedy chceme záznam opravdu přidat. Uvnitř této podmínky zjistíme hodnoty v jednotlivých komponentách a vytvoříme řádek v seznamu, jak jsme se to naučili v minulém díle.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim dlg As New Dialog1()
If dlg.ShowDialog() = DialogResult.OK Then
Dim datum As DateTime = dlg.DateTimePicker1.Value
Dim castka As Double = dlg.NumericUpDown1.Value
Dim text As String = dlg.TextBox1.Text
Dim polozka As New ListViewItem()
If castka < 0 Then polozka.ForeColor = Color.Red
polozka.Text = datum.ToShortDateString()
polozka.SubItems.Add(castka)
polozka.SubItems.Add(text)
ListView1.Items.Add(polozka)
End If
End Sub
Pokud jsou komponenty na formuláři, kde právě jsme, stačí pouze napsat název komponenty. Pokud jsou jinde, musíme před to dát i objekt toho formuláře (takže dlg, ne Dialog1). dlg je objekt, Dialog1 je třída. Pak už je vše stejné. Vytvoříme si novou položku seznamu, tedy ListViewItem a naplníme ji daty. Nakonec ji přidáme do kolekce položek Items v komponentě ListView. Jediná změna je použití metody ToShortDateString na proměnné datum. Komponenta DateTimePicker vrací i čas, který nás nezajímá. Proto datum převedeme touto metodou na krátký formát tak, jak je všude (5.5.2007, dlouhý by byl 5. května 2007). Při načítání ze souboru nemusíme, tam se ukládá pouze datum, čas už ne.
Úprava záznamu
Upravení záznamu bude velice podobné. Vytvoříme objekt formuláře a před tím, než jej zobrazíme, nastavíme do jeho komponent hodnoty z vybrané položky v komponentě ListView. Po zobrazení nebudeme novou položku vytvářet, ale nastavíme nové hodnoty té stávající.
ListView podporuje výběr více položek najednou. To se nám momentálně nehodí, takže nastavte v režimu návrhu hodnotu vlastnosti MultiSelect na hodnotu False. Položky, které jsou právě vybrané, jsou ve vlastnosti SelectedItem komponenty ListView. Může jich být totiž více, vlastnost SelectedItems je totiž kolekce. Může ale také nastat situace, kdy není vybraná žádná položka. To zjistíme jednoduše, protože hodnota SelectedItems.Count bude rovna nule. Count je totiž počet položek v kolekci. Pokud je nula, kolekce je prázdná a tedy žádné položky nejsou vybrány. Pokud je vybrána nějaká položka, najdeme ji v SelectedItems(0). Více položek vybrat nelze, protože jsme to zakázali. Celá procedura úpravy záznamu tedy bude vypadat takto:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
If ListView1.SelectedItems.Count = 0 Then Exit Sub
Dim dlg As New Dialog1()
dlg.DateTimePicker1.Value = CDate(ListView1.SelectedItems(0).SubItems(0).Text)
dlg.NumericUpDown1.Value = CDbl(ListView1.SelectedItems(0).SubItems(1).Text)
dlg.TextBox1.Text = ListView1.SelectedItems(0).SubItems(2).Text
If dlg.ShowDialog() = DialogResult.OK Then
Dim datum As DateTime = dlg.DateTimePicker1.Value
Dim castka As Double = dlg.NumericUpDown1.Value
Dim text As String = dlg.TextBox1.Text
If castka < 0 Then ListView1.SelectedItems(0).ForeColor = Color.Red Else ListView1.SelectedItems(0).ForeColor = Color.Black
ListView1.SelectedItems(0).SubItems(0).Text = datum.ToShortDateString()
ListView1.SelectedItems(0).SubItems(1).Text = castka
ListView1.SelectedItems(0).SubItems(2).Text = text
End If
End Sub
Toto tedy fungovat bude, ale upřímně řečeno, moc se vám to určitě nelíbí. Některé kousky ListView1.SelectedItems(0) se tam opakují příliš často. Tomu se ale dá vyhnout, pokud použijeme With blok. Je to snadné.
Část kódu, kde se daný kousek opakuje, ohraničíme zepředu řádkem With ListView1.SelectedItems(0) a na konci řádkem End With. A všechny výskyty ListView1.SelectedItems(0) můžeme z obsahu tohoto bloku klidně vymazat. Pokud totiž něco uvnitř bloku začíná tečkou, je to automaticky přidruženo k vlastnosti nebo objektu za slovem With. Takto nemůžeme rozdělit prakticky jakýkoliv objekt a vlastnost, ale pouze na místě tečky. Nikde jinde. Procedura úpravy tedy bude po zjednodušení vypadat takto:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
If ListView1.SelectedItems.Count = 0 Then Exit Sub
With ListView1.SelectedItems(0)
Dim dlg As New Dialog1()
dlg.DateTimePicker1.Value = CDate(.SubItems(0).Text)
dlg.NumericUpDown1.Value = CDbl(.SubItems(1).Text)
dlg.TextBox1.Text = .SubItems(2).Text
If dlg.ShowDialog() = DialogResult.OK Then
Dim datum As DateTime = dlg.DateTimePicker1.Value
Dim castka As Double = dlg.NumericUpDown1.Value
Dim text As String = dlg.TextBox1.Text
If castka < 0 Then .ForeColor = Color.Red Else .ForeColor = Color.Black
.SubItems(0).Text = datum.ToShortDateString()
.SubItems(1).Text = castka
.SubItems(2).Text = text
End If
End With
End Sub
Musíte uznat, že to je hned přehlednější. A navíc je to méně psaní. Pokud by bylo opakování na dvou místech, With blok se nevyplatí. Ale pokud je to prakticky na každém řádku, rozhodně doporučuji tento blok použít. Kódu je méně a lépe se v něm orientuje.
Odstranění záznamu
Poslední funkce, kterou musíme implementovat, je odstranění záznamu. To je však velmi jednoduché.
Celý tento a minulý díl slibuji, že vysvětlím, co jsou to kolekce. Tak tedy do toho. Kolekce má podobnou funkci jako pole. U pole je však obtížné měnit jeho velikost a ne vždy to je vůbec možné. Kolekce je "nafukovací" pole. Můžeme do ní metodou Add položky přidávat, vlastnost Count vrací počet položek a máme také metodu Remove, která položky odebírá. Kolekce se používají velice často (kolekce položek v seznamu, kolekce komponent na formuláři atd.).
Před odstraněním, stejně jako před úpravou, musíme zjistit, jestli uživatel má vybranou nějakou položku. Pokud by se tak stalo, nejen že bychom nevěděli, kterou položku máme odebrat, ale program by nám vyhodil chybu při práci s ListView1.SelectedItems(0), protože tato položka by neexistovala. Procedura tlačítka pro odstranění bude tedy vypadat takto:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
If ListView1.SelectedItems.Count = 0 Then Exit Sub
ListView1.Items.Remove(ListView1.SelectedItems(0))
End Sub
To je vše. Aplikace je hotová. V příštím díle si povíme o procedurách a funkcích a ukážeme si, jak si je vytvářet.