Jelikož se zde ve fóru objevil zajímavý dotaz, jak udělat ve Visual Basic .NET program na tisk faktur, rozhodl jsem se, že tomuto tématu věnuji jeden článek.
Pokud chceme pracovat s dokumenty, máme v zásadě 2 možnosti. Buď si napíšeme všechno úplně sami, anebo použijeme částečně hotové řešení s drobným omezením. Tento článek řeší druhý případ - použijeme program Microsoft Word, ale budeme k němu přistupovat z Visual Basicu. Tím si ušetříme spoustu práce, protože šablonu si navrhneme přímo ve Wordu a dosadíme jen na příslušná místa příslušné údaje, ale plyne nám z toho omezení - musíme mít nainstalovaný Microsoft Word. Pokud bychom si celou fakturu vykreslovali sami, toto omezení by nebylo, ale měli bychom o mnoho více práce. Tím, že použijeme Word, navíc získáme ještě jednu výhodu - fakturu nemusíme tisknout, ale můžeme ji odeslat e-mailem.
Tvorba šablony
Jak jsem již naznačil, nejjednodušší bude vytvořit fakturu ve Wordu a následně dosadit na příslušná místa příslušné texty, konkrétně adresu odběratele, adresu dodavatele, všechny fakturované položky včetně množství a cen, podpis a kontakt na osobu, která fakturu vystavila, a datum vystavení. Nebudeme zde šablonu tvořit celou od začátku, jen naznačím, jak tvorba bude vypadat.
Microsoft Word je opravdu mocný nástroj - mnoho lidí se bude hádat, ale nevěřte jim. Hlavní problém Wordu je ten, že jej totiž dohromady nikdo pořádně neumí používat - velikost a barvu písma umí změnit každý, ale tím znalosti většiny lidí končí. O stylech, poznámkách pod čarou, křížových odkazech a podobných věcech už ani nemluvím, ty umí opravdu málokdo. Co jej ovšem výrazně povyšuje nad konkurenci je promyšlený objektový model, který je dostupný i navenek, takže můžeme prakticky veškeré funkce Wordu používat v našich aplikacích. Dost už ale teorie, jdeme na vytvoření šablony.
Navrhneme tedy základní strukturu celé faktury (já mám MS Office 2007, ale mělo by to jít i ve starších, používat budeme ještě starý formát DOC, o novém DOCX zase někdy příště).
Jen pro doplnění, část s adresami odběratele a dodavatele je jedna tabulka (1 řádek, 2 sloupce, v každém jedna adresa), dál následuje druhá tabulka s položkami a jejich cenami, a pod tím máme třetí tabulku (1 řádek, 2 sloupce). Místo textů nyní budeme chtít dosazovat vlastní hodnoty. Použijeme další funkci Wordu, a tou jsou záložky. Jsou to místa v dokumentu, která se nám budou snadno přístupná z objektového modelu.
Abychom mohli se záložkami pracovat, je třeba je zapnout v nastavení. Ve starších Office je to někde v menu Nástroje pod položkou Možnosti, v nových je to v hlavním menu - stačí kliknout dole na položku Nastavení a na kartě Pokročilé je možnost Zobrazit záložky. Nyní budou záložky viditelné a budeme s nimi moci pracovat. Na každé místo, kam budeme dosazovat, vložíme záložku, kterou pojmenujeme.
Záložky
Záložku přidáme jednoduše - označíme text, místo kterého budeme dosazovat, a pak v menu Vložit (v nových Office na kartě Vložení) vybereme možnost Záložky a v dialogu nastavíme název záložky a klikneme na tlačítko Přidat.
Správně vytvořenou záložku poznáme tak, že se kolem ní ukáží hranaté závorky (pokud jsme zobrazení záložek správně zapnuli, pokud ne, neuvidíme nic).
Takto pojmenujeme a označíme všechna požadovaná místa v dokumentu, akorát prostřední tabulku položek necháme být, tu vyplníme bez záložek. Nezapomeneme ani nahradi záložku v záhlaví. Abyste tuto zdlouhavou činnost nemuseli dělat, vytvořil jsem dokument s fakturou se záložkami já, takže si jej můžete stáhnout. Je to uloženo jako obyčejný DOC dokument, takže jej otevřete i ve starých Office.
Návrh aplikace
Šablonu máme připravenou, zbývá nám navrhnout okno aplikace a napsat aplikační logiku. Abychom se něco naučili, ukážeme si také, jak používat tzv. Application Settings. Tato věc je nová v .NET Frameworku 2.0, jedná se o systém pro snadné ukládání nastavení aplikací. Vytvořte si tedy nový projekt (Windows Application) a rozmístěte obládací prvky třeba takto. Visual Studio nám umožňuje vytvářet opravdu pěkná rozhraní, vždycky jsem strašně naštvaný, když vidím ledabyle "naflákaná" tlačítka a textová pole.
Prostřední tabulka je komponenta DataGridView, pokud na ni klepnete pravým tlačítkem a vyberete možnost Edit columns, objeví se okno pro přidávání sloupců tabulky. Přidejte 4 sloupce, nastavte jim vlastnosti HeaderText na dané popisky a poslednímu sloupci nastavte ještě vlastnost ReadOnly na hodnotu True. Posledním dvou sloupcům nastavte vlastnosti Format na hodnotu C2, čímž zajistíme, že hodnota v těchto sloupcích se zformátuje jako měna. Ceny budeme uvádět vždy bez DPH a cena s DPH se dopočítá sama. Dolní šedé pole je obyčejný PictureBox, do kterého zobrazíme razítko, které pak vložíme do faktury na příslušné místo.
Dále si na formulář přidejte jednu komponentu OpenFileDialog pro výběr souboru obrázku s podpisem, a dále jednu komponentu SaveFileDialog pro výběr umístění hotové faktury.
Výběr obrázku ze souboru
Přidejte do projektu tuto proceduru, která se po kliknutí na pole pro podpis zeptá na soubor a pokud jej vybereme, zobrazí jej v PictureBoxu. Protože budeme potřebovat i cestu k tomuto souboru, uložíme ji do vlastnosti Tag. Tato vlastnost se dá využít jako poznámka, mohli bychom samozřejmě použít i nějakou proměnnou.
Private Sub PictureBox1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.Click
OpenFileDialog1.InitialDirectory = My.Computer.FileSystem.SpecialDirectories.MyPictures
If OpenFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
PictureBox1.Image = Image.FromFile(OpenFileDialog1.FileName)
PictureBox1.Tag = OpenFileDialog1.FileName
End If
End Sub
Validace hodnot v tabulce
Nebudeme kontrolovat správnost všech údajů, to bychom se nikam nedostali, jediné, co si ukážeme, jsou sloupce s cenou. Zadáním ceny bez DPH se musí dopočítat cena s DPH. Dále ještě zkontrolujeme, jestli je množství číslo. Ostatní jistě zvládnete sami, takže to nebudu rozvádět. Do okna kódu vložte událost CellEndEdit, která nastane, jakmile dokončíme editaci buňky.
Private Sub DataGridView1_CellEndEdit(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DataGridView1.CellEndEdit
With DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex)
If e.ColumnIndex = 2 Then
Dim m As Double
If Not Double.TryParse(.Value, m) Then
m = 0
End If
.Value = m
DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex + 1).Value = .Value * 1.19
ElseIf e.ColumnIndex = 1 Then
Dim i As Integer
If Not Integer.TryParse(.Value, i) Then
.Value = 1
End If
End If
End With
End Sub
Automatické uložení nastavených hodnot
U bšech textových polí by se hodilo uložit naposledy zadané hodnoty a po opětnovné spuštění programu je zase načíst. Mohli bychom to napsat ručně (a jistě bychom to zvládli), ale proč psát něco celé o začátku, když už to za nás někdo udělal. Použijeme tzv. Application Settings, což je nová funkcionalita Visual Studia 2005. Označte první textové pole a v okně vlastností klikněte na tlačítko se třemi tečkami u vlastnosti PropertyBindings v položce ApplicationSettings. V dialogu, který se objeví, vybereme příslušnou vlastnost a rozablíme ji, čímž zobrazíme seznam všech dostupných položek nastavení. Odkazem New zobrazíme okno, ve kterém vytvoříme položku novou. Pak již stačí jen zadat název nastavení kliknout na OK.
Položky tabulky ukládat nebudeme, dost značně by to prodloužilo článek. I zde můžeme použít naše Application Settings, ale nepůjde to jednoduše naklikat, museli bychom načítat a ukládat z kódu. Vrhneme se tedy na generování dokumentu. Šablonu máme nachystanou, takže můžeme začít.
Přidání referencí na knihovnu Wordu
Abychom mohli využívat funkce aplikace Microsoft Word, musíme si do projektu přidat reference na jeho knihovnu. Otevřete si tedy vlastnosti projektu (dvojklik na položku My Project v podokně Solution Explorer) a na záložce References klikněte na tlačítko Add. V dialogu vyberte knihovnu s názvem Microsoft Word Object Library (na číslu verze nezáleží) a potvrďte tlačítkem OK. Nyní můžeme využívat funkce MS Wordu z naší aplikace. Pokud jste někdy dělali s VBA ve Wordu (Visual Basic For Applications), vězte, že VBA používá stejnou knihovnu, tím pádem bude kód prakticky stejný.
Další věc, kterou bychom měli udělat, je přidat soubor šablony do projektu. V podokně Solution Explorer tedy klikněte pravým tlačítkem a vyberte Add\Existing Item..., v okně vyberte vzorovou fakturu a potvrďte. Klikněte na soubor a vlastnost Copy To Output Directory nastavte na hodnotu Copy Always, aby se soubor zkopíroval do složky s výstupním EXE souborem. Nyní již můžeme začít programovat. Poklepejte tedy na tlačítko pro generování a po částech si projdeme celý proces generování.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Me.Enabled = False
Dim w As New Microsoft.Office.Interop.Word.Application()
w.Documents.Open(IO.Path.Combine(Application.StartupPath, "Faktura.doc")).Activate()
...
Nejprve zakážeme všechny ovládací prvky na formuláři, aby uživatel nemohl během generování měnit hodnoty v polích a nenadělal nám v dokumentu "paseku". Další příkaz vytvoří novou instanci objektu Word.Application. De facto se na pozadí spustí program Word, který budeme pomocí kódu ovládat. Na dalším řádku zavoláme metodu Open a předáme jí cestu složenou z cesty k EXE souboru (Application.StartupPath) a názvu šablony. Ke spojování cest je dobré využít funkci IO.Path.Combine, nemusíme totiž kontrolovat, jestli první cesta končí lomítkem, aby se spojení provedlo správně. Na otevřený dokument zavoláme ještě funkci Activate, která jej nastaví jako aktivní dokument.
...
With w.ActiveDocument.Bookmarks
.Item("odberatel_nazev").Range.Text = txbOdberatelJmeno.Text
.Item("odberatel_ulice").Range.Text = txbOdberatelUlice.Text
.Item("odberatel_mesto").Range.Text = txbOdberatelMesto.Text
.Item("odberatel_psc").Range.Text = txbOdberatelPSC.Text
.Item("odberatel_ic").Range.Text = txbOdberatelIC.Text
.Item("odberatel_dic").Range.Text = txbOdberatelDIC.Text
.Item("dodavatel_nazev").Range.Text = txbDodavatelJmeno.Text
.Item("dodavatel_ulice").Range.Text = txbDodavatelUlice.Text
.Item("dodavatel_mesto").Range.Text = txbDodavatelMesto.Text
.Item("dodavatel_psc").Range.Text = txbDodavatelPSC.Text
.Item("dodavatel_ic").Range.Text = txbDodavatelIC.Text
.Item("dodavatel_dic").Range.Text = txbDodavatelDIC.Text
.Item("vystavil_jmeno").Range.Text = txbVystavilJmeno.Text
.Item("vystavil_email").Range.Text = txbVystavilEmail.Text
.Item("vystavil_telefon").Range.Text = txbVystavilTelefon.Text
.Item("datum").Range.Text = Now.ToString("d. MMMM yyyy")
.Item("firma").Range.Text = txbDodavatelJmeno.Text
If String.IsNullOrEmpty(PictureBox1.Tag) Then
.Item("vystavil_razitko").Range.Text = ""
Else
.Item("vystavil_razitko").Range.InlineShapes.AddPicture(PictureBox1.Tag)
End If
End With
...
V tomto poměrně dlouhém bloku provádíme náhrady textu v záložkách. ActiveDocument je aktivní dokument a je to ten náš otevřený. Každý dokument má kolekci Bookmarks, která obsahuje všechny záložky. Protože s ní budeme pracovat často, použijeme With blok, který zajistí, že pokud uvnitř něco začíná tečkou, bude se pravocat s podvlastnostmi w.ActiveDocument.Bookmarks. V kolekci máme vlastnost Item, které jako parametr předáme název záložky, který jsme zadali v dokumentu. Podle názvu získáme celou záložku, a Range.Text nám umožní nastavit do této záložky text. Takto tedy vyplníme záložky hodnotami z textových polí, do záložky datum vložíme aktuální datum.
Jediné, co je jiné, je razítko. Do dokumentu potřebujeme vložit obrázek. Pomocí String.IsNullOrEmpty zjistíme, jestli je hodnota vlastnosti Tag v komponentě PictureBox1 prázdná. Pokud je, znamená to, že jsme obrázek neurčili. Pokud není, tak obsahuje cestu k souboru obrázku, který se má použít jako podpis. Pokud tedy je vybrán podpis, pomocí zavolání InlineShapes.AddPicture přidáme na místo záložky obrázek. Jako parametr se zadá cesta k souboru. Tím jsme tedy dosadili potřebné texty na potřebná místa. Nyní vyplníme položky faktury do tabulky.
...
Dim suma As Double = 0
For i As Integer = 0 To DataGridView1.Rows.Count - 2
With w.ActiveDocument.Tables(2).Rows.Add()
.Shading.BackgroundPatternColor = Microsoft.Office.Interop.Word.WdColor.wdColorWhite
.Range.Font.Bold = 0
.Cells(1).Range.Text = DataGridView1.Rows(i).Cells(0).Value
.Cells(2).Range.Text = DataGridView1.Rows(i).Cells(1).Value
.Cells(3).Range.Text = String.Format("{0:c2}", DataGridView1.Rows(i).Cells(2).Value)
.Cells(4).Range.Text = String.Format("{0:c2}", DataGridView1.Rows(i).Cells(3).Value)
.Cells(1).Range.Paragraphs.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphLeft
.Cells(2).Range.Paragraphs.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphRight
.Cells(3).Range.Paragraphs.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphRight
.Cells(4).Range.Paragraphs.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphRight
suma += Double.Parse(DataGridView1.Rows(i).Cells(2).Value, Globalization.NumberStyles.Currency) * Integer.Parse(DataGridView1.Rows(i).Cells(1).Value)
End With
Next
...
Nyní projdeme všechny řádky komponenty DataGridView, ve které máme zadány položky naší faktury. Všimněte si, že cyklus běží od 0 do DataGridView1.Rows.Count - 2, tedy do počtu řádků zmenšeného o 2. To proto, že poslední řádek je vždycky prázdný a je určen k vložení nového záznamu. Jakmile do něj začneme psát, ihned se přidá prázdný řádek další. Tento řádek tedy vynecháme.
Budeme pracovat s druhou tabulkou v dokumentu - jak už jsem psal dříve, adresy dodavatele a odběratele jsou v samostatné tabulce o dvou sloupcích a jednom řádku. Tabulka pro položky je tedy v ActiveDocument.Tables(2) - v této knihovně narozdíl od zbytku .NET frameworku číslujeme od jedničky. Do této tabulky přidáme zavoláním Rows.Add nový řádek a to celé dáme do With bloku. Cokoliv uvnitř začne tečkou, bude tedy pracovat s tímto nově přidaným řádkem.
Nejprve přes Shading.BackgroundPatternColor nastavíme barvu pozadí řádku na bílou - nový řádek totiž vždy kopíruje styl toho předchozího, což je v našem případě světle modré záhlaví. Dále nastavíme Range.Font.Bold na hodnotu 0, čímž vypneme tučné písmo (záhlaví totiž tučné je). Pak již jen vypisujeme hodnoty buněk - kolekce Cells přistupuje k jednotlivým buňkám a opět pomocí Range.Text nastavíme jejich hodnoty. Aby se čísla a ceny zarovnaly správně, nastavíme jim vlastnost Range.Paragraphs.Alignment na příslušné hodnoty,; název položky zarovnáváme doleva, množství a ceny doprava.
Nakonec ještě do proměnné suma přičteme celkovou cenu za položku, tedy množství vynásobené jednotkovou cenou. Protože cena v tabulce je již formátovaná jako měna (např. 13,25 Kč), pomocí Double.Parse z tohoto řetězce získáme původní desetinné číslo. Sumu počítáme proto, že chceme pod položky přidat ještě jeden řádek s celkovou cenou bez DPH i včetně.
...
With w.ActiveDocument.Tables(2).Rows.Add()
.Shading.BackgroundPatternColor = Microsoft.Office.Interop.Word.WdColor.wdColorGray10
.Range.Font.Bold = 1
.Cells(1).Range.Text = "CELKEM"
.Cells(2).Range.Text = ""
.Cells(3).Range.Text = String.Format("{0:c2}", suma)
.Cells(4).Range.Text = String.Format("{0:c2}", suma * 1.19)
.Cells(1).Range.Paragraphs.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphLeft
.Cells(3).Range.Paragraphs.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphRight
.Cells(4).Range.Paragraphs.Alignment = Microsoft.Office.Interop.Word.WdParagraphAlignment.wdAlignParagraphRight
End With
...
Tento blok je velmi podobný - přidá do druhé tabulky další řádek, nastaví mu tentokrát pozadí na 15% šedou, zapne tučné písmo, a vyplní celkové ceny bez DPH i s DPH. Provede i zarovnání. Tím je dokument hotov, tedy až na poslední věc - uložení a zobrazení výsledku.
...
Me.Enabled = True
SaveFileDialog1.FileName = IO.Path.Combine(My.Computer.FileSystem.SpecialDirectories.MyDocuments, "Faktura_" & Now.ToString("yyyy-MM-dd") & ".doc")
If SaveFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
w.ActiveDocument.SaveAs(SaveFileDialog1.FileName)
Me.WindowState = FormWindowState.Minimized
w.Visible = True
Else
w.Quit(False)
End If
End Sub
Generování je hotovo, takže již můžeme povolit ovládací prvky na formuláři. Dále do dialogu pro uložení souboru vygenerujeme nějaký název souboru - Faktura_Datum.doc a umístíme jej do složky Dokumenty (nebudeme rozhodně vypisovat cestu do dokumentů natvrdo, v každé verzi Windows je totiž trochu někde jinde, místo toho použijeme My.Computer.FileSystem.SpecialDirectories.MyDocuments; zde máme i další cesty, použili jsme už cestu do složky Obrázky).
Cestu už máme, zobrazíme tedy dialog pro uložení souboru. Pokud uživatel kliknul na OK, dokument tam metodou Save uložíme, pak zminimalizujeme okno naší aplikace a objektu aplikace Word nastavíme hodnotu Visible na True, čímž aplikaci Word normálně zobrazíme. Pokud uživatel kliknul při uložení na tlačítko Storno, zavoláme Quit, abychom Word ukončili. Jako parametr předáme False, aby se Word neptal na uložení změn v dokumentu. To je celé, výsledek vypadá asi takto:
Fakturu můžeme snadno vytisknout, uložit nebo odeslat e-mailem. Toto řešení samozřejmě vyžaduje nainstalovanou aplikaci Microsoft Word. Základním využitým principem je vkládání textů, případně obrázků, na místa předem určených záložkami, a dále přidání řádků do tabulky a vyplnění jejích buněk. To vše díky promyšlenému objektovému modelu aplikace Microsoft Word. Podobným způsobem lze generovat tabulky v aplikaci Microsoft Excel, opět stačí přidat jedinou knihovnu. Možnosti jsou opravdu veliké. Celá aplikace pro vytváření faktur je ke stažení, jen upozorňuji, že udaje o firmách jsou smyšlené a odráží mé momentální psychické rozpoložení.
Kompatibilita jednotlivých verzí
Dokud aplikaci používáte jen pro vlastní účely, je vše v pořádku. Potíž ale je, pokud chcete aplikaci nasadit na počítače s jinými verzemi Office. Ty si již rozumět nemusí. Obecně se doporučuje přidat do projektu knihovny té nejstarší verze Office, která je k dispozici. Další verze by totiž měly být zpětně kompatibilní. Pokud ale mám pouze Office 2007, pak pravděpodobně aplikace spustit na starších nepůjde, selže totiž kontrola verzí knihoven. Takže lepší je vyvíjet pro verze starší.
Pokud Vám aplikace spustit nepůjde, otevřete projekt ve Visual Studiu, ve vlastnostech projektu odeberte reference na knihovny Wordu, a přidejte je znovu. Je to tou kontrolou verzí knihoven, starší Office bohužel nemám.