Lekce 7 - Upomínač narozenin v C# .NET WPF - Logická vrstva
V minulé lekci, Upomínač narozenin v C# .NET WPF - Návrh oken, jsme začali pracovat na aplikaci k upomínání
narozenin v C# .NET WPF. Představili jsme si elementy
<StackPanel>
, <ListBox>
,
<Image>
, <Calendar>
a
<Label>
.
V dnešním WPF tutoriálu se budeme zabývat návrhem logické vrstvy.
Logická vrstva obsahuje třídy s logikou aplikace.
Budeme rozšiřovat naši aplikaci z lekce Upomínač narozenin v C# .NET WPF - Návrh oken.
Třída Osoba
V naší aplikaci budou zcela jistě figurovat osoby. Vytvořme jim tedy
třídu. Před třídu Osoba
si dáme modifikátor
public
.
Vlastnosti
Osoba bude mít 4 vlastnosti:
Jmeno
– jméno typustring
.Narozeniny
– narozeniny typuDateTime
.Vek
– věk typuint
.ZbyvaDni
– zbývající počet dní do nejbližších narozenin typuint
.
První dvě vlastnosti jsou jednoduché:
public string Jmeno { get; set; } public DateTime Narozeniny { get; set; }
Tyto vlastnosti nastavíme pomocí parametrického konstruktoru. Třída vypadá zatím takto:
public class Osoba { public string Jmeno { get; set; } public DateTime Narozeniny { get; set; } public Osoba(string jmeno, DateTime narozeniny) { Jmeno = jmeno; Narozeniny = narozeniny; } }
Další dvě vlastnosti nebudou pouhou proměnnou, ale budou obsahovat další logiku. Takové vlastnosti je potom možné pomocí bindingu jednoduše zobrazovat na formuláři.
Vlastnost Vek
Vlastnost vypočítá a vrátí aktuální věk osoby v celých letech.
Výpočet bohužel není jen o tom odečíst dva datumy, jelikož
TimeSpan
neumí zjistit počet let, pouze počet dní. Výpočet
věku tedy provedeme následujícím způsobem:
- Získáme aktuální datum (bez času) z vlastnosti
Today
na struktuřeDateTime
. - Věk spočítáme jako rozdíl roků v aktuálním datu a datu narozenin. Je jasné, že tento věk není přesný. Pokud jsme se narodili 1.2.1990 a je 1.1.2010, není nám 20 let, ale jen 19. Z toho důvodu provedeme korekci.
- V případě, že je aktuální datum menší (dříve) než datum narození po přidání námi vypočítaných let, nastal výše uvedený případ a my věk o rok snížíme.
- Hotový věk vrátíme.
Kód vlastnosti je následující:
public int Vek { get { DateTime dnes = DateTime.Today; int vek = dnes.Year - Narozeniny.Year; if (dnes < Narozeniny.AddYears(vek)) vek--; return vek; } }
Vlastnost ZbyvaDni
Vlastnost nám vrátí kolik dní zbývá do narozenin osoby. To zjistíme následujícím způsobem:
- Získáme datum dalších narozenin přičtením věku
+ 1
k datu narození. - Data odečteme a celkový rozdíl ve dnech vrátíme.
Jelikož je rozdíl typu
double
, musíme ho zkonvertovat naint
.
Kód vlastnosti je následující:
public int ZbyvaDni { get { DateTime dalsiNarozeniny = Narozeniny.AddYears(Vek + 1); TimeSpan rozdil = dalsiNarozeniny - DateTime.Today; return Convert.ToInt32(rozdil.TotalDays); } }
Metoda ToString()
Jelikož budeme osoby vypisovat, přepíšeme třídě ještě metodu
ToString()
tak, aby vracela jméno osoby:
public override string ToString() { return Jmeno; }
Třída SpravceOsob
Další logickou komponentou aplikace bude správce osob. Třída se bude starat o osoby. Bude je umět přidávat, odebírat a jejich seznam ukládat do souboru a opětovně načíst. Konečně bude umět mezi osobami vyhledat tu, která má nejbližší narozeniny.
Přidejme si k projektu tedy třídu SpravceOsob
a učiňme ji
veřejnou.
Vlastnosti
Správce osob bude disponovat třemi veřejnými vlastnostmi.
Vlastnost Osoby
První je seznam osob typu
ObservableCollection
. S touto kolekcí jsme se v kurzu ještě
nesetkali. Jedná se o chytřejší List
, který na sobě umí
vyvolat událost změny v případě, že se změní jeho obsah. Díky tomuto
mechanismu se automaticky obnoví všechny prvky na formuláři, které mají
nastavený jako zdroj dat právě tuto kolekci
ObservableCollection
. Asi si dokážeme představit, že obnovovat
ručně desítky formulářových prvků v případě změny může být velice
nepřehledné. Jakmile v naší aplikaci přidáme novou osobu, bude ihned
vidět v seznamu osob aniž bychom ho obnovovali, obnoví se sám.
ObservableCollection()
inicializujeme v konstruktoru.
Pokud bychom chtěli implementovat i editaci osob, musela by
třída Osoba
implementovat rozhraní
INotifyPropertyChanged
. Jakákoli změna (např. jména) by se
poté automaticky projevila ve všech prvcích všech formulářů, kde osoba
figuruje. Tímto se však pro zjednodušení zabývat nebudeme.
Třída SpravceOsob
zatím vypadá takto:
public class SpravceOsob { public ObservableCollection<Osoba> Osoby { get; set; } public SpravceOsob() { Osoby = []; } }
Vlastnosti
DnesniDatum
a NejblizsiOsoba
Dalšími vlastnostmi jsou:
DnesniDatum
, která vrátí dnešní datum.NejblizsiOsoba
, která vrátí osobu s nejbližšími narozeninami.
Jejich kód je:
public Osoba? NejblizsiOsoba { get; set; } public DateTime DnesniDatum { get { return DateTime.Now; } }
Metody
Kromě přidávání a odebírání bude třída umět i nalézt osobu s nejbližšími narozeninami. Ukládání a načítání osob do/ze souboru necháme na později.
Metoda NajdiNejblizsi()
V privátní metodě NajdiNejblizsi()
najdeme a uložíme osobu,
která má nejbližší narozeniny. K vyhledání osoby v seznamu použijeme LINQ metodu OrderBy()
, která
osoby uspořádá podle toho, kolik dní zbývá do jejich narozenin. Výsledek
uložíme do kolekce, jejíž typ neuvedeme a použijeme místo toho klíčové
slovo var
, jak je v LINQ zvykem. Následně vrátíme první
osobu:
private void NajdiNejblizsi() { var serazeneOsoby = Osoby.OrderBy(o => o.ZbyvaDni); if (serazeneOsoby.Any()) NejblizsiOsoba = serazeneOsoby.First(); else NejblizsiOsoba = null; }
LINQ metodu First()
bychom měli volat jen tehdy,
když jsou v seznamu nějaké osoby. Proto se na to ptáme LINQ metodou
Any()
.
Metoda Pridej()
V metodě Pridej()
přidáme novou osobu do kolekce
ObservableCollection
. Jelikož osobu budeme přidávat
formulářem, hodí se nám, aby metoda brala v parametrech vlastnosti osoby a
na jejich základě vytvořila novou instanci. Z data narození uložíme pouze
část s datem, tedy bez času.
Před přidáním zkontrolujeme, zda jméno není příliš
krátké nebo není zadané datum v budoucnosti. Pokud
nějaká z těchto situací nastane, vyvoláme výjimku. V
našem případě se nám hodí výjimka ArgumentException
(chyba v
argumentu). Do konstruktoru výjimky zadáváme text chyby. Jakmile je výjimka
vyvolána, metoda již dále nepokračuje. Jak na chybu reagovat si ukážeme
až budeme metodu volat z formuláře.
Výjimky jsou jediným správným způsobem, jak pracovat s chybami v objektových aplikacích.
Při zadávání data ovládacím prvkem DatePicker
dostaneme
datum jako typ DateTime?
, který může nabývat hodnoty
null
. Pokud je v datu null
, znamená to, že nebylo
zadané a vyhodíme též výjimku. K hodnotě nullovatelného typu
přistupujeme přes vlastnost Value
. Na konci metody obnovíme
nejbližší osobu, protože to může být zrovna ta přidaná:
public void Pridej(string jmeno, DateTime? datumNarozeni) { if (jmeno.Length < 3) throw new ArgumentException("Jméno je příliš krátké"); if (datumNarozeni == null) throw new ArgumentException("Nebylo zadané datum narození"); if (datumNarozeni.Value.Date > DateTime.Today) throw new ArgumentException("Datum narození nesmí být v budoucnosti"); Osoba osoba = new Osoba(jmeno, datumNarozeni.Value.Date); Osoby.Add(osoba); NajdiNejblizsi(); }
Metoda Odeber()
V metodě odebereme osobu z kolekce ObservableCollection
.
Protože vždy budeme chtít odebírat již hotovou osobu, vezmeme v parametru
právě tu. Po odebrání opět obnovíme nejbližší osobu:
public void Odeber(Osoba osoba) { Osoby.Remove(osoba); NajdiNejblizsi(); }
V příští lekci, Upomínač narozenin v C# .NET WPF - Propojení vrstev, začneme propojovat logickou a prezentační
vrstvu aplikace k upomínání narozenin. Naučíme se výjimky, dialogy a
bindovat ObservableCollection
na element ListBox
.
Měl jsi s čímkoli problém? Zdrojový kód vzorové aplikace je ke stažení každých pár lekcí. Zatím pokračuj dál, a pak si svou aplikaci porovnej se vzorem a snadno oprav.