Vizuális programozás gyakorlat Készítsen egy Windows Presentation Foundation alkalmazást, ami a közismert, képeken alapuló memóriajáték egy egyszerű változatát valósítja meg. A program funkcionalitása a következő: A Kever menüpont hatására az induláskor automatikusan betöltött négy pár képet véletlenszerű elrendezésben megjeleníti. Várakozik két másodpercet, majd a képek helyett zöldre festett téglalapokat jelenít meg (hatter.jpg). A Kérdez menüpont hatására egy új ablakot jelenít meg Kérdés fejléccel. Az ablakban az előzőekben látható négy képtípus valamelyike jelenik meg véletlenszerűen. A felhasználó kiválasztja, hogy szerinte mely helyeken fordult elő a főablakban a kép, majd kattint az OK gombon. Egy lehetséges megoldást mutat a jobb oldali ábra. Az OK gombon történő kattintást követően eltűnik a Kérdez ablak, és a válasz helyességétől függően a mellékelt két üzenetablak egyike jelenik meg, majd újból láthatóvá válik a nyolc kép úgy, ahogy azt a legelső ábrán láthatjuk.
Tanácsok a megvalósításhoz A játékhoz szükséges négy kép és a háttérkép lemásolható a t:\info\johanyák Csaba\Rajzfilmfigurak\ címről. Hozzunk létre egy WPF alkalmazást úgy, hogy a megoldás és a projekt neve legyen Memoriajatek. A felület kialakítása Az ablak osztályát nevezzük át wndfőablakra, míg annak állományneve legyen wndfoablak alakú. Az ablak fejlécében a Memóriajáték felirat jelenjen meg. A menü négy pontot tartalmazzon az alábbi ábrának megfelelően. Minden menüponthoz generáltassunk egy Click eseménykezelő vázat a Visual Studioval. Az ablak menüsor alatti részében egy két soros és négy oszlopos rácsot helyezzünk el. A rács minden cellájába egy Image komponens kerüljön úgy, hogy egy 5 pontos margó kihagyásával töltse ki a cellát. A felületet leíró XAML kód az alábbi <Window x:class="memoriajatek.wndfőablak" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Memóriajáték" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30" /> <RowDefinition Height="*"/>
</Grid.RowDefinitions> <Menu x:name="mnfőmenü" Height="30" HorizontalAlignment="Stretch" VerticalAlignment="Top" Grid.Row="0"> <Menu.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </Menu.ItemsPanel> <MenuItem Header="Kilép" x:name="mikilép" VerticalAlignment="Center" Click="miKilép_Click" /> <MenuItem Header="Kever" x:name="mikever" VerticalAlignment="Center" Click="miKever_Click"/> <MenuItem Header="Kérdez" x:name="mikérdez" VerticalAlignment="Center" Click="miKérdez_Click"/> <MenuItem Header="Súgó" x:name="misúgó" VerticalAlignment="Center" Click="miSúgó_Click"/> </Menu> <Grid x:name="grképek" Grid.Row="1"> <Grid.ColumnDefinitions> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Image x:name="im00" Grid.Column="0" Grid.Row="0" Margin="5" <Image x:name="im01" Grid.Column="1" Grid.Row="0" Margin="5" <Image x:name="im02" Grid.Column="2" Grid.Row="0" Margin="5" <Image x:name="im03" Grid.Column="3" Grid.Row="0" Margin="5" <Image x:name="im10" Grid.Column="0" Grid.Row="1" Margin="5" <Image x:name="im11" Grid.Column="1" Grid.Row="1" Margin="5" <Image x:name="im12" Grid.Column="2" Grid.Row="1" Margin="5" <Image x:name="im13" Grid.Column="3" Grid.Row="1" Margin="5" </Grid> </Grid> </Window> A felhasznált képek számára hozzunk létre egy mappát a Solution Explorerben a projekten belül.
A mappa neve legyen Kepek. A Kepek mappába másoljuk be az öt képet. A képek meg fognak jelenni a Solution Explorerben is. Jelöljük ki a képeket, és nézzük meg a Properties ablakban a hozzájuk tartozó beállításokat. Ez azt jelenti, hogy erőforrásként lettek megjelölve, és a fordító be fogja fordítani őket a szerelvénybe. Az ablak osztályában (wndfőablak) hozzunk létre egy adattagot, amiben tároljuk a hátlapkép nevét. /// A hátlapként alkalmazott kép állomány neve. string HátlapNév = "hatter.jpg"; Hozzunk létre egy adattagot a négy rajfilmfigura képnevének tárolására.
/// A játékban felhasznált mesefigurákat tartalmazó /// kép állományok nevei. string[] KépNevek = "grabowski.jpg", "kukori.jpg", "kuldonc.jpg", "vili.jpg" ; Hozzunk létre egy adattagot a hátlapkép objektum referenciájának tárolásához. /// A hátlapkép referenciáját tároló változó. BitmapImage bihátlapkép; Hozzunk létre egy adattagot, aminek segítségével egy tömbben tudjuk tárolni a nyolc kép referenciáit. /// Tömb a nyolc kép objektum referenciáinak tárolására. BitmapImage[] biképek = new BitmapImage[8]; Hozzunk létre egy adattagot a mesefigurák megjelenítésére használt Image komponensek referenciáinak tárolására. /// Tömb a mesefigurák megjelenítésére használt Image /// komponensek referenciáinak tárolására. Image[] imképhelyek; Hozzunk létre egy adattagot a véletlenszám generáló objektum számára. /// Véletlenszámok előállítására szolgáló objektum. Random Véletlen = new Random(); A főablak konstruktorában hozzuk létre a képeket megjelenítő komponensek tömbjét, és töltsük be a képeket a memóriába. Állítsuk be, hogy alapból minden komponens a hátlapképet mutassa. /// A főablak konstruktora. Létrehozza a képeket megjelenítő /// komponensek tömbjét, és betölti a képeket a memóriába. public wndfőablak() // Komponensek léterhozása és inicializálása. InitializeComponent(); // Képeket megjelenítő komponensek tömbjének létrehozása. imképhelyek = new Image[] im00, im01, im02, im03, im10, im11, im12, im13 ; // Képek betöltése a memóriába. KépeketBetölt(); // Minden komponens a hátlapképet mutatja. HátlapképpelKitölt();
A képek betöltéséhez ún. Pack Uri (ld. http://msdn.microsoft.com/enus/library/aa970069.aspx) típusú erőforrás elérés használata szükséges. A relatív útvonalaknál elegendő a projekt gyökérkönyvtárához viszonyítva megadni az erőforrás elérési útvonalát. elsőként a hátlapképet, majd ezt követően egy ciklusban a rajzfilmfigurák képeit fogjuk betölteni. A később szükséges keverés könnyebb megoldása érdekében minden betöltött rajzfilmfigura kép referenciáját két tömb elemben tároljuk, így a négy képhez egy nyolcelemű tömböt használunk. /// Betölti memóriába a játékhoz használt képeket. A képek a /// projekt gyökérkönyvtárában levő Kepek alkönyvtárában kell legyenek. private void KépeketBetölt() try // Hátlapkép betöltése. bihátlapkép = new BitmapImage(new Uri(@"Kepek/"+HátlapNév, UriKind.Relative)); // A négy mesefigura kép betöltése. for (int i = 0; i < 4; i++) biképek[i] = new BitmapImage(new Uri(@"Kepek/" + KépNevek[i], UriKind.Relative)); // A másodpéldányokat beazonosító referenciák. biképek[i + 4] = biképek[i]; catch (Exception) MessageBox.Show("A képek nem találhatók a megadott útvonalon!", "Hiba", MessageBoxButton.OK); A hátlapképeket megmutató metódus vázát a Visual Studioval generáltassuk le, majd a tartalmát alakítsuk ki az alábbiak szerint. /// Mind a nyolc helyen a hátlapképet jeleníti meg. private void HátlapképpelKitölt() for (int i = 0; i < 8; i++) imképhelyek[i].source = bihátlapkép; Készítsük el a Kever menüpont eseménykezelőjét. Ebben az induláskor automatikusan betöltött négy pár képet véletlenszerű elrendezésben megjelenítjük, várakozunk két másodpercet, majd a képek helyett zöldre festett téglalapokat jelenítünk meg (hátlap). /// A Kever menüpont hatására az induláskor automatikusan /// betöltött négy pár képet véletlenszerű elrendezésben /// megjeleníti. /// Várakozik két másodpercet, majd a képek helyett zöldre
/// festett téglalapokat jelenít meg (hatter.jpg). /// <param name="sender">a menüpont objektum.</param> /// <param name="e"></param> private void mikever_click(object sender, RoutedEventArgs e) // Véletlen sorrend meghatározása. VéletlenSorrendbeRak(); // Képek láthatóvá tétele. KépeketMegmutat(); // Várakozás két másodpercig. Thread.Sleep(2000); // Háttérkép megjelenítése mind a nyolc helyen. HátlapképpelKitölt(); Az egyes részfeladatokat egy-egy metódussal oldjuk meg. A metódusok vázát a Visual Studioval generáltatjuk le automatikusan. A várakozáshoz használt Thread osztály a System.Threading névtérben található. Illesszük be a használatához szükséges usingot. A képek elhelyezkedését meghatározó VéletlenSorrendbeRak() metódusban létrehozunk egy generikus lista objektumot, amibe betesszük az összes képet. Innen véletlenszerűen kihúzva képeket alakítjuk ki a biképek tömb tartalmát. /// Meghatározza a képek véletlenszerű sorrendjét. private void VéletlenSorrendbeRak() // Létrehozunk egy lista objektumot a képek referenciáinak tárolássára. List<BitmapImage> KépLista = new List<BitmapImage>(); // Mind a 8 kép referenciáját elhelyezzük a listában. KépLista.AddRange(biKépek); // "Húzás a kalapból" véletlenszerűen kivesszük a nyolc referenciát. for (int i = 0; i < 8; i++) // A maradék listából véletleszerűen kiválasztunk egy elemet. int Sorszám = Véletlen.Next(0, KépLista.Count); // Betesszük a tömb i. helyére biképek[i] = KépLista[Sorszám]; // Eltávolítjuk a listáról. KépLista.RemoveAt(Sorszám); A képek láthatóvá tétele (KépeketMegmutat() metódus) azt jelenti, hogy a biképek tömbben hivatkozott sorrendben rendeljük a képeket az egyes Image komponensekhez. A metódus működése hasonló a HátlapképpelKitölt() metóduséhoz. /// Láthatóvá teszi a nyolc képet. private void KépeketMegmutat()
for (int i = 0; i < 8; i++) imképhelyek[i].source = biképek[i]; A program várakozásunkkal ellentétben nem fogja megmutatni a rajzfilmfigurákat, mivel az Image komponensek frissítése (újrarajzolása) nem történik meg. A probléma megoldásaként egy bővítő metódust készítünk (ld. http://geekswithblogs.net/newthingsilearned/archive/2008/08/25/refresh--update-wpfcontrols.aspx). Ehhez egy új osztályt adunk a projektünkhöz ImageExtension néven. public static class ImageExtension // http://geekswithblogs.net/newthingsilearned/archive/2008/08/25/refresh--updatewpf-controls.aspx private static Action EmptyDelegate = delegate() ; public static void Refresh(this UIElement uielement) uielement.dispatcher.invoke(dispatcherpriority.render, EmptyDelegate); Az osztály kulcseleme a Refresh() metódus. Ennek meghívását úgy a KépeketMegmutat(), mint a HátlapképpelKitölt() metódusba elhelyezzük az új kép megadást követően és... imképhelyek[i].source = biképek[i]; // Kép komponens frissítése imképhelyek[i].refresh();... imképhelyek[i].source = bihátlapkép; // Kép komponens frissítése. imképhelyek[i].refresh(); Készítsük el a Kilép menüpont eseménykezelőjének kódját. A korábban legenerált vázba csak egyetlen utasítást kell beírni. /// Kilép az alkalmazásból. /// <param name="sender">a Kilép menüpont objektum.</param> /// <param name="e"></param> private void mikilép_click(object sender, RoutedEventArgs e) Application.Current.Shutdown(); A Kérdez menüpont eseménykezelőjének elkészítése előtt készítünk egy új ablakot a projektünkben wndkérdés néven (az állománynév wndkerdes legyen).
Nevezzük át az ablak osztályát wndkérdés-re és a fejlécben helyezzük el a Kérdés feliratot <Window x:class="memoriajatek.wndkérdés" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Kérdés" Height="300" Width="300"> <Grid> </Grid> </Window> A felületmenedzserben alakítsunk ki két oszlopot és négy sort. Az első oszlop első három cellájába helyezzünk egy Image komponenst. <Window x:class="memoriajatek.wndkérdés" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Kérdés" Height="350" Width="500"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="150"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="40"/> <RowDefinition Height="85*"/> <RowDefinition Height="85*"/> <RowDefinition Height="51*"/> </Grid.RowDefinitions> <Image x:name="imkép" Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" /> <TextBlock x:name="tkfellirat" Text="Mely helyeken volt látható ez a mesefigura?" Grid.Row="0" Grid.Column="1" FontSize="14" FontWeight="Bold" Margin="5"/> <GroupBox x:name="gbelső" Grid.Row="1" Grid.Column="1" Header="A kép első előfordulása">
<Grid x:name="grelső"> <Grid.ColumnDefinitions> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Label Content="1. sor" Grid.Row="0" Grid.Column="0"/> <Label Content="2. sor" Grid.Row="1" Grid.Column="0"/> <RadioButton x:name="rbelső00" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="True"/> <RadioButton x:name="rbelső01" Grid.Row="0" Grid.Column="2" <RadioButton x:name="rbelső02" Grid.Row="0" Grid.Column="3" <RadioButton x:name="rbelső03" Grid.Row="0" Grid.Column="4" <RadioButton x:name="rbelső10" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" /> <RadioButton x:name="rbelső11" Grid.Row="1" Grid.Column="2" <RadioButton x:name="rbelső12" Grid.Row="1" Grid.Column="3" <RadioButton x:name="rbelső13" Grid.Row="1" Grid.Column="4" </Grid> </GroupBox> <GroupBox x:name="gbmásodik" Grid.Row="2" Grid.Column="1" Header="A kép második előfordulása"> <Grid x:name="grmásodik"> <Grid.ColumnDefinitions> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Label Content="1. sor" Grid.Row="0" Grid.Column="0"/> <Label Content="2. sor" Grid.Row="1" Grid.Column="0"/> <RadioButton x:name="rbmásodik00" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="True"/> <RadioButton x:name="rbmásodik01" Grid.Row="0" Grid.Column="2" <RadioButton x:name="rbmásodik02" Grid.Row="0" Grid.Column="3" <RadioButton x:name="rbmásodik03" Grid.Row="0" Grid.Column="4" <RadioButton x:name="rbmásodik10" Grid.Row="1" Grid.Column="1" <RadioButton x:name="rbmásodik11" Grid.Row="1" Grid.Column="2"
<RadioButton x:name="rbmásodik12" Grid.Row="1" Grid.Column="3" <RadioButton x:name="rbmásodik13" Grid.Row="1" Grid.Column="4" </Grid> </GroupBox> <Button x:name="btok" Content="OK" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Width="150" Height="30" Click="btOK_Click" /> </Grid> </Window> Készítsük el a Kérdez menüpont eseménykezelőjének kódját. A Kérdez menüpont hatására a program egy új ablakot jelenít meg Kérdés fejléccel. Az ablakban az előzőekben látható négy képtípus valamelyike jelenik meg véletlenszerűen. A felhasználó kiválasztja, hogy szerinte mely helyeken fordult elő a főablakban a kép, majd kattint az OK gombon. Ekkor eltűnik a Kérdez ablak, és a válasz helyességétől függően az alábbi két üzenetablak egyike jelenik meg, majd újból láthatóvá válik a nyolc kép úgy, ahogy azt a legelső ábrán láthatjuk. /// A Kérdez menüpont hatására egy új ablakot jelenít meg /// Kérdés fejléccel. Az ablakban az előzőekben látható /// négy képtípus valamelyike jelenik meg véletlenszerűen. /// A felhasználó kiválasztja, hogy szerinte mely helyeken fordult elő a /// főablakban a kép, majd kattint az OK gombon. /// Ekkor eltűnik a Kérdez ablak, és a válasz helyességétől /// függően az alábbi két üzenetablak egyike jelenik meg, /// majd újból láthatóvá válik a nyolc kép úgy, ahogy azt a /// legelső ábrán láthatjuk. /// <param name="sender">a Kérdez menüpont objektum.</param> /// <param name="e"></param> private void mikérdez_click(object sender, RoutedEventArgs e) // Ablak objektum létrehozása. wndkérdés wndkérdés = new wndkérdés(); // Kép sorszám kiválasztása véletlenszerűen. int Sorszám = Véletlen.Next(0, 8); // Keresett kép kiválasztása. BitmapImage KeresettKép = biképek[sorszám]; // Kép komponenshez rendelése. wndkérdés.bikép = KeresettKép; // Megjeleníti a párbeszédablakot. wndkérdés.showdialog(); // Lekérdezi, hogy mely pozíciókat választotta ki a // felhasználó, majd előállítja az adott pozícióban található // képek referenciáit. BitmapImage bielső = biképek[wndkérdés.elsősorszám]; BitmapImage bimásodik = biképek[wndkérdés.másodiksorszám]; // Megvizsgálja, hogy a keresett kép azonos-e a két megjelölt // képpel. if (bielső == KeresettKép && bimásodik == KeresettKép) MessageBox.Show("Hurrá eltaláltad!"); else MessageBox.Show("Hát ez most nem jött össze!"); // Újból láthatóvá teszi a képeket. KépeketMegmutat();
A metódusban a megjelenített kép megadásához (wndkérdés.bikép) és a felhasználó által kiválasztott pozíciók lekérdezéséhez (wndkérdés.elsősorszám, wndkérdés.másodiksorszám) olyan tulajdonságokat használtunk, amelyik még nem léteznek a wndkérdés osztályban. A tulajdonságok vázát úgy generálhatjuk le a legegyszerűbben, hogy kattintunk a tulajdonság név első betűje alatt megjelenő aláhúzásjelen, majd a Generate property stub menüpontot választjuk az alábbi ábrának megfelelően. Míg a bikép esetében csak írható, addig a másik két esetben csak olvasható tulajdonságokra van szükség. A generált tulajdonságok alapból statikusak, de nekünk objektum szintű tulajdonságok szükségesek ahhoz, hogy elérjük a megfelelő komponensek tartalmát, ezért távolítsuk el a static kulcsszót mind a három esetben. Házi feladat Készítse el a wndkerdes osztályban szükséges kódot. Hozzon létre egy súgó ablakot és gondoskodjon a megjelenítésről.