V závěrečném dílu této trilogie doděláme Adresář, připravený na doplnění dalších funkcí podle vaší fantazie.
Dnes dostaneme Adresář do finální podoby. Jak jsem slíbil, program nebude umět ukládat změny v kontaktech, importovat je, ani exportovat, ale bude kompletní po WPF stránce.
Minule vytvořené okno OknoBezOkna bude sloužit pouze jako takové spojení mezi okny na přidávání kontaktů a zbytkem programu. Teď si vytvoříme už tolikrát zmiňované dvě stránky s políčky na údaje o novém kontaktu. První stránku pojmenujeme třeba KontaktStr1, bude obsahovat pole pro křestní jméno, příjmení, e-mail adresu a webovou adresu. Popis, jak založit nový XAML soubor, už přeskočím (viz minulý díl), jen zmíním, že bude opět podle šablony Page Function a nezapomeňte opět změnit TypeArguments na Object!
<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.KontaktStr1"
x:TypeArguments="sys:Object"
Title="Nový kontakt - 1">
<Grid Name ="HlavniMrizka"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<!-- Popisky jednotlivých polí -->
<TextBlock Width="200" Height="30" Grid.Column="0" Grid.Row="1"
Text=" Křestní jméno" />
<TextBlock Width="200" Height="30" Grid.Column="0" Grid.Row="2"
Text=" Příjmení" />
<TextBlock Width="200" Height="30" Grid.Column="0" Grid.Row="3"
Text=" E-mail adresa" />
<TextBlock Width="200" Height="30" Grid.Column="0" Grid.Row="4"
Text=" WWW adresa" />
<!-- Vstupní pole -->
<TextBox Name="txtJmenoKrestni" Width="200" Height="30"
Grid.Column="1" Grid.Row="1"
Text="{Binding Path=JmenoKrestni, Mode=TwoWay}"/>
<TextBox Name="txtJmenoPrijmeni" Width="200" Height="30"
Grid.Column="1" Grid.Row="2"
Text="{Binding Path=JmenoPrijmeni, Mode=TwoWay}"/>
<TextBox Name="txtEmail" Width="200" Height="30"
Grid.Column="1" Grid.Row="3"
Text="{Binding Path=Email, Mode=TwoWay}"/>
<TextBox Name="txtHomePage" Width="200" Height="30"
Grid.Column="1" Grid.Row="4"
Text="{Binding Path=HomePage, Mode=TwoWay}"/>
<Button Name="tlHomePage" Width="200" Height="30"
Grid.Column="1" Grid.Row="4"
Content="{Binding Path=HomePage, Mode=TwoWay}"
Click="Jdi_WWW"/>
<Button Name="tlEmail" Width="200" Height="30"
Grid.Column="1" Grid.Row="3"
Content="{Binding Path=Email, Mode=TwoWay}"
Click="PoslatEmail"/>
<!-- Dolní navigační tlačítka -->
<DockPanel Name="Panel1" Grid.Column="0" Grid.Row="5" >
<Button Name="tlDalsi" Width="100" Height="30"
Click="Dalsi_Klik" Content="Další" />
<Button Name="tlZrusit" Width="100" Height="30"
Click="Zrusit_Klik" Content="Zrušit" />
</DockPanel>
<DockPanel Name="Panel2" Grid.Column="1" Grid.Row="5">
<Button Name="tlHotovo" Width="100" Height="30"
Click="Hotovo_Klik" Content="Hotovo"/>
</DockPanel>
</Grid>
</PageFunction>
V kódu není nic, co bychom už neznali. Jako základ nám poslouží mřížka s dvěma sloupci a šesti řádky. V levém sloupci v každém řádku je popisek ve formě TextBlocku a k němu odpovídající TextBox, resp. tlačítko (Button). Ještě si všimněte, jak je každý prvek z druhého sloupce hezky provázán se svojí vlastností, využíváme tzv. TwoWay Binding – pokud se změní obsah TextBoxu, automaticky se na stejnou hodnotu přepíše i jeho vlastnost. To samé by se stalo s TextBoxem, kdybychom nějak externě změnili obsah vlastnosti.
Přesuneme se do „logické“ části tohoto souboru, do KontaktStr1.xaml.cs. Pokud jste už tak neučinili automaticky, změňte opět typový argument ze String na Object.
Toto okno budeme chtít zobrazovat ve dvou módech, které rozlišíme podle booleanovské hodnoty. Jedním módem bude mód Úprav, druhý mód bude Čtení. První ze jmenovaných využijeme v tom, co teď děláme (přidávač kontaktů), a Čtení aplikujeme pro pravou část hlavního okna aplikace, kde se objeví detaily o kontaktu, který si uživatel zvolí v ListBoxu vlevo. Tam zobrazené informace totiž budou pouze pro čtení, aby je uživatel mohl upravit, bude muset kliknout na některé tlačítko z nabídky.
// Vytvoří první stránku přidávače, argument mod_uprav
// určí mód použití
public KontaktStr1(bool mod_uprav, int cisloPredmetu)
{
InitializeComponent();
this.KeepAlive = true;
// pokud je v módu čtení...
if (mod_uprav == false)
{
SeznamKontaktu sk =
Application.Current.Properties["SeznamKontaktu"]
as SeznamKontaktu;
// pokud obsahuje seznam kontaktů alespoň jeden zápis, pomocí
// DataContextu vyplní jednotlivé TextBoxy a...
if (sk != null && sk.Count > 0)
{
Kontakt k = sk[cisloPredmetu];
this.HlavniMrizka.DataContext = k;
// ... a nastaví je jen pro čtení (ReadOnly)
this.txtEmail.IsEnabled = false;
this.txtJmenoKrestni.IsEnabled = false;
this.txtJmenoPrijmeni.IsEnabled = false;
this.Panel1.Visibility = Visibility.Hidden;
this.Panel2.Visibility = Visibility.Hidden;
}
}
else
{
this.tlHomePage.Visibility = Visibility.Hidden;
this.tlEmail.Visibility = Visibility.Hidden;
}
}
Pokud se teď budete pokoušet aplikaci zkompilovat, mělo by vám Visual Studio vyhodit 5 chyb, jejich důvod je nad slunce jasný :-). Máme totiž v KontaktStr1 pět tlačítek a u každého Click událost – při kliknutí na dané tlačítko se zavolá určitá metoda. A my ty metody zatím nemáme. U těchto metod není moc co řešit, snad jen u jedné. Tou je Hotovo_Klik(), která má za úkol sesbírat zadané informace z TextBoxů a předat je „return handleru“ (uděláme ho za chvíli).
private void Zrusit_Klik(object sender, RoutedEventArgs e)
{
// zavře okno
((NavigationWindow)(this.Parent)).Close();
}
private void Hotovo_Klik(object sender, RoutedEventArgs e)
{
SeznamKontaktu sk = Application.Current.Properties["SeznamKontaktu"]
as SeznamKontaktu;
Kontakt k = new Kontakt();
k.JmenoKrestni = this.txtJmenoKrestni.Text;
k.JmenoPrijmeni = this.txtJmenoPrijmeni.Text;
k.HomePage = new Uri(this.txtHomePage.Text);
k.Email = this.txtEmail.Text;
OnReturn(new ReturnEventArgs<object>(k));
}
Naším dalším krokem bude udělání výše zmíněného return handleru. Dává se do volající třídy, v našem případě tedy bude v souboru OknoBezOkna.xaml.cs. Ještě navíc přidáme instanciaci třídy KontaktStr1 v metodě Nacteno. Jak už jsem kdysi dávno řekl, typový argument Object třídy PageFunction, od které dědí OknoBezOkna, naznačuje jakého typu budou vrácená data. Kompletní soubor OknoBezOkna.xaml.cs by mohl vypadat takhle:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Navigation;
namespace Adresar
{
public partial class OknoBezOkna : PageFunction<Object>
{
public OknoBezOkna()
{
}
// Handler "Nacteno" se zavola automaticky po vytvoření okna
private void Nacteno(object sender, RoutedEventArgs e)
{
// "Nacteno" nasměruje toto okno na dvě "přidávací" stránky
KontaktStr1 pageFunction =
new KontaktStr1(true, 0); // pageFunction bude v módu úprav (mod_uprav==true)
pageFunction.Return +=
new ReturnEventHandler<object>(pageFunction1_VratitHodnoty);
NavigationService.GetNavigationService(this).
Navigate(pageFunction);
}
// Return handler pro KontaktStr1
void pageFunction1_VratitHodnoty(object sender, ReturnEventArgs<object> e)
{
SeznamKontaktu sk =
Application.Current.Properties["SeznamKontaktu"]
as SeznamKontaktu;
Kontakt k = e.Result as Kontakt; //nastaví to, co dostane (Result), jako Kontakt
// nakonec to nejdůležitější - přidání kontaktu do seznamu
sk.Add(k);
}
}
}
Pomocí známého Loaded teď už jen přidáme do XAML části OknaBezOkna event handler Nacteno:
<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"
Loaded="Nacteno"
Title="OknoBezOkna">
</PageFunction>
Když budeme mít informace o kontaktu v módu Čtení, budeme chtít, aby e-mail a WWW adresa vypadaly jako tlačítka. Kliknutí na e-mail bude mít za následek otevření výchozího e-mail klienta, WWW adresa neotevře prohlížeč, jak byste asi čekali, ale vykreslí stránku do pravé části hlavního okna (kde budou i zmíněné detaily o kontaktu, pomocí navigačních šipek se ale budete moci mezi „prohlížečem“ a detaily přesunovat). V obou případech ale uděláme URI a pak na něj odlišným způsobem odkážeme. Následující kód přidáme do KontaktStr1.xaml.cs.
private void Jdi_WWW(object sender, RoutedEventArgs e)
{
if (this.txtHomePage != null) // Pokud v TextBoxu něco je...
{
NavigationService ns =
NavigationService.GetNavigationService(this);
// bude navigovat na zadaný odkaz, pokud bude
// ve tvaru http://, inteligentní WPF vytvoří "prohlížeč",
// ve kterém otevře zadaný odkaz
ns.Navigate(new Uri(this.txtHomePage.Text));
}
}
private void PoslatEmail(object sender, RoutedEventArgs e)
{
if (this.txtEmail != null)
{
NavigationService ns =
NavigationService.GetNavigationService(this);
ns.Navigate(new Uri("mailto:" + this.txtEmail.Text));
}
}
Tak jako jsme udělali KontaktStr1, teď uděláme KontaktStr2, bude obsahovat pouze dva TextBoxy na domovní a pracovní adresu a tlačítka Zpět, Zrušit, Hotovo.
<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.KontaktStr2"
x:TypeArguments="sys:Object"
Title="Nový kontakt - 2">
<Grid
Name="HlavniMrizka"
VerticalAlignment="Center"
HorizontalAlignment="Center" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="110"/>
<RowDefinition Height="110"/>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<!-- Popisky -->
<TextBlock Width="100" Height="30" Grid.Column="0" Grid.Row="1"
Text="Adresa do práce" />
<TextBlock Width="100" Height="30" Grid.Column="0" Grid.Row="2"
Text="Adresa domů" />
<!-- Vstupní pole -->
<TextBox Name="txtAdresaDomu" Width="200" Height="100"
Grid.Column="1" Grid.Row="1" TextWrapping="Wrap"
Text="{Binding Path=AdresaDomu, Mode=TwoWay}"/>
<TextBox Name="txtAdresaZamestnani" Width="200" Height="100"
Grid.Column="1" Grid.Row="2" TextWrapping="Wrap"
Text="{Binding Path=AdresaZamestnani, Mode=TwoWay}"/>
<DockPanel Name="Panel1" Grid.Column="0" Grid.Row="3" >
<Button Name="tlZpet" Width="100" Height="30"
Click="Zpet_Klik" Content="Zpět" />
<Button Name="tlZrusit" Width="100" Height="30"
Click="Zrusit_Klik" Content="Zrušit" />
</DockPanel>
<DockPanel Name="Panel2" Grid.Column="1" Grid.Row="3">
<Button Name="tlHotovo" Width="100" Height="30"
Click="Hotovo_Klik" Content="Hotovo" />
</DockPanel>
</Grid>
</PageFunction>
Kromě TextWrapping není v tomto kódu nic, co bychom už neznali. TextWrapping je jednoduše zalamování textu, pokud je nastaven na „Wrap“, text se zalamuje, pokud není nastaven, nebo je nastaven na „NoWrap“, nezalamuje se. Logická část tohoto souboru je vážně snadná, však se podívejte sami.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Adresar
{
public partial class KontaktStr2 : PageFunction<Object>
{
public KontaktStr2()
{
InitializeComponent();
}
// Vytvoří KontaktStr2, podle zadaného módu, protože
// ho nebudeme zobrazovat na hlavním okně, jako je tomu u první stránky,
// druhý konstruktor nepřijímá jako parametr index kontaktu v seznamu
public KontaktStr2(bool mod_uprav)
{
InitializeComponent();
if (mod_uprav == false)
{
SeznamKontaktu sk =
Application.Current.Properties["SeznamKontaktu"]
as SeznamKontaktu;
Kontakt k = sk[0];
this.HlavniMrizka.DataContext = k;
this.HlavniMrizka.IsEnabled = false;
this.Panel1.Visibility = Visibility.Hidden;
this.Panel2.Visibility = Visibility.Hidden;
}
}
// Vrátí uživatele na KontaktStr1
private void Zpet_Klik(object sender, RoutedEventArgs e)
{
NavigationService ns =
NavigationService.GetNavigationService(this);
ns.GoBack();
}
// Zavře okno
private void Zrusit_Klik(object sender, RoutedEventArgs e)
{
((NavigationWindow)(this.Parent)).Close();
}
// Když uživatel klikne na tlačítko Hotovo, program si vezme
// zadaná data a vrátí je volajícímu - KontaktStr1. Toto
// okno pak vezme i svoje data a všechno dohromady předá OknuBezOkna
private void Hotovo_Klik(object sender, RoutedEventArgs e)
{
Kontakt k = new Kontakt();
k.AdresaDomu = this.txtAdresaDomu.Text;
k.AdresaZamestnani = this.txtAdresaZamestnani.Text;
OnReturn(new ReturnEventArgs<object>(k));
// zavře přidávač
Window w = Application.Current.Properties["SpustitPruvodcePridani"]
as Window;
w.Close();
}
}
}
Teď tedy už máme kompletně hotovou druhou stránku přidávače, už se k ní nebudeme vracet. Musíme ji spojit dohromady s první stránkou, ale o to se už postará ona. Nejdřív do ní přidáme metodu Dalsi_Klik, která slouží k přesunu na druhou stránku, a v zápětí i return handler.
// Když uživatel klikne na tlačítko Další, tato metoda
// ho přesune na druhou stránku
private void Dalsi_Klik(object sender, RoutedEventArgs e)
{
NavigationService ns =
NavigationService.GetNavigationService(this);
KontaktStr2 pageFunction = new KontaktStr2();
pageFunction.Return += new
ReturnEventHandler<Object>(pageFunction2_VratitHodnoty);
ns.Navigate(pageFunction);
}
// Když druhá stránka pošle svoje data, tato
// metoda je přidá k datům z první stránky. Nakonec všechno dohromady
// pošle OknuBezOkna.
void pageFunction2_VratitHodnoty(object sender, ReturnEventArgs<object> e)
{
Kontakt k = e.Result as Kontakt;
k.JmenoKrestni = this.txtJmenoKrestni.Text;
k.JmenoPrijmeni = this.txtJmenoPrijmeni.Text;
k.HomePage = new Uri(this.txtHomePage.Text);
k.Email = this.txtEmail.Text;
OnReturn(new ReturnEventArgs<object>(k));
}
Stiskněte F5, klikněte někde na „Přidat kontakt“ a můžete se kochat. Součástí tohoto seriálu budou už pouze dvě věci, vše ostatní si už můžete dodělat sami, protože zbytek je už jen a jen C#.
Pamatujete, jak jsme si kdysi dávno předpřipravili metodu JeZvolenyKontakt() v souboru Window1.xaml.cs? Doděláme ji až teď, protože teprve nyní bude mít nějaký smysl. Když uživatel zvolí v ListBoxu vlevo nějaký kontakt, v pravé části okna se objeví detaily o něm. A tohle je docela zajímavá část, my totiž do té pravé části umístíme vlastně první okno přidávače v módu pro čtení (proto jsme dva módy vůbec definovali), který má nastavená tlačítka na Hidden (skryté). Takhle tedy metoda JeZvolenyKontakt() vypadá.
public void JeZvolenyKontakt(object sender, SelectionChangedEventArgs args)
{
// zobrazí KontaktStr1 v pravé části hlavního okna, v módu pro Čtení
KontaktStr1 pageFunction =
new KontaktStr1(false, vsechnyKontakty.SelectedIndex);
pageFunction.Return += new
System.Windows.Navigation.ReturnEventHandler<object>
(pageFunction0_VratitHodnoty);
this.Frame_Zbytek.Navigate(pageFunction);
// nastaví status lištu
SeznamKontaktu sk =
Application.Current.Properties["SeznamKontaktu"]
as SeznamKontaktu;
Kontakt k = sk[vsechnyKontakty.SelectedIndex];
this.tb.Text = k.JmenoKrestni + " " + k.JmenoPrijmeni;
}
Zase jsem použil věc, kterou nemáme ještě definovanou. Je jí pageFunction0_VratitHodnoty(), která bude prázdná, protože KontaktStr1 bude zobrazeno v módu pro čtení, kde se neočekává, že metoda bude někdy něco vracet.
// Return handler pro KontaktStr1, je prázdný, protože není co vracet,
// ale napsat ho musíme, protože je to povinný parametr
void pageFunction0_VratitHodnoty(object sender,
System.Windows.Navigation.ReturnEventArgs<object> e)
{
}
A máme hotovo. Zkuste si napsat fiktivní e-mail Ondrovi nebo otevřít jeho web. Ovšem pozor, pokud jste zadali nějakou nesmyslnou URL, jako jsem to udělal já, při kliknutí na tlačítko se vytvoří výjimka! Proto jsem adresu na doprovodném obrázku změnil na český Google. Další funkce, jako je ukládání seznamu kontaktů, dialogy pro import jinde uložených, editace kontaktů, propracovaný systém výjimek a další, jsou už jen na vás. My v tomto seriálu udělali to nejdůležitější – nakoukli jsme pod pokličku WPF a ukázali si některé zajímavé prvky.