Dnes se zaměříme hlavně na programování logiky WPF adresáře, kterému jsme minule stihli udělat pouze design.
Po menší pauze budeme pokračovat v tvorbě Adresáře; v minulém dílu jsme skončili doděláním jeho designu, dnes se ke slovu dostane hlavně C#, a tedy logika programu.
Pokračujeme
Na konci minulého dílu jsem vyzval zájemce, aby si přidali do Windows1.xaml.cs prázdné metody nutné pro spuštění programu spustit. Kdo si je nepřidal, tak se dočká právě teď, protože tím dnes začneme.
public void NacteneOkno(object sender, RoutedEventArgs e)
{
//Zavolá se při načtení Window1, nastaví kolekci SeznamKontaktu jako zdroj
//pro data
}
public void NeniImplementovano(object sender, RoutedEventArgs e)
{
//Vyvolá MessageBox se zprávou, že daná funkce ještě není implementována
MessageBox.Show("Tato funkce není implementována.","F-ce není!",
MessageBoxButton.OK,MessageBoxImage.Error);
}
public void SpustitPruvodcePridani(object sender, RoutedEventArgs e)
{
//Tato metoda se zavolá, když uživatel klikne na Přidat kontakt (Nový kontakt, atd.)
}
public void Konec(object sender, RoutedEventArgs e)
{
//Při zavolání ukončí aplikaci
this.Close();
}
public void JeZvolenyKontakt(object sender, SelectionChangedEventArgs args)
{
//Spustí se, když uživatel klikne na kontakt v ListBoxu
}
Přidáním tohoto kódu se dostanete do fáze, ze které pochází screen z konce minulého dílu. Nebude na škodu, když ho tady znovu ukážu.
Načtení kontaktů
Po spuštění musí náš program načíst informace o všech kontaktech ze souboru kontakty.txt, proto jsme ho taky v minulém dílu udělali. Otevřeme si App.xaml.cs a napíšeme si „čtecí“ metodu NactiKontaktyZeSouboru() a metodu, která jednotlivé řádky „rozseká“ na jednotlivé údaje.
private SeznamKontaktu NactiKontaktyZeSoubor()
{
//Načte informace ze souboru
SeznamKontaktu seznamKontaktu = new SeznamKontaktu();
//Musíme vytvořit instanci StreamReaderu, abychom mohli číst soubory
// >> na začátek dokumentu přidat "using System.IO;"!
using (StreamReader sr = new StreamReader("kontakty.txt"))
{
String radek;
//Bude číst data z "kontakty.txt", dokud nedojde na prázdný řádek
while ((radek = sr.ReadLine()) != null)
{
seznamKontaktu.Add(NactiRadekKontakt(radek));
}
}
return seznamKontaktu;
}
private Kontakt NactiRadekKontakt(string radek)
{
//Rozkouskuje jednotlivé řádky podle znaku ";"
string[] znaky = radek.Split(new char[] { ';' });
if (znaky.Length != 6)
{
//Na jednom řádku musí být 6 parametrů!
throw new ApplicationException(
String.Format("Chyba!. " +
"Požadovaných parametrů: {0}; Momentálně parametrů: {1}", 6,
znaky.Length));
}
Kontakt kontakt = new Kontakt();
//Pěkně přiřadíme jednotlivé úseky oddělené ";" do proměnných
kontakt.FirstName = znaky[0];
kontakt.LastName = znaky[1];
kontakt.EmailAddress = znaky[2];
kontakt.HomePage = (String.IsNullOrEmpty(znaky[3]) ?
null :
new Uri(znaky[3], UriKind.Absolute));
kontakt.HomeAddress = znaky[4];
kontakt.BusinessAddress = znaky[5];
return kontakt;
}
Jak už píšu v komentáři uvnitř kódu, na začátek nezapomeňte přidat odkaz na namespace System.IO, protože náš program využívá funkci StreamReader pro práci se soubory! Teď upravíme metodu StartAplikace(), aby přijímala výsledky vrácené metodou NactiRadekKontakt():
void StartProgramu(object sender, StartupEventArgs args)
{
Window1 hlavniOkno = new Window1();
hlavniOkno.WindowStartupLocation = WindowStartupLocation.CenterScreen;
SeznamKontaktu seznamKontaktu = NactiKontaktyZeSoubor();
this.Properties["SeznamKontaktu"] = seznamKontaktu;
hlavniOkno.Show();
}
Když teď aplikaci zkompilujeme a spustíme, tak navzdory velkým změnám v kódu pořád neuvidíme žádnou změnu očima, tento nedostatek hned teď „opravíme“.
Vazba ListBoxu s kontakty.txt
WPF poskytuje několik skvělých možností pro práci s daty v našich aplikacích. Jednotlivé prvky UI mohou být provázány s nejrůznějšími typy dat. Využijeme rozhraní IDataSource, které implementuje například třídy ObjectDataProvider a XmlDataProvider, které umožňují vzít nějaká data (objekt, XML) a my je můžeme použít jako zdroj pro nějakou funkci. Nejdřív si nadefinujeme ObjectDataProvider, který pojmenujeme „SeznamKontaktu“, jako resource ve speciální sekci v našem Window1.xaml kódu.
Jednosměrná vazba
Všimli jste si, že se náš nový ObjectDataProvider jmenuje stejně jako třída s kolekcí na kontakty? To proto, že právě ona bude našemu DataProviderovi posílat data a on pak koncovému zákazníku – ListBoxu. Takže kód bude vypadat takhle:
<Window.Resources>
<ObjectDataProvider x:Key="ContactList"
MethodName="AddressBook.ContactList,AddressBook" />
</Window.Resources>
Atributy u MethodName jsou hned dva – první je název třídy a její namespace, druhý atribut je název celého sestavení. Jenom se ujistěte, že jste tento kód uvedli nad definici Gridu!
Styly
Trošku se posuneme dál, a to ke stylům. Ti, kteří už XAML někdy zkoušeli, se s nimi určitě do styku dostali, protože tato „látka“ patří k těm základním a velmi užitečným věcem. Zde má slovo „styl“ stejný význam jako kdekoliv jinde: Nadefinujete si věci jako velikost, barva, použité písmo, jeho velikost…třeba tlačítka, uložíte tyto vlastnosti do stylu a tím si uděláme jakousi šablonu. Kdykoliv budete chtít udělat stejné tlačítko, tak nemusíte všechny jeho parametry vypisovat znovu, ale jenom odkážete na vytvořenou šablonu.
Na stejném principu fungují i datové šablony (DataTemplates) – nastavíme chování a „vzhled“ dat na jednom místě a nemusíme pořád to samé opisovat ve zbytku aplikace. Já osobně využívám formát zápisu TNazevSablony, kde T je zkratka pro anglické „template“ (není to nic oficiálního, jenom můj výmysl). Napíšeme si tedy šablonu TJmenoKrestni, která bude obsahovat TextBox spojený s vlastností JmenoKrestni, kterou máme už ze začátku prvního dílu.
<Window.Resources>
<ObjectDataProvider x:Key="ContactList"
MethodName="AddressBook.ContactList,AddressBook" />
<DataTemplate x:Key="TJmenoKrestni" >
<TextBlock Text="{Binding Path=JmenoKrestni}" />
</DataTemplate>
</Window.Resources>
Teď máme vše potřebné pro specifikování zdroje pro ListBox vsechnyKontakty.
<ListBox Name="vsechnyKontakty"
SelectionChanged="JeZvolenyKontakt"
ItemsSource="{Binding}"
ItemTemplate="{DynamicResource TJmenoKrestni}"
IsSynchronizedWithCurrentItem="True">
<ListBox.ContextMenu>
<ContextMenu>
<!--atd...-->
Máme vlastnost ItemsSource, která určuje typ zdroje, v našem případě je to vazba (Binding), dál šablonu, ze které budou přicházet data, a nakonec vlastnost IsSynchronizedWithCurrentItem, která, pokud je nastavená na true, zajistí, že jak v ListBoxu, tak v kolekci SeznamKontaktu budou vždy stejné údaje (neboli se budou synchronizovat).
Vše, co zbývá, je nastavení zdroje dat pro DockPanel_LevyPanel:
public void NacteneOkno(object sender, RoutedEventArgs e)
{
//Zavolá se při načtení Window1, nastaví kolekci SeznamKontaktu jako zdroj dat
DockPanel_LevyPanel.DataContext = Application.Current.Properties["SeznamKontaktu"];
}
Zkuste teď aplikaci zkompilovat a spustit, pokud je vše správně, měli byste v ListBoxu vidět křestní jména všech kontaktů z kontakty.txt.
Na závěr dnešního dílu si napíšeme část oken a metod pro přidávání kontaktů. Část proto, že je to dost rozsáhlá kapitola, vlastně největší, co v celém seriálu uděláme.
Pro přechod mezi stránkami „přidávače kontaktů“ se budeme pohybovat pomocí šipkové navigace známé z internetových prohlížečů a Windows Vista, vedle textu naleznete obrázek, jak to bude zhruba vypadat.
Jak to vlastně funguje? Do každé ze šipek se automaticky ukládá URI odkaz na předchozí stránku, po spuštění programu jsou obě šipky zašedlé, protože nemají na co odkazovat. Jakmile ale přejdeme na další stránku pomocí nějakého odkazu (v aplikaci to bude většinou tlačítko), levá šipka se zaktivuje. To samé se stane s pravou, pokud klikneme na levou. Určitě si to všichni dokážete představit a kdo ne, ať si otevře internetový prohlížeč. Systém si tedy vede jakousi historii procházení, pokud klikneme na zpáteční šipku, zavolá se metoda OnReturn(), která vykreslí poslední položku historie daného okna. Nemusíte zadávat žádné další parametry, které by určovaly odkazovanou stránku ani nic podobného, systém si sám vezme poslední položku z historie.
V této části se naučíme zacházet právě s touto užitečnou funkcí, která má podle mě co říct i v offline aplikacích a WPF její použití velmi usnadňuje.
Přidáváme kontakty
Náš PruvodcePridaniKontaktu se bude skládat ze dvou stránek (nejen kvůli prostoru, ale taky abychom si mohli ukázat šipkovou navigaci). Když uživatel klikne na tlačítko „Hotovo“, průvodce si vytáhne zadané informace z obou stránek a přidá je do kolekce a ta promítne do ListBoxu křestní jméno.
Jako první si napíšeme metodu SpustitPruvodcePridani(), která vyvolá první stránku nabídky pro přidání kontaktu a umístí ji na střed obrazovky. Dáme ji do souboru Window1.xaml.cs, ale proč, když vlastně budeme dělat nové okno, pro něj nevytvoříme nový soubor, třeba Window2.xaml.cs? My totiž nebudeme psát okno WinFormovského typu Form, ani nového Window, ale použijeme další z novinek, a to NavigationWindow. Toto okno dědí svoje vlastnosti od Window a navíc přidává již zmiňované navigační šipky. Nový soubor jsme nevytvořili kvůli tomu, že okno, které bychom v něm definovali, by neumělo vůbec nic a stejně dobře nám poslouží, aby se okno vytvořilo při zavolání metody SpustitPruvodcePridani(). NavigationWindow má speciální vlastnost Source, do které se ukládá zdroj, který se vykreslí při vytvoření okna. Pro zvídavé prozradím, u našeho okna bude tímto zdrojem „okno“ bez jakýchkoliv grafických prvků, jenom nastaví pár věcí a hned se objeví finální okno na přidávání kontaktů.
Teď je načase tu možná trochu chaotickou teorii převést do praxe, takže pro začátek kód:
//Spustí se, když uživatel klikne na kterékoliv z "Přidat kontakt" tlačítek
private void SpustitPruvodcePridani(object sender, RoutedEventArgs e)
{
NavigationWindow pruvodcePridaniKontaktu = new NavigationWindow();
//Nastavíme...
pruvodcePridaniKontaktu.Title = "Informace o kontaktu"; //...záhlaví okna
pruvodcePridaniKontaktu.Width = 500; //...šířku okna
pruvodcePridaniKontaktu.Height = 400; //...výšku okna
pruvodcePridaniKontaktu.WindowStyle = WindowStyle.ToolWindow; //...typ okna
pruvodcePridaniKontaktu.Name = "PruvodcePridaniKontaktu"; //...název okna
//První, co se udělá, je vykreslení okna, které zavolá další funkce, s charakteristickým
//názvem "Okno bez okna" :)
pruvodcePridaniKontaktu.Source = new Uri("OknoBezOkna.xaml",
UriKind.Relative); //typ odkazu bude Relative - závisí na původní adrese,
// /index.html je relativní, http://blabla.cz/index.html by byl odkaz absolutní
Application.Current.Properties["PruvodcePridaniKontaktu"]
= pruvodcePridaniKontaktu;
pruvodcePridaniKontaktu.WindowStartupLocation
= WindowStartupLocation.CenterScreen;
pruvodcePridaniKontaktu.ShowDialog();
}
Nezapomeňte přidat using direktivu pro namespace System.Windows.Navigation!
Z komentářů v kódu by mělo být pochopitelné, co a jak; jednoduše máme metodu, která, když ji zavoláme, vytvoří instanci NavigationWindow, nastaví jí ty a ty hodnoty a nakonec ji vykreslí (tedy, ShowDialog vykreslí to, co je uložené ve vlastnosti Source).
Do vlastnosti Source jsme ale uložili odkaz na něco, co zatím neexistuje, a proto to teď v posledních dvou minutách tohoto dílu napravíme. Nejdřív si v Solution Exploreru přidáme nový .xaml soubor (na projekt klikneme pravým tlačítkem myši, pak Add → New Item → Page Function (WPF)) a pojmenujeme ho OknoBezOkna.xaml. Kód, který jsme dostali, nám až na pár drobností vyhovuje. Takže toto dostaneme při vytvoření nového Page Function WPF okna:
<PageFunction
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Class="Adresar.OknoBezOkna"
x:TypeArguments="sys:String"
Title="OknoBezOkna">
<Grid>
</Grid>
</PageFunction>
Na první pohled je zřejmé, co změníme, neděláme totiž okno, které bude vidět, takže Grid skutečně nepotřebujeme. Další, už ne tak jasnou věcí, je změna TypeArguments. Ty určují, co bude dané okno vracet za datový typ, a my String přepíšeme na univerzální Object. Teď bude naše XAML část kódu vypadat nějak takhle:
<PageFunction
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Class="Adresar.OknoBezOkna"
x:TypeArguments="sys:Object"
Title="OknoBezOkna">
</PageFunction>
A u toho dnes skončíme. Pokud chcete, aby byla aplikace zkompilovatelná, proveďte stejnou změnu návratového typu i v hlavičce třídy OknoBezOkna (přepište String na Object).
Příště na vás bude připraven již poslední článek této článkové trilogie.