Vizuális programozás 1. A gyakorlat célja A gyakorlat célja a Könyvtár alkalmazás folytatása az előző gyakorlaton elkészített grafikus felület felhasználásával. Elsőként lemásoljuk az előző gyakorlat eredményeként elkészült http://johanyak.hu/files/u1/segedlet/vizualis_programozas/gyak_fel_08_konyvtar.zip projektet, majd a 7. gyakorlaton megismerteknek megfelelően létrehozzuk az adatbázist a lokális SQLEXPRESS szerveren. Ezt követően megnyitjuk a Konyvtar_FF nevű WPF alkalmazást. A gyakorlat során az alábbi ábrán látható négy entitáshoz kapcsolódó adatbevitelt fogjuk megoldani. 2. A feladatot megvalósító kód A középpontban a könyvek felvitele áll, a többi entitást ehhez kapcsolódóan fogjuk megadni. 2.1. Mentés és kilépés A feladatokat megoldó két eseménykezelő metódust a wndfoablak.xaml.cs állományban hozzuk létre. A mentés feladata az 1
entitáshalmazokon végrehajtott módosítások lementése az adatbázisba. private void mimentés_click(object sender, RoutedEventArgs e) conkonyvtar.savechanges(); private void mikilépés_click(object sender, RoutedEventArgs e) Application.Current.Shutdown(); 2.2. Könyvadatok rögzítése Elsőként meg kell jelenítenünk a főablakon belül a feladatot megoldó űrlapot. Ezt a menüponthoz kapcsolódó eseménykezelőben tesszük meg. private void mikönyvrögzít_click(object sender, RoutedEventArgs e) uckönyvrögzít.visibility = Visibility.Visible; Létrehozzuk az entitáskonténer objektumot, majd tároljuk a konténer objektum referenciáját az alkalmazás tulajdonságai között, így az minden ablakból/user control-ból elérhető lesz. /// Az entitáskonténer objektum. private conkonyvtar conkönyvtár; /// Az ablak konstruktora. public wndfőablak() InitializeComponent(); // Létrehozzuk az entitáskonténer objektumot. conkönyvtár = new conkonyvtar(); // Tároljuk a konténer objektum referenciáját az alkalmazás // tulajdonságai között, így az minden ablakból/user control-ból // elérhető lesz. Application.Current.Properties["conKönyvtár"] = conkönyvtár; Az űrlap megvalósításánál alapkoncepciónk az, hogy csak akkor történik adatrögzítés a memóriában levő entitáshalmazok valamelyikébe, ha a felhasználó kattint a Rögzít gombon, ezért kezdetben az 2
entitáshalmazon kívül kell ideiglenesen tárolnunk az újonnan felvitt kiadókat, szerzőket és könyveket. Emellett a könyvhöz tartozó szerzők és kiadók esetében tárolnunk kell, hogy ők új szerzők/kiadók-e. Az adatrögzítést szolgáló UserControl osztályában (uckonyvrogzit.xaml.cs) először létrehozzuk a feladat megoldásához szükséges adattagokat. /// Az entitáskonténer objektum. private conkonyvtar conkönyvtár; /// Az aktuális könyv objektum. private Konyv Könyv; /// A könyv kiadója egy új kiadó-e, vagy már szerepel az adatbázisban. private bool ÚjKiadó; /// A könyv szerzői egy új szerzők-e, vagy már szerepelnek az adatbázisban. private List<bool> ÚjSzerző; Alapértelmezésben feltételezzük, hogy úgy a kiadó, mint a szerzők már szerepelnek az adatbázisban. /// A usercontrol konstruktora. public uckönyvrögzít() InitializeComponent(); // Alapértelmezésben feltételezzük, hogy úgy a kiadó mint a szerzők már szerepelnek az // adatbázisban. ÚjKiadó = false; ÚjSzerző=new List<bool>(); Amikor az űrlap láthatóvá válik, kiolvassuk az alkalmazás tulajdonságai közül az entitáskonténer objektum referenciáját, létrehozunk egy könyv objektumot kezdőértékekkel, és az űrlap adatforrásaként megadjuk a könyv objektumot. A felület elkészítésekor definiált adatkötések tesztelése érdekében a könyv objektumot inicializálással hozzuk létre, úgy, hogy kezdőértékeket adunk a kapcsolódó objektumok számára is. /// Az űrlap láthatóvá válik. private void UserControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) 3
// Az entitás objektum referenciájának kiolvasása és eltárolása egy adattagban. conkönyvtár = Application.Current.Properties["conKönyvtár"] as conkonyvtar; // Könyv objektum létrehozása. Könyv = new Konyv() // Az alábbi inicializálás csak tesztelési célt szolgál. Kiado = new Kiado Név = "Springer", Székhely = "New York", Ár = 27106,Cím = "Artificial Immune Systems",KiadásiÉv = 2011, KonyvPeldanyok = new EntityCollection<KonyvPeldany> new KonyvPeldanyKönyvtáriAzonosító = "15687", Szerzok = new EntityCollection<Szerzo>new SzerzoVezetéknév ="Lió",Utónév = "Pietro" ; // Az űrlap adatforrásaként megadjuk a könyv objektumot. DataContext = Könyv; Amennyiben minden adat megjelenik helyesen, töröljük az inicializálást a kódból. Az adatrögzítés funkcionalitás megvalósítását a megoldandó feladatok szerint négy csoportba bontva tekintjük át. 2.2.1. Cím, Kiadási év és Ár A Cím, Kiadási év és Ár mezők egyszerűen kitölthetőek, és a felületet leíró XAML kódban már kötve lettek a megfelelő adattagokhoz. 4
Ellenőrzés Az ellenőrzést végző kóddal azt szeretnénk elérni, hogy amikor a felhasználó érvénytelen értéket ír be, akkor az adott szövegmező piros kerettel jelenjen meg. Továbbá a Rögzít gombon történő kattintáskor az érvénytelen szövegmező kapja meg az input fókuszt, és a gyorstippben jelenjen meg a hibaüzenet az alábbi mintáknak megfelelően. A cím, kiadási év és ár adatok ellenőrzéséhez létrehozunk három validációs szabályt. Ehhez definiálunk három leszármazott osztályt (vrcím, vrév és vrár) a ValidationRules osztályhoz, majd átdefiniáljuk bennük a Validate virtuális metódust. 5
public class vrcím:validationrule /// Ellenőrzi a Cím szerkesztőmezőbe beírt értéket. /// <param name="value">a szerkesztőmező tartalma.</param> /// <param name="cultureinfo">the culture to use in this rule.</param> /// <returns>egy <see cref="t:system.windows.controls.validationresult"/> objektum, ami jelzi /// az érvényességet (true/false), és tartalmazza a hibaüzenetet.</returns> 6
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureinfo) string Cím=value as string; // A cím mező nem állhat üresen. return Cím==null Cím.Length==0? new ValidationResult(false, "A cím megadása kötelező") : new ValidationResult(true, null); class vrév:validationrule /// Ellenőrzi az Év szerkesztőmezőbe beírt értéket. /// <param name="value">a szerkesztőmező tartalma.</param> /// <param name="cultureinfo">the culture to use in this rule.</param> /// <returns>egy <see cref="t:system.windows.controls.validationresult"/> objektum, ami jelzi /// az érvényességet (true/false) és tartalmazza a hibaüzenetet.</returns> public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureinfo) string sév = value as string; // Az év megadása kötelező. if (sév == null) return new ValidationResult(false, "A kiadási év megadása kötelező!"); // Az év pozitív egész kell legyen és kisebb vagy egyenlő mint az aktuális év. Int16 Év; bool siker = Int16.TryParse(sÉv, out Év) && Év >= 1 && Év <= DateTime.Now.Year; if (!siker) return new ValidationResult(false, "Az év 1 és " + DateTime.Now.Year + " közötti pozitív érték kell legyen!"); // A megadott érték érvényes. return new ValidationResult(true, null); class vrár:validationrule /// Ellenőrzi az Ár szerkesztőmezőbe beírt értéket. /// <param name="value">a szerkesztőmező tartalma.</param> /// <param name="cultureinfo">the culture to use in this rule.</param> /// <returns>egy <see cref="t:system.windows.controls.validationresult"/> objektum, ami jelzi /// az érvényességet (true/false) és tartalmazza a hibaüzenetet.</returns> public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureinfo) 7
string sár = value as string; // Az ár megadása kötelező. if(sár==null) return new ValidationResult(false,"Ár megadása kötelező!"); // Az ár pozitív egész kell legyen. Int16 Ár; bool siker = Int16.TryParse(sÁr, out Ár)&& Ár>=0; if(!siker) return new ValidationResult(false,"Az ár 0 és "+Int16.MaxValue+ " közötti pozitív érték kell legyen!"); // A megadott érték érvényes. return new ValidationResult(true,null); A szabályok végrehajtása (kiértékelése) érdekében néhány apróbb módosítás szükséges a UserControl XAML kódjában. Először a gyökértagben felvesszük a projektünk névterét, így lehetőségünk lesz arra, hogy hivatkozzunk a validációs szabály osztályokra. Következő lépésként átalakítjuk a három szövegmezőre vonatkozó részt úgy, hogy az adatkötés definíciójába beépítjük a validációs szabályok hivatkozását is. <TextBox x:name="tbkrcím" Width="298" Height="40" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" HorizontalAlignment="Left"> <TextBox.Text> <Binding Path="Cím" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:vrcím /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> 8
<TextBox x:name="tbkrkiadásiév" HorizontalAlignment="Left" Width="100" > <TextBox.Text> <Binding Path="KiadásiÉv" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:vrév /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <TextBox x:name="tbkrár" HorizontalAlignment="Left" Width="100"> <TextBox.Text> <Binding Path="Ár" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:vrár /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> 2.2.2. Példány hozzáadása A példány hozzáadása gombon történő kattintás engedélyezi a Könyvtári azonosító szövegmezőt és az alatta levő gombot, ahol a felhasználó felvihet egy újabb azonosítót, azaz könyvpéldányt. A gombon kattintva a példány azonosítója felhasználásával egy új KonyvPeldany objektumot hozunk létre, amit felveszünk a könyv objektum által hivatkozott KonyvPeldanyok gyűjteménybe. Az adatkötésnek köszönhetően az azonosító megjelenik a bal oldali listadobozban. A feladatot megvalósító két eseménykezelő (érintett nyomógombok eseménykezelői) az alábbiak. /// Kattintás a Példány hozzáadása gombon. private void btkrpéldányhozzáadása_click(object sender, RoutedEventArgs e) // A jobb oldali StackPanel engedélyezése. spkrkönyvtáriazonosító.isenabled = true; // A Példány hozzáadása gomb tiltása. 9
btkrpéldányhozzáadása.isenabled = false; // A könyvtári azonosító bevitelére szolgáló szövegmező ürítése. tbkrkönyvtáriazonosító.text = ""; /// Kattintás a Példányt rögzít gombon. private void btkrkarögzít_click(object sender, RoutedEventArgs e) // A jobb oldali StackPanel tiltása. spkrkönyvtáriazonosító.isenabled = false; // A Példány hozzáadása gomb engedélyezése. btkrpéldányhozzáadása.isenabled = true; // Új könyvpéldány hozzáadása a KonyvPeldanyok gyűjteményhez. Könyv.KonyvPeldanyok.Add( new KonyvPeldany KönyvtáriAzonosító = tbkrkönyvtáriazonosító.text ); 2.2.3. Kiadó megadása A USerControlon szereplő kiadó szövegmező (tbkrkiadó) csak olvasható. A Kiadó megadása/kiválasztása gombon kattintva először létrehozzuk a párbeszédablak objektumot. Ha a felhasználó a Rögzít nyomógombbal zárta be a párbeszédablakot, akkor kiolvassuk, hogy a megadott kiadó egy új kiadó-e (még nem szerepel az adatbázisban), kiolvassuk a felhasználó által megadott adatokból létrehozott kiadó objektum referenciáját, és gondoskodunk a kiadói adatok megjelenítéséről. /// Kattintás a Kiadó megadása/kiválasztása gombon. private void btkrkiadómegadása_click(object sender, RoutedEventArgs e) // Párbeszédablak objektum létrehozása. var wndkiadó = new wndkiadó(); // Párbeszédablak megjelenítése. var er = wndkiadó.showdialog(); // Kilépés, ha a felhasználó a Mégse gombbal zárta be a párbeszédablakot. if (er!= true) return; // Kiolvassuk, hogy a megadott kiadó egy új kiadó-e (még nem szerepel az adatbázisban). ÚjKiadó = wndkiadó.újkiadó; 10
// A felhasználó által megadott adatokból létrehozott kiadó objektum. Könyv.Kiado = wndkiadó.kiadó; // A kiadói adatok megjelenítése. tbkrkiadó.text = Könyv.KiadóLeírás; A párbeszédablak osztályában egy adattagra és két tulajdonságra lesz szükségünk. /// Az entitáskonténer objektum. private conkonyvtar conkönyvtár; /// A kiadó objektum. public Kiado Kiadó get; private set; /// A megadott kiadó egy új kiadó-e (még nem szerepel az adatbázisban)? public bool ÚjKiadó get; private set; Mindkét tulajdonság automatikus tulajdonság, a private megkötéssel azt érjük el, hogy csak az osztályon belül állítható be az értéke. A párbeszédablak konstruktorában kiolvassuk az entitás objektum referenciáját és eltároljuk azt egy adattagban. A párbeszédablakban egy legördülő listából választhatunk az adatbázisban szereplő kiadók közül. Ehhez a konstruktorban adatforrásként meg kell adnunk a kiadók entitáshalmazát. public wndkiadó() InitializeComponent(); // Az entitás konténer objektum referenciájának kiolvasása és eltárolása egy adattagban. conkönyvtár = Application.Current.Properties["conKönyvtár"] as conkonyvtar; // Adatforrás megadása a legördülő listához. cbkiadók.itemssource = conkönyvtár.kiadok; // Alapértelmezés szerint a kiadó már szerepel az adatbázisban. ÚjKiadó = false; 11
Amennyiben a felhasználó az Új kiadó megadása gombon kattint, akkor engedélyeznünk kell az új kiadó nevének és székhelyének megadását lehetővé tevő StackPanel-t. /// Kattintás az Új kiadó gombon. private void btújkiadó_click(object sender, RoutedEventArgs e) // Az új kiadó nevének és székhelyének megadását lehetővé tevő StackPanel engedélyezése. spkiadóadatok.isenabled = true; Amennyiben a felhasználó a rögzít gombon kattint, a StackPanel engedélyezettségi állapotától függően beállítjuk az ÚjKiadó logikai tulajdonság értékét, valamint vagy létrehozunk egy új kiadó objektumot a szerkesztőmezőkben levő értékek alapján vagy eltároljuk a kiválasztott kiadó objektum referenciáját. Végül bezárjuk a párbeszédablakot. /// Kattintás a Rögzít gombon. private void btkrögzít_click(object sender, RoutedEventArgs e) if(spkiadóadatok.isenabled) // Ha új kiadót adtunk meg, akkor létrehozunk hozzá egy új objektumot inicializálással. Kiadó=new KiadoNév = tbkiadónév.text,székhely = tbszékhely.text; ÚjKiadó = true; else // Ha egy létező kiadót választottunk, akkor tároljuk objektumának referenciáját. Kiadó = cbkiadók.selecteditem as Kiado; 12
ÚjKiadó = false; // Párbeszédablak bezárása. DialogResult = true; 2.2.4. Szerzők megadása A szerzők megadása sok tekintetben a kiadók megadásához hasonlóan történik. A könyv szerzőit egyenként adhatjuk meg. Szerző megadása gombon történő kattintás egy új ablak megjelenését idézi elő. A kiadó esetéhez képest itt az az eltérés, hogy úgy a szerző objektumok referenciáit, mint az új jellegükre vonatkozó információt itt egy-egy gyűjteményben tároljuk. /// Kattintás a Szerző megadása/kiválasztása gombon. /// <param name="sender">a nyomógomb objektum</param> private void btkrszerzőmegadása_click(object sender, RoutedEventArgs e) // Párbeszédablak objektum létrehozása. var wndszerző = new wndszerző(); // Párbeszédablak megjelenítése. var er = wndszerző.showdialog(); // Kilépés, ha a felhasználó a Mégse gombbal zárta be a párbeszédablakot. if (er!= true) return; // Kiolvassuk, hogy a megadott szerző egy új szerző-e (még nem szerepel az // adatbázisban), és tároljuk a logikai gyűjteményben. ÚjSzerző.Add(wndSzerző.ÚjSzerző); // A szerző objektumot hozzáadjuk a könyv szerzőinek gyűjteményéhez. Könyv.Szerzok.Add(wndSzerző.Szerző); A szerző megadására szolgáló párbeszédablak osztályában egy adattagra és két tulajdonságra lesz szükségünk. /// Az entitáskonténer objektum. private conkonyvtar conkönyvtár; /// A szerző objektum. public Szerzo Szerző get; private set; /// A megadott szerző egy új szerző-e (még nem szerepel az adatbázisban)? public bool ÚjSzerző get; private set; 13
Mindkét tulajdonság automatikus tulajdonság, a private megkötéssel azt érjük el, hogy csak az osztályon belül állítható be az értéke. A párbeszédablak konstruktorában kiolvassuk az entitás objektum referenciáját és eltároljuk azt egy adattagban. A párbeszédablakban egy legördülő listából választhatunk az adatbázisban szereplő szerzők közül. Ehhez a konstruktorban adatforrásként meg kell adnunk a kiadók entitáshalmazát. /// A párbeszédablak konstruktora. public wndszerző() InitializeComponent(); // Az entitás konténer objektum referenciájának kiolvasása és eltárolása egy adattagban. conkönyvtár = Application.Current.Properties["conKönyvtár"] as conkonyvtar; // Adatforrás megadása a legördülő listához. cbszerzők.itemssource = conkönyvtár.szerzok; // Alapértelmezés szerint a kiadó már szerepel az adatbázisban. ÚjSzerző = false; Amennyiben a felhasználó az Új szerző megadása gombon kattint, akkor engedélyeznünk kell az új kiadó nevének és székhelyének megadását lehetővé tevő StackPanel-t. /// Kattintás az Új szerző gombon. private void btújkiadó_click(object sender, RoutedEventArgs e) // Az új szerző adatainak megadását lehetővé tevő StackPanel engedélyezése. spszerződatok.isenabled = true; 14
Amennyiben a felhasználó a rögzít gombon kattint, a StackPanel engedélyezettségi állapotától függően beállítjuk az ÚjSzerző logikai tulajdonság értékét, valamint vagy létrehozunk egy új szerző objektumot a szerkesztőmezőkben levő értékek alapján vagy eltároljuk a kiválasztott szerző objektum referenciáját. Végül bezárjuk a párbeszédablakot. /// Kattintás a Rögzít gombon. private void btszrögzít_click(object sender, RoutedEventArgs e) // Ha új szerzőt adtunk meg, akkor létrehozunk hozzá egy új objektumot inicializálással. if(spszerződatok.isenabled) Szerző=new SzerzoVezetéknév = tbvezetéknév.text, Utónév = tbutónév.text; ÚjSzerző = true; else // Ha egy létező szerzőt választottunk, akkor tároljuk objektumának referenciáját. Szerző = cbszerzők.selecteditem as Szerzo; ÚjSzerző = false; // Párbeszédablak bezárása. DialogResult = true; Az adatkötésnek köszönhetően a főablakban a szerzők táblázatában megjelenik az új szerző. 2.2.5. Az űrlap mégse nyomógombja A Mégse nyomógombon történő kattintással azt jelzi a felhasználó, hogy nem kívánja megőrizni a felvitt adatokat. Ezért ekkor elrejtjük az űrlapot. /// Kattintás a mégse nyomógombon. 15
private void btkrmégse_click(object sender, RoutedEventArgs e) // Az űrlap elrejtése. Visibility = Visibility.Hidden; 2.2.5. Az űrlap rögzít nyomógombja A rögzít gombon történő kattintáskor először leellenőrizzük a cím kiadási és ár szövegmezőket. Hiba esetén jelzünk a felhasználónak, lehetőséget adva a javításra. Kiadó megadása kötelező, ennek hiányában, az input fókuszt a megfelelő nyomógombra helyezzük. Amennyiben vannak könyvpéldányok, akkor felvisszük azokat a könyvpéldányok entitás halmazába. A könyvet felvesszük a könyvek entitás halmazába. A kiadónál beállítjuk az aktuális könyvet. Ha ez egy új kiadó, akkor felvesszük a kiadók entitás halmazába. Sorra vesszük a szerzőket, és az aktuális szerzőnél beállítjuk az aktuális könyvet. Ha ez egy új szerző, akkor felvesszük a szerzők entitás halmazába. Végül elrejtjük az űrlapot. /// Kattintás Rögzít gombon. private void btkrrögzít_click(object sender, RoutedEventArgs e) // Ellenőrizzük a cím, kiadási év és ár adatokat. if (!Érvényes(tbKrCím)!Érvényes(tbKrKiadásiÉv)!Érvényes(tbKrÁr)) return; // Kiadó megadása kötelező. if (Könyv.Kiado==null) // Input fókusz a nyomógombra. btkrkiadómegadása.focus(); // Hibaüzenet gyorstippben! btkrkiadómegadása.tooltip = "Kiadó megadása kötelező!"; return; // Amennyiben vannak könyvpéldányok, akkor felvisszük azokat a könyvpéldányok // entitás halmazába. foreach (var x in Könyv.KonyvPeldanyok) conkönyvtár.konyvpeldanyok.addobject(x); // A könyvet felvesszük a könyvek entitás halmazába. conkönyvtár.konyvek.addobject(könyv); // A kiadónál beállítjuk az aktuális könyvet. Könyv.Kiado.Konyvek.Add(Könyv); // Ha ez egy új kiadó, akkor felvesszük a kiadók entitás halmazába. if (ÚjKiadó) conkönyvtár.kiadok.addobject(könyv.kiado); // Sorra vesszük a szerzőket. Ha nincs megadva szerző, akkor a Count értéke 0. for (int i = 0; i < Könyv.Szerzok.Count; i++) 16
// Az aktuális szerzőnél beállítjuk az aktuális könyvet. Könyv.Szerzok.ElementAt(i).Konyvek.Add(Könyv); // Ha ez egy új szerző, akkor felvesszük a szerzők entitás halmazába. if (ÚjSzerző[i]) conkönyvtár.szerzok.addobject(könyv.szerzok.elementat(i)); // Az űrlap elrejtése. Visibility = Visibility.Hidden; /// Ellenőrzi a UserControl-on megadott adatok érvényességét. /// <returns>true, ha minden adat érvényes, és false egyébként.</returns> /// <param name="tb">az ellenőrzött TextBox.</param> private bool Érvényes(TextBox tb) // Mivel a szövegmezőkben úgy is lehet érvénytelen adat, ha a felhasználó // be se lépett az adott mezőbe, ezért először kikényszerítjük az ellenőrző // szabály kiértékelését. tb.getbindingexpression(textbox.textproperty).updatesource(); // Ha volt hiba if (tb.getbindingexpression(textbox.textproperty).haserror) // A szövegmező gyorstippjébe tesszük a hibaüzenetet. tb.tooltip = tb.getbindingexpression(textbox.textproperty).validationerror.errorcontent; // Az input fókuszt a szövegmezőre tesszük. tb.focus(); // Hamis visszatérési értékkel jelezzük, hogy hiba volt. return false; // Nem volt hiba, a megadott adat érvényes. return true; 3. További feladat A két párbeszédablaknál az ellenőrzés megvalósítása. 17