Zmiany

Dziennik ucznia

Dodane 120 422 bajty, 17:35, 8 paź 2015
Utworzono nową stronę "Zadanie, które wykonamy w tym ćwiczeniu pozwoli Ci zapoznać się z elementami graficznego interfejsu użytkownika. Mówiąc inaczej ze wszystkimi rodzajami elementów..."
Zadanie, które wykonamy w tym ćwiczeniu pozwoli Ci zapoznać się z elementami graficznego interfejsu użytkownika. Mówiąc inaczej ze wszystkimi rodzajami elementów, których używamy korzystając z różnych aplikacji. Są to m.in. pola tekstowe, listy rozwijane, menu, przyciski.
{| class="wikitable" border="1"
|'''Pamiętaj!''' Ćwiczenie, które wykonamy wymaga kontrolek, które nie są dostarczone z podstawową wersją Greenfoot. Należy pobrać wersję dostarczoną przez CoderDojo.
|}
Na przykładzie naszego ulubionego Greenfoot-a przejrzyjmy listę kontrolek, które zapewne znasz z innych aplikacji.<br />
Pole tekstowe
[[File:jmk-dziennik_image50.png| center]]
Checkbox
[[File:jmk-dziennik_image16.png| center]]
Lista wyboru
[[File:jmk-dziennik_image09.png| center]]
Menu górne
[[File:jmk-dziennik_image31.png| center]]
Menu kontekstowe
[[File:jmk-dziennik_image49.png| center]]
Suwak
[[File:jmk-dziennik_image17.png| center]]
Przyciski
[[File:jmk-dziennik_image55.png| center]]
To jednak tylko część elementów, każdy z nich ma inne właściwości i używa się ich tak, aby użytkownik aplikacji w jak najprostszy sposób mógł z niej korzystać.

== Zapoznanie z Greenfoot GUI Components ==
Greenfoot posiada rozszerzenie stworzone przez Taylora Borna, które pozwala na używanie takich elementów we własnej aplikacji. Taką aplikację stworzymy właśnie w tym ćwiczeniu. Na początek przyjrzyjmy się oferowanym przez Taylora kontrolkom.<br />
Wyszukaj w google frazy “Greenfoot GUI compoenents” lub wejdź na poniższy link [[http://www.greenfoot.org/scenarios/7578| http://www.greenfoot.org/scenarios/7578]]. Powinieneś zobaczyć opis rozszerzenia jak na poniższym obrazku.
[[File:jmk-dziennik_image00.png| center]]
Kliknij '''Open in Greenfoot'''. Zostanie pobrany plik o rozszerzeniu '''gfar''', który możesz otworzyć w '''Greenfoot'''. Kliknij po prostu dwukrotnie na plik lub przez menu Greenfoot otwórz go wybierając kolejno '''Scenario -> Open''', i wskaż na pobrany plik.<br />
Po otwarciu powinieneś zobaczyć okno Greenfoot takie, jak na obrazku poniżej.
[[File:jmk-dziennik_image46.png| center]]
Teraz skompilujmy ten program. Uwaga! Ze względu na to, że w Greenfoocie, który otrzymaliście od nas, już dodaliśmy te klasy, pojawią się ostrzeżenia (kilkanaście), jak na poniższym obrazku. Kliknij po prostu przy każdym OK.
[[File:jmk-dziennik_image12.png| center]]
Po zamknięciu wszystkich wiadomości projekt się skompiluje i Twoim oczom powinno pojawić się okno takie, jak poniżej.
[[File:jmk-dziennik_image48.png| center]]
Poklikaj w aplikacji, zobacz jak działają poszczególne elementy GUI.<br />
Jak możesz zauważyć, świat nazywa się '''GUI_DemoWorld'''. Świat, jak w każdej aplikacji, pozwala na dodawanie aktorów. W tym przypadku to kontrolki są aktorami.<br />
Przeanalizujmy strukturę kontrolek.<br />
* Wszystkie z nich dziedziczą z klasy '''GUI_Component''', jest to klasa bazowa dla wszystkich kontrolek. Jeśli zajrzysz w jej dokumentację, dowiesz się jakie ma metody i na co pozwalają wszystkie kontrolki. Klasa ta pozwala m.in. na zmianę tła, czcionki, obramowania. Oznacza to, że wszystkie dziedziczące po niej kontrolki mają te metody i możesz ich używać.
* Następna klasa to '''WindowComponent'''. Jest klasą bazową dla wszystkich podstawowych elementów takich jak lista rozwijana (DropDownList), pole tekstowe (TextBox), przycisk (Button), hasło (Password), itd.
* Kolejna klasa bazowa to '''Window'''. Jest to klasa bazowa, z której dziedziczą klasy odpowiedzialne za wyświetlanie wyskakujących okien. Jeśli w przykładowej aplikacji wybierzesz '''Window Examples''' i któryś z elementów podmenu, to zobaczysz nowe okno.

== Założenie aplikacji i stworzenie świata ==

=== Zapoznanie z klasą GUI_DemoWorld ===
W pierwszej kolejności przed przystąpieniem do stworzenia nowego projektu spójrzmy jak wygląda kod klasy '''GUI_DemoWorld'''. Pozwoli to na zapoznanie się z tym, jak autor widział wykorzystanie napisanych przez niego klas.<br />
Otwórz klasę klikając na nią dwukrotnie.
[[File:jmk-dziennik_image02.png| center]]
Na początku klasy widać pola, które są kontrolkami. Umieszczono je tutaj, aby mogły być wykorzystane w całej klasie. Zobaczmy jak są inicjowane.
[[File:jmk-dziennik_image23.png| center]]
W konstruktorze, czyli w momencie tworzenia świata, tworzone są obiekty, a ich wartości są przypisane do pól.
[[File:jmk-dziennik_image36.png| center]]
<syntaxhighlight lang="java">
String text = "A TextBox that ...";
txtB = new TextBox(new Point(250, 100), text, new Font("Helvetica", Font.PLAIN, 15));
</syntaxhighlight>
Do pola '''txtB''' zostaje przypisane pole tekstowe, w które można wprowadzić własny tekst.<br />
Następnie za pomocą polecenia
<syntaxhighlight lang="java">
addObject(txtB, 470, 73);
</syntaxhighlight>
pole tekstowe '''txtB''' jest dodawane do świata, aby je wyświetlić. Obiekt ten dodaje się do świata tak samo jak wszystkich aktorów, gdyż właśnie '''TextBox''' jest dzieckiem klasy '''Actor'''.<br />
Jeśli klikasz na przyciski, możesz zauważyć, że wywołują one pewne akcje. Zobaczmy jeszcze jak to jest obsłużone. W metodzie '''act()''' zobaczysz taki oto kawałek kodu:
[[File:jmk-dziennik_image34.png| center]]
W pierwszej instrukcji sprawdzane jest, czy przycisk '''btnClick''' został kliknięty. Jeśli tak, to w następnym kroku sprawdzana jest aktualna akcja.
<syntaxhighlight lang="java">
if (btnAction.getText().equals("Add Counter"))
</syntaxhighlight>
Jeśli akcja ma polegać na dodaniu do licznika, to za pomocą takiej instrukcji w wyświetlanej etykiecie (label), wartość zostaje zwiększona o 1.
<syntaxhighlight lang="java">
lblCounter.setText("" + (Integer.parseInt(lblCounter.getText()) + 1));
</syntaxhighlight>
{| class="wikitable" border="1"
|'''''Uwaga!''' Projekt GUI_Components warto jest mieć otwarty, aby móc się na nim wzorować. Greenfoot może być uruchomiony w kilku oknach.''
|}


=== Stworzenie świata ===
Załóżmy teraz nasz własny projekt, w którym napiszemy naszą wersję dzienniczka ucznia.<br />
W Greenfoot wybierz z menu '''Scenario -> New''', następnie wskaż miejsce do zapisu projektu, stwórz nowy folder '''Dziennik''' i wybierz '''Create'''.
[[File:jmk-dziennik_image56.png| center]]
Zostanie stworzony nowy projekt, z którym będziemy pracować i krok po kroku rozwijać.
[[File:jmk-dziennik_image54.png| center]]
Teraz skupimy się na umieszczeniu pierwszych kontrolek w naszym świecie, tak abyśmy na koniec tej części ćwiczenia otrzymali obraz jak poniżej.
[[File:jmk-dziennik_image30.png|center|frame|Obrazek. Pierwsze GUI.]]
Aby tego dokonać dodajmy najpierw klasę świata, w którym ustawimy odpowiednie elementy graficzne (GUI - graphical user interface). Kliknijmy na klasę '''World''' prawym przyciskiem myszy (PPM) i wybierzmy opcję '''New subclass'''. W oknie które się pojawi, wprowadźmy dowolną nazwę klasy. W przykładzie będę używał nazwy '''DziennikUcznia'''.<br />
<small>''Pamiętajmy jednak, że powinna być ona znacząca, tak aby łatwiej móc z takiej klasy korzystać w przyszłości. Dzięki temu wracając do programu po kilku tygodniach dalej wiemy co dana klasa lub metoda robiła.''</small>
Kliknijmy PPM na nową klasę i wybierzmy new DziennikUcznia() tworząc tym samym nowy świat.<br />
Otwórzmy naszą klasę i zacznijmy kodowanie.<br />
W pierwszej kolejności ustawmy na środku etykietę (ang. Label) z imieniem i nazwiskiem ucznia. Pod spodem zróbmy podobną etykietę z nazwą przedmiotu.
{| class="wikitable" border="1"
| ''Sprawdźmy w projekcie GUI_Components jakie mamy dostępne konstruktory klasy Label. Możemy to zrobić klikając PPM na klasę i sprawdzając jakie ma konstruktory (poprzedzone słówkiem new)''
[[File:jmk-dziennik_image26.png| center]]
[[File:jmk-dziennik_image08.png| center|frame|Obrazek. Konstruktory klasy Label.]]
Lepiej jest jednak sprawdzić to w dokumentacji (JavaDoc).<br />
Obrazek. Jak widać dokumentacja w sposób bardziej czytelny pokazuje jakie konstruktory mamy do wyboru.
W przypadku etykiety widzimy, że mamy do wyboru 2 konstruktory. Pozwolą stworzyć nam one obiekty na różne sposoby.
|}
Na początku użyjmy prostszego konstruktora, któremu wystarczy dostarczyć właściwy tekst. Stwórzmy 2 obiekty w konstruktorze naszej klasy i umieśćmy je w świecie Greenfoot. Spróbujmy umieścić je na środku. Możemy wykorzystać do tego metodę prób i błędów:) Po kliknięciu przycisku '''Compile''' w oknie świata powinna pojawić się stworzona etykieta.
[[File:jmk-dziennik_image52.png| center|frame|Obrazek. Imię i nazwisko ucznia oraz przedmiot.]]
Kod z kolei wygląda tak:
<syntaxhighlight lang="java">
public DziennikUcznia() {
// Create a new world with 600x400 cells with a cell size of 1x1 pixels.
super(600, 400, 1);
// etykiety nazw
Label uczenLabel = new Label("Jan Kowalski");
Label przedmiotLabel = new Label("matematyka");
addObject(uczenLabel, 300, 30);
addObject(przedmiotLabel, 300, 60);
</syntaxhighlight>
Dodajmy jeszcze listę (ListBox) z ocenami. Umieśćmy nad nią etykietę “Oceny”. Podobnie jak poprzednio przyjrzyjmy się, jakie mamy dostępne konstruktory i jakie obiekty możemy stworzyć.<br />
''Zróbmy to teraz w trochę inny sposób. Programując często warto zobaczyć jak to robi ktoś inny, można wtedy nauczyć się czegoś nowego, poznać nowe klasy i sposób ich użycia. Zajrzyjmy do klasy '''ListBox''', klikając na nią dwukrotnie w projekcie '''GUI_Components'''. Gdy pojawi się dokumentacja zmieńmy widok na '''Source Code'''. Znajdźmy konstruktor i przyjrzyjmy się jak on wygląda? Czy potrafisz na podstawie opisu (komentarze nad konstruktorem) oraz implementacji określić jak obiekt jest tworzony, co jest potrzebne aby stworzyć obiekt?''
[[File:jmk-dziennik_image59.png| center|frame|Obrazek. Konsturkor klasy ListBox.]]
''Jak widać do działania potrzebuje on obiektu typu '''Point''' o nazwie '''size'''. Z opisu wynika, że trochę niejednoznacznie obiekt Point opisuje wielkość tworzonego obiektu. '''Point''' nie jest pełną nazwą klasy, znajduje się on w pakiecie. Czy potrafisz określić jaki to pakiet? Czy potrafisz znaleźć kawałek kodu, który inicjalizuje obiekt Point?''
[[File:jmk-dziennik_image24.png| center|frame|Obrazek. Importy klasy ListBox.]]
''W importach widzimy, że klasa Point pochodzi z pakietu '''java.awt'''. Znajdźmy w internecie te klasę i jej dokumentację. Dostępna jest pod adresem: <br />[[http://docs.oracle.com/javase/7/docs/api/java/awt/Point.html| http://docs.oracle.com/javase/7/docs/api/java/awt/Point.html]]''
<small>''Pakiet powstał ze względu na konieczność rozróżnienia różnych klas tworzonych przez programistów. W projektach często mamy do czynienia z tysiącami klas i ze względu na łatwiejsze ich użycie wprowadzono taką konstrukcję.''</small>
[[File:jmk-dziennik_image01.png| center|frame|Obrazek. Konstruktory klasy '''Point'''.]]
Drugi parametr konstruktora klasy '''ListBox''' to lista typu '''java.util.ArrayList. ArrayList''' to jeden z podstawowych typów Javy. Warto poznać ją szerzej.<br />
'''ArrayList''' pozwalaja na przechowywanie dowolnego rodzaju danych w postaci listy. Lista charakteryzuje się tym, że wartości są w niej dodawane w kolejności, domyślnie na koniec listy. Elementy na liście są zdefiniowane w kolejności i jeśli nic nie zmieniamy, to możemy je łatwo odczytać. Sprawdźmy jakie metody ma lista i jak możemy jej używać. Należy też pamiętać, że podobnie jak z tablicami '''elementy są numerowane od zera'''. To oznacza, że po stworzeniu pustej listy i dodaniu elementu, posiadamy listę o rozmiarze 1, ale wartość pobieramy przez metodę z podaniem indeksu 0, np. lista.get(0). Analogicznie drugi element pobieramy przez podanie indeksu 1, lista.get(1).<br />
[[https://docs.oracle.com/javase/7/docs/api/java/util/List.html| https://docs.oracle.com/javase/7/docs/api/java/util/List.html]]<br />
Najważniejsze metody to:
{| class="wikitable" border="1"
|+ style="caption-side:bottom;"|''Tabela. Podstawowy metody klasy java.util.ArrayList.''
|-
!Metoda
!Opis
|-
| boolean add(E e)
| Dodaje element na koniec listy. E jest to dowolny obiekt.
|-
| void add(int index, E element)
| Dodaje element do listy pod wskazanym indeksem. Element można wstawić w dowolne miejsce listy, elementy po podanym indeksie zostają przesunięte w prawo. Nie można podać liczby mniejszej od 0 i większej od wielkości listy.
|-
| boolean contains(Object o)
| Sprawdza czy podany obiekt jest już na liście. Porównanie za pomocą metody equals.
|-
| E get(int index)
| Pobiera element z listy z podanego indeksu. Nie można podać wartości mniejszej od 0 i większej, bądź równej rozmiarowi listy.
|-
| int indexOf(Object o)
| Zwraca indeks pod którym znajduje się podany obiekt.
|-
| boolean isEmpty()
| Sprawdza czy lista jest pusta.
|-
| E remove(int index)
| Usuwa element pod podanym indeksem.
|-
| boolean remove(Object o)
| Usuwa podany obiekt.
|-
| int size()
| Zwraca wielkość listy.
|}

W tej chwili wiemy, że aby stworzyć obiekt klasy '''ListBox''' musimy przekazać do jej konstruktora dwa inne obiekty: rozmiar elementu widziany na ekranie oraz listę wartości. Spróbujmy dodać ja na ekranie. Tworząc listę spróbuj dodać od razu kilka ocen. Na koniec dodajmy jeszcze listę. Pamiętaj, aby dodać na początku klasy w sekcji import wpisy:<br />
import java.awt.Point;<br />
import java.util.ArrayList;
[[File:jmk-dziennik_image52.png| center]]
Dzięki temu można korzystać z klas '''ArrayList''' i '''Point'''. Przykładowy kawałek kodu:
<syntaxhighlight lang="java">
//stworzenie listy ocen
ArrayList<String> oceny = new ArrayList<String>();
oceny.add("5 ");
oceny.add("4 ");
oceny.add("6 ");
Point rozmiar = new Point(30, 60);
ListBox<String> ocenyListBox = new ListBox<String>(rozmiar, oceny);
addObject(ocenyListBox, 300, 160);
</syntaxhighlight>
Obok '''ArrayList''' oraz '''ListBox''' możesz zauważyć wyrażenie '''<String>'''. Jak wspomniałem wcześniej, lista - jak i w tym wypadku ListBox - mogą przechowywać obiekty dowolnego typu. Taka konstrukcja mówi kompilatorowi (a także programiście), jakiego typu dane są przechowywane w danym obiekcie.<br />
<small>''Możesz spotkać się z takimi konstrukcjami jak np.<br />
ArrayList<String> - przechowuje napisy typu String<br />
ArrayList<Integer> - lista przechowuje liczby<br />
ArrayList<Actor> - lista przechowuje aktorów, a nawet<br />
ArrayList<ArrayList<Integer>> - lista przechowująca listy liczb''</small><br />
Dodajmy teraz informacje o uwagach i osiągnięciach. Zróbmy to analogicznie do danych o ocenach.
<syntaxhighlight lang="java">
//wyswietlanie uwag uwag
ArrayList<String> uwagi = new ArrayList<String>();
uwagi.add("Swietnie poszlo w ulamkami.");
uwagi.add("Sprobuj poprawic trygonometrie.");
rozmiar = new Point(200, 60);
ListBox<String> uwagiListBox = new ListBox<String>(rozmiar, uwagi);
addObject(uwagiListBox, 300, 250);

//stworzenie etykiety uwag
Label uwagiLabel = new Label("Uwagi/osiagniecia");
addObject(uwagiLabel, 300, 210);
</syntaxhighlight>
Uruchom aplikację. Czy wszystko poszło ok, jak wygląda teraz?<br />
Spróbuj jeszcze ustawić tło, aby ekran wyglądał lepiej. Na tym etapie ekran powinien wyglądać podobnie do prezentowanego na poniższym obrazku. Powinieneś użyć metody <i style="color:green">setBackground</i> z klasy '''World'''.<br />
Najpierw znajdź np. w internecie obrazek który chciałbyś wyświetlać w tle. Następnie pobierz go i zmień nazwę na '''tlo'''. Pamiętaj, aby obrazek był typu jpg.<br />
Następnie obrazek umieść w folderze projektu w folderze '''images'''.
[[File:jmk-dziennik_image13.png| center]]
Teraz na końcu konstruktora wywołaj funckję '''setBackground'''.
<syntaxhighlight lang="java">
//ustawienie tla ekranu
setBackground("tlo.jpg");
</syntaxhighlight>
Po uruchomieniu aplikacji powinieneś zobaczyć ekran jak poniżej.
[[File:jmk-dziennik_image30.png| center|frame|Obrazek. Stworzenie podstawowego ekranu.]]

== Wprowadzanie oceny ==
W tym kroku pozwolimy nauczycielowi wprowadzić ocenę do naszego dzienniczka.<br />
W pierwszej kolejności potrzebny będzie nam przycisk dodawania oceny. Sprawdźmy klasę '''Button''' i to, jak wygląda jej konstruktor.
[[File:jmk-dziennik_image37.png| center]]
Jak widzimy, aby utworzyć przycisk, należy jako pierwszy argument podać wyświetlany tekst. Drugi argument to rozmiar, który jest znanego nam już typu '''Point'''.<br />
Spróbuj dodać przycisk, tak aby pojawił się on obok tabeli z ocenami.<br />
Najpierw utwórz pole '''przyciskOceny'''.<br />
<syntaxhighlight lang="java">
public class DziennikUcznia extends World {
private Button przyciskOceny;
</syntaxhighlight>
Następnie utwórzmy nową metodę w klasie o nazwie '''utworzElementyGuiOcen()'''. Przenieśmy do niej kod odpowiedzialny za tworzenie listy.
<syntaxhighlight lang="java">

private void utworzElementyGuiOcen() {
//stworzenie listy ocen
ArrayList<String> oceny = new ArrayList<String>();
oceny.add("5 ");
oceny.add("4 ");
oceny.add("6 ");
Point rozmiar = new Point(30, 60);
ListBox<String> ocenyListBox = new ListBox<String>(rozmiar, oceny);
//stworzenie etykiety ocen
Label ocenyLabel = new Label("Oceny");
addObject(ocenyLabel, 300, 120);
}
</syntaxhighlight>
Poniżej tego kodu dodajmy tworzenie przycisku:
<syntaxhighlight lang="java">
Label ocenyLabel = new Label("Oceny");
addObject(ocenyLabel, 300, 120);
//stworzenie przycisku
przyciskOceny = new Button("dodaj ocene", new Point(80, 20));
addObject(przyciskOceny, 380, 140);
</syntaxhighlight>
Przycisk zostanie dodany na ekranie. Skompiluj program i uruchom. Czy jest widoczny? Zmieniając parametry wywołania new Point(80, 20) możesz modyfikować jego wielkość. Z kolei zmieniając wartości w metodzie addObject addObject(przyciskOceny, 380, 140);
możesz zmieniać jego położenie.
[[File:jmk-dziennik_image36.png| center|frame|Obrazek. Przycisk dodaj ocenę.]]

Świetnie, mamy już przycisk, teraz musimy dodać logikę, która zapyta nauczyciela o ocenę. Najwłaściwszy będzie tu element, który wyświetli nam nowe okno (nazywane oknem dialogowym, z ang. dialog window). Użyjmy do tego celu klasy '''InputWindow''', wyświetli ona nam komunikat z prośbą o podanie oceny i przyciskami “ok” oraz “cancel”. Aby wyświetlić to okno musimy przekazać do przycisku odpowiednią instrukcję, która wywoła to okno po kliknięciu przycisku.<br />
Zacznijmy od wprowadzenia kilku zmian w metodzie '''utworzElementyGuiOcen'''.
* usuńmy dodawanie ocen, pozostawmy pustą listę
* zdefiniujmy listBox z ocenami i przycisk, jako pola klasy, a nie jak było do dotychczas jak zmienne lokalne
Dodaj pole OcenyListBox na początku klasy
<syntaxhighlight lang="java">
private Button przyciskOceny;
private ListBox<String> ocenyListBox;
</syntaxhighlight>
Następnie zmień w metodzie '''utworzElementyGuiOcen''' sposób tworzenia listyOcen
<syntaxhighlight lang="java">
private void utworzElementyGuiOcen() {
//stworzenie listy ocen
Point rozmiar = new Point(30, 60);
ArrayList<String> oceny = new ArrayList<String>();
ocenyListBox = new ListBox<String>(rozmiar, oceny);
addObject(ocenyListBox, 300, 160);
</syntaxhighlight>
'''ocenyListBox''' nie jest już zmienną lokalną i można jej użyć we wszystkich metodach klasy.<br />
<small>''Dokładniej mówiąc, we wszystkich niestatycznych metodach klasy. To znaczy takich metodach, które można wywołać tylko dla obiektu. Przykładem metody statycznej jest metoda startująca aplikację<br />
Greenfoot.start();''</small>
W następnej kolejności dodajmy nowe pole typu InputWindow o nazwie np. oknoOceny, będzie ono przechowywać okno do wprowadzania ocen. Od razu zainicjujmy je. Sprawdźmy jak wygląda konstruktor:
[[File:jmk-dziennik_image21.png| center|frame|Obrazek. Konstruktor InputWindow]]
Jak widać przyjmuje 2 parametry:<br />
* prompt - komunikat wyświetlany użytkownikowi, wpiszmy np. “Proszę o podanie oceny”
* max - ilość możliwych do wprowadzenia znaków, ustawmy 1
Kod powinien wyglądać mniej więcej tak, jak poniżej:
<syntaxhighlight lang="java">
public class DziennikUcznia extends World {
private Button przyciskOceny;
private ListBox<String> ocenyListBox;
private InputWindow oknoOceny = new InputWindow("Podaj ocene", 1);
</syntaxhighlight>
Musimy jeszcze dodać logikę, która będzie nasłuchiwać na kliknięcie przycisku, wyświetlać okno z pytaniem o ocenę oraz - po wprowadzeniu - doda ją na listę ocen.<br />
Musimy nadpisać metodę void act() klasy World, która pozwoli nam przechwycić zdarzenia występujące w świecie. Aby kod wyglądał czytelnie, stwórzmy nową metodę void sluchajNaPrzyciskuOceny(), w której umieścimy odpowiedni kod, a samą metodę wywołajmy w metodzie act(). W ten sposób podzielimy kod na czytelne części.
<syntaxhighlight lang="java">
@Override
public void act() {
sluchajNaPrzyciskuOceny();
}
</syntaxhighlight>
W metodzie sluchajNaPrzyciskuOceny() dodajmy sprawdzanie czy przycisk został wciśnięty i pokażmy okno z pytaniem o ocenę. Zerknijmy w dokumentacje klas '''Button''' oraz '''InputWindow''' i znajdźmy potrzebne nam metody:
[[File:jmk-dziennik_image25.png| center|frame|Obrazek. Metody klasy InputWindow]]
[[File:jmk-dziennik_image47.png| center|frame|Obrazek. Metody klasy Button]]
Interesują nas 2 metody, '''Button.wasClicked()''' oraz '''InputWindow.toogleShow(value)'''. Pierwsza z nich sprawdzi czy przycisk został wciśnięty, druga pozwoli na pokazanie okna, jeśli jest ono ukryte i przekaże do niej pusty napis.<br />
Kod powinien wyglądać mniej więcej jak poniżej:
<syntaxhighlight lang="java">
private void sluchajNaPrzyciskuOceny() {
if (przyciskOceny.wasClicked()) {
oknoOceny.toggleShow("");
}

}
</syntaxhighlight>
Zapiszmy zmiany ('''CTRL+S'''), skompilujmy projekt i uruchommy nasz program klikając przycisk '''Run'''. Po kliknięciu na przycisk powinno pojawić się okno, w które możemy wprowadzić wartość.<br />
Można zauważyć, że jednak po jej wprowadzeniu i kliknięciu '''OK''' nic się nie stanie. Musimy dodać jeszcze sprawdzenie czy kliknięto w okno '''OK''' i pobrać tę wartość. Spróbuj w metodach klasy '''InputWindow''' odnaleźć metodę, która pozwoli to sprawdzić.<br />
Odpowiednia metoda to '''InputWindow.getResult()'''. Jeśli zwróci ona wartość, to oznacza, że wprowadzono wartość i kliknięto ok. Dodaj sprawdzanie czy metoda zwraca wartość, a jeśli tak jest, dodaj wartość do listy ocen przez wywołanie metody: ocenyListBox.add(wprowadzonaOcena)<br />
Pamiętaj, że po wywołaniu getResult() drugie wywołanie zwróci pustą wartość, więc konieczne będzie przechowanie wartości getResult() w zmiennej lokalnej.<br />
Kod powinien wyglądać mniej więcej tak.
<syntaxhighlight lang="java">
if (przyciskOceny.wasClicked()) {
oknoOceny.toggleShow("");
}
String wprowadzonaOcena = oknoOceny.getResult();
if (wprowadzonaOcena != null) {
ocenyListBox.add(wprowadzonaOcena);
}
</syntaxhighlight>
Przed zakończeniem tego ćwiczenia, dodajmy jeszcze wywołanie w na końcu konstruktora świata.
<syntaxhighlight lang="java">
//ustawienie tla ekranu
setBackground("tlo.jpg");
Greenfoot.start();
}
</syntaxhighlight>
Dzięki temu, już po kompilacji aplikacja się uruchomi i nie będziemy musieli za każdym razem klikać przycisku '''Run'''.<br />
Przetestuj czy dodawanie ocen działa prawidłowo. Zobacz jakie wartości możesz dodać. Spróbuj dodać pustą wartość, co się stanie?

== Walidacja danych ==
Dobrą praktyką jest sprawdzanie czy wprowadzane wartości są prawidłowe. Dodajmy prostą walidację, która doda do listy tylko prawidłowe wartości. Załóżmy, że są to oceny 1,2,3,4,5,6.<br />
Spróbuj dodać tę logikę po sprawdzeniu wartości '''InputWindow.getResult()''' w metodzie '''sluchajNaPrzyciskuOceny'''.<br />
W pierwszej kolejności dodajmy najprostsze sprawdzanie, czyli porównanie każdej wartości - sprawdzenie czy wprowadzona wartość jest równa “1”, “2”, “3” itd.. Pamiętaj, że w Javie do porównywania obiektów, w tym napisów ('''String''') służy funkcja '''Object.equals(Object o)''', np. “1”.equals(wartosc). W naszym przypadku będzie to kod:
<syntaxhighlight lang="java">
if (wprowadzonaOcena != null) {
if (wprowadzonaOcena.equals("1") || wprowadzonaOcena.equals("2")
|| wprowadzonaOcena.equals("3") || wprowadzonaOcena.equals("4")
|| wprowadzonaOcena.equals("5") || wprowadzonaOcena.equals("6")) {
ocenyListBox.add(wprowadzonaOcena);
}
</syntaxhighlight>
Operator || jest operatorem logicznym i oznacza '''albo'''. Przy wpisaniu wyrażeń, ważne jest aby '''conajmniej''' jedno z nich było prawdziwe. Tłumacząc na pseudokod mamy tutaj wyrażenie :
{| class="wikitable" border="1"
|jeśli<br />
wprowadzona ocena = 1 albo wprowadzona ocena = 2<br />
albo wprowadzona ocena = 3<br />
..<br />
to
|}
Dodaj ten kod sprawdzający i zobacz, czy teraz możesz wprowadzić inne wartości niż podane w warunku '''if'''.<br />
Powyższe rozwiązanie jest jednak brzydkie i czasochłonne. Jeśli trzeba byłoby wymienić 100 wartości, pojawiłby się duży problem.<br />
Inny sposób to zastosowanie listy możliwych wartości i sprawdzenie jej. Kod wyglądałaby np. tak:
<syntaxhighlight lang="java">

ArrayList<String> prawidloweWartosc = new ArrayList<String>();
prawidloweWartosc.add("1");
prawidloweWartosc.add("2");
prawidloweWartosc.add("3");
prawidloweWartosc.add("4");
prawidloweWartosc.add("5");
prawidloweWartosc.add("6");
if (prawidloweWartosc.contains(wprowadzonaOcena)) {
ocenyListBox.add(wprowadzonaOcena);
}
</syntaxhighlight>

Zmienną '''prawidloweWartosci''' można wprowadzić jako stałą w aplikacji i wtedy może być użyta wielokrotnie. Zmień kod który wprowadziłeś wcześniej i zobacz czy aplikacja dalej działa tak samo.<br />
Jak widać to rozwiązanie może być odrobinę lepsze, ale nie różni się znacząco od pisania wielu warunków w '''if'''.<br />
Lepszym rozwiązaniem byłoby zamienienie wprowadzonej oceny na liczbę i sprawdzenie czy jest z przedziału <1,6>. Możemy to zrobić za pomocą poniższego kodu. Spróbuj go użyć w swojej aplikacji. Sprawdź czy aplikacja dalej działa prawidłowo.
<syntaxhighlight lang="java">
try {
//rzutowanie wartosci na integer i sprawdzenie czy wartosc jest prawidlowa
Integer wartosc = Integer.valueOf(wprowadzonaOcena);
if (wartosc >= 1 && wartosc <= 6) {
//dodanie do listy wartosci
ocenyListBox.add(wprowadzonaOcena);
}
} catch (NumberFormatException e) {
//ignorujemy, po prostu nie dodajmy do listy
}
</syntaxhighlight>
Ten przykład może wydawać się odrobinę skomplikowany, prześledźmy go dokładnie.<br />
W pierwszej kolejności pobieramy liczbową wartość zmiennej, jest to linijka:
<syntaxhighlight lang="java">
Integer wartosc = Integer.valueOf(wprowadzonaOcena);
</syntaxhighlight>
Od teraz '''wartosc''' będzie liczbą. Teraz musimy sprawdzić czy jest ona z odpowiedniego przedziału, do tego służy warunek '''if'''.
<syntaxhighlight lang="java">
if (wartosc >= 1 && wartosc <= 6)
</syntaxhighlight>
Operator '''>=''' oznacza większe, bądź równe.<br />
Operator '''<=''' oznacza mniejsze, bądź równe.<br />
Operator '''&&''' jest to operator logiczny '''i'''. Warunek jest spełniony, jeśli oba wyrażenia są prawdziwe.<br />
W pseudokodzie możemy zapisać to jako:
{| class="wikitable" border="1"
|jeśli<br />
'''wartosc''' jest większa, bądź równa 1<br />
i<br />
'''wartosc''' jest mniejsza, bądź równa 6<br />
|}
Na pewno zastanawia Cię konstrukcja
<syntaxhighlight lang="java">
try {
...
}
} catch (NumberFormatException e) {

}
</syntaxhighlight>
Jest to tak zwane '''łapanie wyjątku'''. Niektóre metody, w tym przypadku ''Integer.valueOf()'' zwracają wartość, ale jeśli przekaże się im na przykład nieprawidłowe dane to '''rzucają wyjątek'''. W takim wypadku, trzeba go złapać i obsłużyć. Jeśli tego nie zrobimy, aplikacja może nie działać prawidłowo.<br />
W ramach eksperymentu usuńmy na chwilę te linijki i zobaczmy co się stanie jak wpiszesz np. wartość “a”, zamiast cyfry. Zostawmy samo
<syntaxhighlight lang="java">
//rzutowanie wartosci na integer i sprawdzenie czy wartosc jest prawidlowa
Integer wartosc = Integer.valueOf(wprowadzonaOcena);
if (wartosc >= 1 && wartosc <= 6) {
//dodanie do listy wartosci
ocenyListBox.add(wprowadzonaOcena);
}
</syntaxhighlight>
Powinieneś zobacz okno z informacją o błędzie jak poniżej:
[[File:jmk-dziennik_image44.png| center]]
Dodatkowo możesz zaobserwować, że aplikacja przestała działać.<br />
Przywróć kod do poprzedniej postaci, aby błędnie wprowadzone wartości były ignorowane.

== Stworzenie listy uwag i osiągnięć.==
Spróbuj w sposób analogiczny do listy ocen stworzyć listę uwag i osiągnięć. Pamiętaj o:<br />
* wydzieleniu metod, które pozwolą Ci w łatwy sposób odnaleźć się w kodzie aplikacji
* zamianie zmiennych lokalnych elementów GUI na pola klasy
* ustawieniu pustej listy uwag i osiągnięć na starcie aplikacji
* dodaniu okna uwag i ustawieniu odpowiedniej szerokości (odpowiedni parametr w konstruktorze)
*dodaniu odpowiedniej metody sprawdzającej stan przycisku i czy kliknięto przycisk '''ok''' w oknie
*dodaniu walidacji, spróbuj nie dodawać uwagi, gdy ktoś wprowadzi pusty tekst (sprawdzenie przez użycie metody '''!'''''"".equals(tekst))''
W pierwszej kolejności zmień zmienną lokalną '''uwagiListBox''' z konstruktora na pole. Na początku klasy, pod polem '''oknoOcen''' zadeklaruj pole '''uwagiListBox''':
<syntaxhighlight lang="java">
private InputWindow oknoOceny = new InputWindow("Podaj ocene", 1);
private ListBox<String> uwagiListBox;
</syntaxhighlight>
Zmień kod inicjujący listę, usuwając również wpisy uwag, na:
<syntaxhighlight lang="java">
ArrayList<String> uwagi = new ArrayList<String>();
Point rozmiarUwag = new Point(200, 60);
uwagiListBox = new ListBox<String>(rozmiarUwag, uwagi);
addObject(uwagiListBox, 300, 250);
</syntaxhighlight>
Skompiluj projekt i zobacz czy działa prawidłowo.<br />
Teraz poprawmy trochę czytelność kodu. Stwórz nową metodę '''utworzElemenyUwag()'''
<syntaxhighlight lang="java">
private void utworzElementyUwag() {

}
</syntaxhighlight>
Przenieś (wytnij i wklej) do niej kod odpowiedzialny za tworzenie listy uwag i etykiety.
<syntaxhighlight lang="java">
private void utworzElementyUwag() {
//stworzenie wyswietlania uwag
ArrayList<String> uwagi = new ArrayList<String>();
Point rozmiarUwag = new Point(200, 60);
uwagiListBox = new ListBox<String>(rozmiarUwag, uwagi);
addObject(uwagiListBox, 300, 250);
//stworzenie etykiety uwag
Label uwagiLabel = new Label("Uwagi/osiagniecia");
addObject(uwagiLabel, 300, 210);
}
</syntaxhighlight>
Pamiętaj o wywołaniu metody w konstruktorze.
<syntaxhighlight lang="java">
utworzElementyGuiOcen();
utworzElementyUwag();
</syntaxhighlight>
Ponownie skompiluj projekt. Wszystko powinno działać jak wcześniej.<br />
Teraz jesteśmy gotowi na dodanie funkcjonalności. Zadeklaruj pole z przyciskiem dodawania uwag.
<syntaxhighlight lang="java">
private Button przyciskUwag;
</syntaxhighlight>
W metodzie '''utworzElementyUwag''' zainicjuj przycisk i dodaj do świata.
<syntaxhighlight lang="java">
//stworzenie etykiety uwag
Label uwagiLabel = new Label("Uwagi/osiagniecia");
addObject(uwagiLabel, 300, 210);
przyciskUwag = new Button("Dodaj uwage", new Point(80, 20));
addObject(przyciskUwag, 450, 270);
</syntaxhighlight>
Skompiluj projekt. Czy widzisz nowo dodany przycisk?
[[File:jmk-dziennik_image06.png| center]]
Teraz potrzebujemy uruchomienia akcji na kliknięcie przycisku i otwarcia okna z pytaniem o uwagę.<br />
Dodajmy pole '''oknoUwag''', tak ak w przypadku ocen.
<syntaxhighlight lang="java">
private InputWindow oknoUwag = new InputWindow("Dodaj ocene lub osiagniecie", 100);
</syntaxhighlight>
Teraz dodajmy metodę '''sluchajNaPrzyciskuUwag''', w której otworzymy okno po klinięciu na przycisk.
<syntaxhighlight lang="java">

private void sluchajNaPrzyciskuUwag() {
//sprawdzenie czy wcisniego przycisk
if (przyciskUwag.wasClicked()) {
//pokazanie okna, z czyszczeniem wartosci
oknoUwag.toggleShow("");
}
}
</syntaxhighlight>
Teraz musimy już tylko wywołać tę metodę w metodzie '''act'''.
<syntaxhighlight lang="java">
public void act() {
sluchajNaPrzyciskuOceny();
sluchajNaPrzyciskuUwag();
}
</syntaxhighlight>
Ponownie skompiluj. Sprawdź czy jeśli klikniesz na przycisku, pojawi się okno.
[[File:jmk-dziennik_image40.png| center]]
Teraz wystarczy dodać kod, który doda uwagę do listy uwag. W metodzie '''sluchajNaPrzyciskuUwag''' dodaj kod, który do '''uwagiListBox''' doda wprowadzony wpis. Sprawdźmy od razu czy wpis nie jest pusty, a jeśli tak, zignorujmy go.
<syntaxhighlight lang="java">
//sprawdzenie czy wprowadzono uwage
String wprowadzonaUwaga = oknoUwag.getResult();
if (wprowadzonaUwaga != null && !"".equals(wprowadzonaUwaga)) {
//dodanie do listy uwag
uwagiListBox.add(wprowadzonaUwaga);
}
</syntaxhighlight>
<syntaxhighlight lang="java">
Instrukcja !"".equals(wprowadzonaUwaga) jest dość skomplikowana. W pierwszej kolejności przyjrzyjmy się konstrukcji
"".equals(wprowadzonaUwaga)
sprawdza ona tylko czy zmienna wprowadzonaUwaga nie jest pustym napisem.
"" - oznacza pusty napis
Wykrzyknik na początku ! jest opertorem negacji. To oznacza, że jeśli wartość jest prawdziwa (true), to po jej zanegowaniu będzie fałszywa (false).
Wyrażenie
if(!false){
jest równoznaczne
if(true){
</syntaxhighlight>
'''Skompiluj program.''' Spróbuj dodać nową uwagę. Czy wszystko działa prawidłowo? Spróbuj też dodać pustą uwagę, co się dzieje?

== Stworzenie klas ucznia i dziennika przedmiotu ==
Poprzedni rozdział pozwolił Ci na szlifowanie nabytych wcześniej umiejętności. W tym poznasz nowe elementy języka Java i przygotujesz aplikację tak, aby przypominała prawdziwy dziennik.<br />
Jak pewnie zauważyłeś, w aplikacji brakuje możliwości zmiany przedmiotu i ucznia. Żeby tego dokonać potrzebujemy klas, które przechowają uczniów oraz ich oceny i uwagi z poszczególnych przedmiotów. Z tego względu zróbmy najpierw nową klasę, która będzie przechowywać dla nas oceny i uwagi z pojedynczego przedmiotu.<br />
Wybierz w menu '''Edit -> NewClass''' aby dodać nową klasę.
[[File:jmk-dziennik_image53.png| center]]
Podaj jej nazwę, '''WynikiUcznia'''.
[[File:jmk-dziennik_image29.png| center]]
Zostanie stworzona klasa wygenerowana z polem x i przykładową metodą.
[[File:jmk-dziennik_image05.png| center]]
Usuń zarówno pole jak i metodę. Pozostaw tylko definicję klasy i konstruktor.
[[File:jmk-dziennik_image28.png| center]]
W tym momencie potrzebujemy listy przedmiotów oraz listy uwag, które obecnie są w naszej klasie świata. W sekcji importów dodaj:
<syntaxhighlight lang="java">
import java.util.*;
</syntaxhighlight>
Utwórzmy pola z listą ocen i listą uwag.
<syntaxhighlight lang="java">
public class WynikiUcznia {

private ArrayList<String> oceny;
private ArrayList<String> uwagi;
</syntaxhighlight>
Zainicjuj te pola w konstruktorze.
<syntaxhighlight lang="java">
public WynikiUcznia() {
oceny = new ArrayList<String>();
uwagi = new ArrayList<String>();
}
</syntaxhighlight>
Dodajmy tu jeszcze metody, które pozwolą nam dodać uwagę i ocenę oraz pobrać obie te listy. Pamiętaj o dodaniu metod publicznych, aby były dostępne w innych klasach.
<syntaxhighlight lang="java">
public void dodajOcene(String ocena) {
oceny.add(ocena);
}

public ArrayList<String> pobierzOceny() {
return oceny;
}

public void dodajUwage(String uwaga) {
uwagi.add(uwaga);
}

public ArrayList<String> pobierzUwagi() {
return uwagi;
}
</syntaxhighlight>
Świetnie, mamy już obiekt, w którym będziemy przechowywać dane z przedmiotu. Dodajmy jeszcze klasę ucznia. Załóż nową klasę, nazywając ją np. '''Uczen''' i podobnie jak poprzednio, usuń zbędne pole i metodę. Zastanów się jakie wartości będzie przechowywać.<br />

Analizując to, co prezentujemy na ekranie ważne będą:
* imię i nazwisko ucznia
* lista przedmiotów ucznia i jego oceny

Dodajmy na początku dwa pola: imię i nazwisko.
<syntaxhighlight lang="java">
public class Uczen {

private String imie;
private String nazwisko;
</syntaxhighlight>
Oceny przechowujemy w klasie '''WynikiUcznia''', teraz musimy się zastanowić jak będziemy przechowywać te informacje w klasie '''Uczen'''.<br />
Do tego celu idealnie nadaje się klasa Javy '''HashMap'''. Mapa jest obiektem w którym możemy przechowywać dane na zasadzie klucz -> wartość. Oznacza to, że możemy powiedzieć takiemu obiektowi, żeby pod podanym kluczem przechował nam inny obiekt. W naszym przypadku możemy użyć mapy do przechowania pod kluczem z nazwą przedmiotu, obiektu klasy '''WynikiUcznia''' z interesującymi nas wartościami.<br />
Spójrzmy jak wygląda konstruktor klasy HashMap i jakie ma główne metody.
[[File:jmk-dziennik_image07.png| center]]
Obrazek. Konstruktory klasy HashMap.<br />
Pierwszy konstruktor jest bezparametrowy, tworzy domyślną mapę, to jest dla nas wystarczające.<br />
Najważniejsze metody klasy HashMap to:
{| class="wikitable" border="1"
|-
! Metoda
! Opis
|-
| Object get(Object key)
| zwraca wartość z mapy znajdującą się pod podanym w parametrze kluczem
|-
| Object put(Object key, Object value)
| dodaje pod podanym w pierwszym parametrze kluczem wartość podaną w drugim parametrze
|-
| void clear()
| usuwa wszystkie elementy mapy
|-
| boolean containsKey(Object key)
| sprawdza czy mapa posiada wartość pod danym kluczem
|-
| boolean isEmpty()
| sprawdza czy mapa jest pusta
|-
| Set<Object> keySet()
| zwraca listę kluczy. Metoda jest bardzo przydatna podczas przechodzenia pętlą po mapie
|-
| int size()
| zwraca liczbę elementów znajdującą się w mapie
|}
Podobnie jak poprzednio z klasą ArrayList, aby użyć klasy HashMap dodaj na początku pliku import:
<syntaxhighlight lang="java">
import java.util.*;
</syntaxhighlight>
Teraz stwórzmy pole z mapą wyników z danych przedmiotów.
<syntaxhighlight lang="java">
private String imie;
private String nazwisko;

private HashMap<String, WynikiUcznia> przedmioty;
</syntaxhighlight>
Tworząc obiekt ucznia powinniśmy znać jego imię i nazwisko. Zmodyfikuj konstruktor tak aby przyjmował w parametrach te wartości i przypisze je do pól. Pamiętaj, że jeśli parametry nazwiesz tak samo jak pola, to musisz użyć słówka kluczowego '''this''' przed nazwą pola.<br />
W konstruktorze także zainicjuj mapę przedmiotów.
<syntaxhighlight lang="java">
public Uczen(String imie, String nazwisko) {
this.imie = imie;
this.nazwisko = nazwisko;

przedmioty = new HashMap<String, WynikiUcznia>();
}
</syntaxhighlight>
Dodajmy teraz metody zwracające imię i nazwisko ucznia.
<syntaxhighlight lang="java">
public String pobierzImie() {
return imie;
}

public String pobierzNazwisko() {
return nazwisko;
}
</syntaxhighlight>
Dodaj jeszcze metodę która zwróci połączone imię i nazwisko. Obiekty typu String możemy łączyć przez operator '''+'''. Możemy je łaczyć także z innymi obiektami i w ten sposób dostać połączony napis. Przykłady użycia:
<syntaxhighlight lang="java">
String wartosc1 = "dziś jest " + 21 + " maja";
String wartosc2 = "Ma na imie " + imie;
</syntaxhighlight>
Łącząc imię i nazwisko pamiętaj o odstępie pomiędzy słowami. Wystarczy użyć spacji w napisie.
<syntaxhighlight lang="java">
public String pobierzUcznia() {
return imie + " " + nazwisko;
}
</syntaxhighlight>
Przyda nam się także metoda tworząca przedmiot i zwracająca obiekt '''WynikiUcznia''' dla danego przedmiotu. Dodaj obie te metody.
<syntaxhighlight lang="java">
public void dodajWyniki(String przedmiot) {
//dodaje nowy przedmiot na mape, tworzy nowy obiekt, aby moc korzystac z przedmiotu
przedmioty.put(przedmiot, new WynikiUcznia());
}

public WynikiUcznia pobierzWyniki(String przedmiot) {
return przedmioty.get(przedmiot);
}
</syntaxhighlight>


Mamy już klasy do obsługi ocen z przedmiotów i uczniów. W tej chwili musimy poprawić kod w klasie '''DziennikUcznia''', aby korzystał ze stworzonych klas. Dodaj dwa pola w klasie '''DziennikUcznia''' o typie '''Uczen''' i '''WynikiUcznia'''. Uczen będzie przechowywał dane ucznia, '''WynikiUcznia''' przechowa aktualnie wyświetlane wyniki z przedmiotu.
<syntaxhighlight lang="java">
//pola uczenia i przedmiotu
private Uczen uczen;
private WynikiUcznia aktualneWyniki;
</syntaxhighlight>
W konstruktorze klasy '''DziennikUcznia''' stwórz nowy obiekt ucznia i dodaj przedmiot matematyka. Pamiętaj jaki konstruktor ma uczeń. Pobierz ten przedmiot do zmiennej aktualnego przedmiotu. Zmień inicjację etykiety ucznia, aby pobierał imię i nazwisko z metody klasy '''Uczen'''.<br />
Kod powinien wyglądać podobnie do podanego poniżej.
<syntaxhighlight lang="java">
uczen = new Uczen("Jan", "Kowalski");
uczen.dodajWyniki("matematyka");
aktualneWyniki = uczen.pobierzWyniki("matematyka");

// etykiety nazw
Label uczenLabel = new Label(uczen.pobierzUcznia());
</syntaxhighlight>
Musimy jeszcze zmienić kod dodający ocenę i uwagę, aby dodawał je do pola '''aktualneWyniki''' po czym odświeżał wartości na liście. Do odświeżania napiszmy metodę, która przyjmie listę gui ('''ListBox''') oraz '''ArrayList''' napisów do wyświetlenia. Musimy przejść pętlą po liście wartości i dodać każdą do '''ListBox'''. Musimy też najpierw wyczyścić '''ListBox'''. Przypomnij sobie, jakie metody w dokumentacji GuiComponents ma '''ListBox'''.<br />
Metoda powinna wyglądać tak:
<syntaxhighlight lang="java">
private void odswiezListBox(ListBox<String> listBox, ArrayList<String> wartosci) {
listBox.clear();
for (String wartosc : wartosci) {
listBox.add(wartosc);
}
}
</syntaxhighlight>
Aby przejść po liście, Java oferuje bardzo przyjazną konstrukcję, która pozwala w bardzo prosty sposób przejść po każdej z wartości.
<syntaxhighlight lang="java">
for (String wartosc : wartosci) {
listBox.add(wartosc);
}
</syntaxhighlight>
Teraz zmieńmy kod do wprowadzenia nowej uwagi- powinien wyglądać jak poniżej.
<syntaxhighlight lang="java">
if (wprowadzonaUwaga != null && !"".equals(wprowadzonaUwaga)) {
//dodanie do listy uwag
aktualneWyniki.dodajUwage(wprowadzonaUwaga);
odswiezListBox(uwagiListBox,aktualneWyniki.pobierzUwagi());
}
</syntaxhighlight>
Najpierw do aktualnego wyniku dodawana jest uwaga, a później wywołane jest odświeżenie listy. Wykorzystana jest wspólna metoda.<br />
Spróbuj zrobić podobnie dla ocen. Wykorzystaj wspólną metodę do odświeżania listy box.
<syntaxhighlight lang="java">
if (wartosc >= 1 && wartosc <= 6) {
//dodanie do listy ocen
aktualneWyniki.dodajOcene(wprowadzonaOcena);
odswiezListBox(ocenyListBox, aktualneWyniki.pobierzOceny());
}
</syntaxhighlight>
Tutaj także, najpierw do aktualnego wyniku dodawana jest nowa ocena, a później lista ocen jest odświeżana.<br />
'''Skompiluj teraz projekt.''' Czy wszystko działa tak jak poprzednio? Czy możesz dodawać uwagi i oceny?<br />
To ćwiczenie było bardzo wymagające, ale pozwoliło Ci zapoznać się z listami, mapami. Zobaczyłeś jak iterować po liście. Umiesz już tworzyć klasy i konstruktory z parametrami. '''Gratulacje!'''

== Zmiana przedmiotów na ekranie ==
Każdy dziennik zawiera wiele przedmiotów. W tym ćwiczeniu:
* dodasz możliwość zmiany przedmiotów,
* poćwiczysz dalsze wykorzystanie list,
* stworzysz też kilka metod, które spowodują, że kod będzie bardziej czytelny i łatwiejszy w obsłudze.
Przejdź do klasy '''Dziennik''' i stwórz kolejne przyciski - następny przedmiot, poprzedni przedmiot. Będą one potrzebne w metodze '''act()''', dlatego niech będą polami w klasie.
<syntaxhighlight lang="java">
//Przyciski zmiany przedmiotow
private Button poprzedniPrzedmiot, nastepnyPrzedmiot;
</syntaxhighlight>
Dodaj jeszcze tworzenie przycisków i umieśćmy je w świecie. W konstruktorze dodajmy wpisy:
<syntaxhighlight lang="java">
nastepnyPrzedmiot = new Button("nastepny przedmiot", new Point(120, 20));
poprzedniPrzedmiot = new Button("poprzedni przedmiot", new Point(120, 20));
addObject(nastepnyPrzedmiot, 450, 60);
addObject(poprzedniPrzedmiot, 150, 60);
</syntaxhighlight>
Ekran powinien wyglądać jak poniżej.
[[File:jmk-dziennik_image22.png| center|frame|Obrazek. Ekran po dodaniu przycisków do obsługi przedmiotów.]]
Brakuje nam teraz list przedmiotów, które chcemy wyświetlać oraz pola, które przechowa nam numer obecnego przedmiotu na liście. Dodaj 3 pola do klasy '''Dziennik''':
* Lista przedmiotów
* indeks aktualnie wyświetlanego przedmiotu, domyślnie niech będzie to 0
* etykietę przedmiotu. Musi od teraz być polem, gdyż będziemy ją zmieniać.
Nowe pola.
<syntaxhighlight lang="java">
//obsluga przedmiotow
private ArrayList<String> przedmioty;
private int aktualnyPrzedmiot = 0;
private final Label przedmiotLabel;
</syntaxhighlight>
Zainicjuj listę w konstruktorze kilkoma przedmiotami. Zmień inicjalizację etykiety przedmiotu, aby domyślnie była pusta
<syntaxhighlight lang="java">
public DziennikUcznia() {
...
przedmioty = new ArrayList<String>();
przedmioty.add("matematyka");
przedmioty.add("jezyk polski");
przedmioty.add("informatyka");

przedmiotLabel = new Label("");
</syntaxhighlight>
Skompiluj projekt. Czy widzisz różnice? Pole z nazwą przedmiotu jest puste. Za chwilę zajmiemy się jego aktualizacją.<br />
Teraz potrzebujemy napisać kod do obsługi wciśnięcia przycisków i zmiany aktualnie wyświetlanego przedmiotu.<br />
Dodaj metodę '''sluchajNaZmianePrzedmiotu()'''.
<syntaxhighlight lang="java">
private void sluchaNaZmianePrzedmiotu() {

}
</syntaxhighlight>
Teraz dodaj kod w tej metodzie, który sprawdzi czy wciśnięto któryś z tych przycisków. Jeśli wciśnięto przycisk “'''następny przedmiot'''”, musisz:<br />
* zwiększyć wartość pola '''aktualnyPrzedmiot''' o 1. Pamiętaj, że jeśli aktualnyPrzedmiot będzie równy wielkości listy przedmiotów, musisz ustawić wartość na 0, aby iść od początku listy
* zmień etykietę '''przedmiotLabel'''. Obecnie jest zmienną lokalną, zrób z niej pole, aby była dostępna w metodzie '''sluchajNaZmianePrzedmiotu'''. Ustaw wartość, która jest na liście pod indeksem '''aktualnyPrzedmiot'''.
<syntaxhighlight lang="java">
private void sluchajNaZmianePrzedmiotu() {
//czy wcisnieto nastepny przedmiot
if (nastepnyPrzedmiot.wasClicked()) {
//przejscie na nastepny przedmiot
aktualnyPrzedmiot = aktualnyPrzedmiot + 1;
//sprawdzenie czy nie doszedl do konca listy
if (aktualnyPrzedmiot >= przedmioty.size()) {
aktualnyPrzedmiot = 0;
}
String aktualnaNazwa = przedmioty.get(aktualnyPrzedmiot);
przedmiotLabel.setText(aktualnaNazwa);
}
</syntaxhighlight>
Wywołaj teraz metodę w metodzie '''act'''.
<syntaxhighlight lang="java">
public void act() {
...
sluchaNaZmianePrzedmiotu();

}
</syntaxhighlight>
Skompiluj projekt. Wciśnij przycisk następny przedmiot. Czy coś się dzieje?<br />
Podobnie zrób przy wciśnięciu przycisku “poprzedni przedmiot”. Pamiętaj, że tam należy zmniejszyć wartość '''aktualnyPrzedmiot'''. Pamiętaj też, że indeks nie może być mniejszy niż 0. Zwróć też uwagę, że lista jest numerowana od 0, więc jeśli jej rozmiar wynosi 5, to poprawne indeksy to wartości od 0 do 4.
<syntaxhighlight lang="java">
//czy wcisnieto przycisk porzedmiego przedmiotu
if (poprzedniPrzedmiot.wasClicked()) {
//przejscie na nastepny przedmiot
aktualnyPrzedmiot = aktualnyPrzedmiot - 1;
//sprawdzenie czy nie jest mniejszy od zera
if (aktualnyPrzedmiot < 0) {
aktualnyPrzedmiot = przedmioty.size() - 1;
}

String aktualnaNazwa = przedmioty.get(aktualnyPrzedmiot);
przedmiotLabel.setText(aktualnaNazwa);
}
</syntaxhighlight>
Teraz uruchom aplikację. Poklikaj na stworzone przyciski. Czy nazwa przedmiotu się zmienia?<br />
Spróbuj teraz dodać nową ocenę lub uwagę. Następnie zmień przedmiot. Czy aplikacja działa prawidłowo? Czy po zmianie przedmiotu lista nie powinna być czysta? Czy możesz powiedzieć dlaczego aplikacja właśnie tak się zachowuje?<br />
Problemem jest to, że lisa przedmiotów nie jest w tym momencie w żaden sposób powiązana z przedmiotami ucznia. Drugim problemem jest to, że odświeżanie elementów GUI, tj. listy ocen, uwag i nazwy przedmiotu, nie jest robione wspólnie. Poprawmy nasz kod.<br />
W pierwszej kolejności zróbmy metodę, która odświeży wszystkie wymienione elementy. Nazwij ją '''odswiezEkran()'''. Najpierw pobierz aktualny przedmiot i ustaw etykietę.
<syntaxhighlight lang="java">
private void odswiezEkran() {
//pobieram aktualny przedmiot
String nazwaAktualnegoPrzedmiotu = przedmioty.get(aktualnyPrzedmiot);
przedmiotLabel.setText(nazwaAktualnegoPrzedmiotu);
}
</syntaxhighlight>
W metodzie '''sluchajNaZmianePrzedmiotu''' zamień ustawianie etykiety na wywołanie metody '''odswiezEkran()'''. Pamiętaj, że dzieje się to w dwóch miejscach.
<syntaxhighlight lang="java">
//sprawdzenie czy nie doszedl do konca listy
if (aktualnyPrzedmiot >= przedmioty.size()) {
aktualnyPrzedmiot = 0;
}

// odswiezam ekran
odswiezEkran();
</syntaxhighlight>
oraz
<syntaxhighlight lang="java">
if (aktualnyPrzedmiot < 0) {
aktualnyPrzedmiot = przedmioty.size() - 1;
}

// odswiezam ekran
odswiezEkran();
}
</syntaxhighlight>
W metodzie '''odswiezEkran''' należy pobrać aktualne wyniki przedmiotu u ucznia, jeśli nie ma wyników, dodajmy je. Wyniki zapisz pod istniejącym polem - '''aktualneWyniki'''.
<syntaxhighlight lang="java">
private void odswiezEkran() {
..
//pobieram aktualne wyniki
aktualneWyniki = uczen.pobierzWyniki(nazwaAktualnegoPrzedmiotu);
if (aktualneWyniki == null) {
// jesli brak wynikow inicjalizuje je
uczen.dodajWyniki(nazwaAktualnegoPrzedmiotu);
aktualneWyniki = uczen.pobierzWyniki(nazwaAktualnegoPrzedmiotu);
}
</syntaxhighlight>
Teraz w tej samej metodzie uaktualnijmy listę uwag i ocen. Możemy wykorzystać kod, który obecnie jest w metodach nasłuchujących na przyciskach dodawania.
<syntaxhighlight lang="java">
private void odswiezEkran() {

...
// odświeżenie list uwag i ocen
odswiezListBox(uwagiListBox, wynikiUcznia.pobierzUwagi());
odswiezListBox(ocenyListBox, wynikiUcznia.pobierzOceny());
}
</syntaxhighlight>
Zamiast tamtego kodu umieść tam wywołanie metody '''odswiezEkran()'''.
<syntaxhighlight lang="java">
private void sluchajNaPrzyciskuUwag() {

if (wprowadzonaUwaga != null && !"".equals(wprowadzonaUwaga)) {
//dodanie do listy uwag
aktualneWyniki.dodajUwage(wprowadzonaUwaga);
odswiezEkran();
}
}
</syntaxhighlight>
oraz
<syntaxhighlight lang="java">
private void sluchajNaPrzyciskuOceny() {
...
try {
...
aktualneWyniki.dodajOcene(wprowadzonaOcena);
odswiezEkran();
}
} catch (NumberFormatException e) {
//ignorujemy, po prostu nie dodajmy do listy
}
}

}
</syntaxhighlight>
Poprawmy też kod w konstruktorze. Usuń kod dodający wynik ucznia oraz pobierający te wyniki do pola aktualneWyniki. Zamiast tego na końcu konstruktora, tuż przed wywołaniem '''Greenfoot.start()''' wywołaj metodę odświeżającą ekran.
<syntaxhighlight lang="java">
public DziennikUcznia() {
...
//uczen = new Uczen("Jan", "Kowalski");
//uczen.dodajWyniki("matematyka");
aktualneWyniki = uczen.pobierzWyniki("matematyka");

...
odswiezEkran();

Greenfoot.start();
}
</syntaxhighlight>
Uruchom teraz aplikację i spróbuj powtórzyć test. Dodaj ocenę np. do jednego przedmiotu, przejdź do następnego, dodaj tam uwagę, a następnie wróć do poprzedniego. Czy wprowadzona ocena jest widoczna?

== Lista uczniów ==
W tym kroku dodamy do naszej aplikacji listę uczniów. Dzięki temu będziemy mogli przełączać się pomiędzy kolejnymi osobami w dzienniku.<br />
W pierwszej kolejności dodaj 2 przyciski: następny i poprzedni uczeń, podobnie jak zrobiłeś to przy przedmiotach. Przyciski będą potrzebne nam później przy sprawdzaniu, czy zostały wciśnięte, więc niech będą polami w klasie Dziennik.
<syntaxhighlight lang="java">
public class DziennikUcznia extends World {
...

//przyciski uczniow
private Button poprzedniUczen, nastepnyUczen;
</syntaxhighlight>
Zainicjujmy je w konstruktorze i dodajmy do świata.
<syntaxhighlight lang="java">
poprzedniUczen = new Button("Poprzedni uczen", new Point(120, 20));
nastepnyUczen = new Button("Nastepny uczen", new Point(120, 20));
addObject(poprzedniUczen, 150, 30);
addObject(nastepnyUczen, 450, 30);
</syntaxhighlight>
Ekran powinien wyglądać jak na obrazku poniżej.
[[File:jmk-dziennik_image33.png| center]]
Teraz potrzebujemy listy, która przechowa nam uczniów. Stwórz listę uczniów, jako pole w klasie.
<syntaxhighlight lang="java">
//pelna lista uczniow
private ArrayList<Uczen> uczniowie;
</syntaxhighlight>
Lista przechowująca obiekty klasy '''Uczen'''.<br />
Zainicjuj pole w konstruktorze, dodając kilka osób - będą nam potrzebne do testów.
<syntaxhighlight lang="java">
public DziennikUcznia() {
...
uczniowie = new ArrayList<Uczen>();
uczniowie.add(new Uczen("Anna", "Nowak"));
uczniowie.add(new Uczen("Pawel", "Kryszalowicz"));
uczniowie.add(new Uczen("Jan", "Kowalski"));
</syntaxhighlight>
Zmień też nazwę pola '''uczen''', na bardziej znaczącą aktualnyUczen. Dodaj też pole '''indeksAktualnegoUcznia''', przechowujący numer aktualnie wyświetlanego ucznia.
<syntaxhighlight lang="java">
//private Uczen uczen;
private Uczen aktualnyUczen;
//indeks aktualnego ucznia
private int indeksAktualnegoUcznia;
</syntaxhighlight>
Zmieniając nazwę pola musisz zmienić wszystkie jego użycia w klasie. Jeśli pole jest publiczne to także wszystkie użycia w innych klasach. Jednak nie przejmuj się, jeśli zapomnisz gdzieś poprawić nową nazwę, to kompilator Cię ostrzeże i wymusi poprawkę, takim oto komunikatem.
[[File:jmk-dziennik_image15.png| center]]
Przykład błędu kompilacji, po zmianie nazwy pola.<br />
Zmień sposób inicjacji pola, niech będzie pobierany pierwszy uczeń z listy.<br />
Kod inicjujący uczniów.
<syntaxhighlight lang="java">
uczniowie.add(new Uczen("Pawel", "Kryszalowicz"));
uczniowie.add(new Uczen("Jan", "Kowalski"));

indeksAktualnegoUcznia = 0;
aktualnyUczen = uczniowie.get(indeksAktualnegoUcznia);
</syntaxhighlight>
Potrzebny jest już tylko kod, który będzie sprawdzał czy wciśnięto przyciski i zmieniał uczniów.<br />
Stwórzmy metodę '''sluchajNaZmianieUcznia''', aby kod był czytelny. Tak jak w poprzedniej części dotyczącej przedmiotów, dodajmy kod, który będzie aktualizował indeks aktualnego ucznia, pobierał go i odświeżał ekran.
<syntaxhighlight lang="java">
private void sluchajNaZmianeUcznia() {

if (poprzedniUczen.wasClicked()) {
indeksAktualnegoUcznia = indeksAktualnegoUcznia - 1;
if (indeksAktualnegoUcznia < 0) {
indeksAktualnegoUcznia = uczniowie.size() - 1;
}
aktualnyUczen = uczniowie.get(indeksAktualnegoUcznia);
odswiezEkran();
}

if (nastepnyUczen.wasClicked()) {
indeksAktualnegoUcznia = indeksAktualnegoUcznia + 1;
if (indeksAktualnegoUcznia >= uczniowie.size()) {
indeksAktualnegoUcznia = 0;
}
aktualnyUczen = uczniowie.get(indeksAktualnegoUcznia);
odswiezEkran();
}

}
</syntaxhighlight>
Musimy jeszcze pamiętać o odświeżaniu etykiety przy zmianie ucznia - musisz przenieść zmienną lokalną do pola klasy.
<syntaxhighlight lang="java">
public class DziennikUcznia extends World {


private Label uczenLabel;
</syntaxhighlight>
Musimy jeszcze zmienić w konstruktorze inicjalizacji tego pola.
<syntaxhighlight lang="java">
indeksAktualnegoUcznia = 0;
aktualnyUczen = uczniowie.get(indeksAktualnegoUcznia);

// etykiety nazw
uczenLabel = new Label(aktualnyUczen.pobierzUcznia());
</syntaxhighlight>
Pozostaje nam w metodzie '''odswiezEkran''', dodać aktualizowanie tej wartości.
<syntaxhighlight lang="java">
private void odswiezEkran() {

...

// odswiezenie etykiety ucznia
uczenLabel.setText(aktualnyUczen.pobierzUcznia());
}
</syntaxhighlight>
Uruchom program i sprawdź czy zmiana ucznia działa. Czy wszystko działa prawidłowo? Jeśli zapomnisz lub pomylisz się przy sprawdzaniu poprawności indeksu możesz otrzymać komunikat jak poniżej. Numery linii mogą się różnić.
[[File:jmk-dziennik_image38.png| center]]
Jest to efektem błędu w poniższym kodzie, po kliknięciu kilkukrotnie na “następny uczeń” pojawi się wcześniej wspomniany błąd. Czy wiesz dlaczego do tego doszło?
<syntaxhighlight lang="java">
if (poprzedniUczen.wasClicked()) {
indeksAktualnegoUcznia = indeksAktualnegoUcznia - 1;
if (indeksAktualnegoUcznia < 0) {
indeksAktualnegoUcznia = uczniowie.size();
}
aktualnyUczen = (Uczen) uczniowie.get(indeksAktualnegoUcznia);
odswiezEkran();
}
</syntaxhighlight>
Odpowiedzią jest, przypisanie złej wartości do pola '''indeksAktualnegoUcznia'''. Przypisanie aktualnej wielkości listy, oznacza, że przy pobieraniu odwołamy się do nieistniejącej wartości.

== Menu górne - dodawanie ucznia i przedmiotu ==
W tym kroku dodamy nową funkcjonalność do aplikacji: dodawanie nowego ucznia i nowego przedmiotu.<br />
Zajrzyj do projektu '''Gui Components'''. Znajduje się w nim klasa '''Menu''', którą wykorzystamy, aby dodać nasze menu. Jej użycie jest trochę skomplikowane, ale na przykładzie wyjaśnię, jak stworzyć nasze menu.<br />
Zacznijmy od umieszczenia na górze strony menu z jednym elementem '''Dziennik'''. Następnie dodajmy 2 pozycje rozwijane z tego menu '''Dodaj przedmiot, Dodaj ucznia'''. <br />
W pierwszej kolejności dodajmy do świata pole z naszym menu.
<syntaxhighlight lang="java">
private Menu menuGlowne;
</syntaxhighlight>
Dodajmy metodę '''inicjujMenu'''. Stwórzmy w niej menu i umieśćmy na ekranie.
<syntaxhighlight lang="java">
private void inicjujMenu() {

ArrayList<String> pozycjeMenu = new ArrayList<String>();
pozycjeMenu.add("Dziennik");

menuGlowne = new Menu(pozycjeMenu);
addObject(menuGlowne, 25, 5);
}
</syntaxhighlight>
Nie zapomnijmy wywołać tej metody w konstruktorze świata.
<syntaxhighlight lang="java">
utworzElementyUwag();

inicjujMenu();

//ustawienie tla ekranu
setBackground("tlo.jpg");
</syntaxhighlight>
Skompiluj projekt. Czy menu się pojawia? Jeśli chcesz umieścić je w innym miejscu, możesz zrobić to zmieniając odpowiednie parametry w metodzie '''addObject'''.<br />
Jak pewnie zauważyłeś, menu po kliknięciu nie rozwija się. Dodajmy 2 pozycje '''Dodaj ucznia''' oraz '''Dodaj przedmiot'''. W metodzie inicjujMenu umieść kod
<syntaxhighlight lang="java">
private void inicjujMenu() {

...

menuGlowne.addItem("Dziennik/Dodaj ucznia");
menuGlowne.addItem("Dziennik/Dodaj przedmiot");
}
</syntaxhighlight>
Za pomocą metody '''addItem''' wywołanej na obiekcie menu, dodawane jest podrzędne menu. Trzeba najpierw podać nazwę głównego elementu, a następnie po ukośniku nazwę podmenu, które pojawi się po rozwinięciu.<br />
Uruchom teraz aplikację. Czy rozwijanie już działa? Jeśli wszystko jest ok, to w efekcie powinieneś po uruchomieniu aplikacji zobaczyć ekran jak poniżej.
[[File:jmk-dziennik_image57.png| center|frame|Obrazek. Dodanie menu górnego.]]
Musimy teraz dodać słuchacza na kliknięcie w menu. Przydatna tutaj będzie metoda Menu.getItemPressed(). Pozwoli ona sprawdzić jaki element menu został wciśnięty. Stwórz nową metodę, która obsłuży wciśniecie przycisku.
<syntaxhighlight lang="java">
private void sluchajNaMenu() {

String wybraneMenu = menuGlowne.getItemPressed();
if (wybraneMenu != null) {
if (wybraneMenu.startsWith("Dziennik/")) {

//wybranie nazwy po ukosniku /
wybraneMenu = wybraneMenu.substring(wybraneMenu.indexOf('/') + 1);

if (wybraneMenu.equals("Dodaj ucznia")) {
// otwarcie okna dodawania ucznia

} else if (wybraneMenu.equals("Dodaj przedmiot")) {
// otwarcie okna dodawania przedmiotu
}
}
}

}
</syntaxhighlight>
Powyższa metoda jest odrobinę skomplikowana. Pierwszy warunek:
<syntaxhighlight lang="java">
if (wybraneMenu != null)
</syntaxhighlight>
sprawdza czy kliknięto na menu. Kolejny:
<syntaxhighlight lang="java">
if (wybraneMenu.startsWith("Dziennik/")) {
</syntaxhighlight>
to sprawdzenie czy kliknięto na element, który znajduje się pod menu '''Dziennik'''. Kolejny krok:
<syntaxhighlight lang="java">
wybraneMenu = wybraneMenu.substring(wybraneMenu.indexOf('/') + 1);
</syntaxhighlight>
to pobranie drugiego członu nazwy. Najpierw pobieram miejsce ukośnika w napisie,<br /> wybraneMenu.indexOf('/')<br />
a następnie “wycinam” napis, zostawiając część, od tego miejsca + 1.<br />
wybraneMenu.substring(wybraneMenu.indexOf('/') + 1)<br />
Dodaj jeszcze wywołanie metody w metodzie '''act'''.
<syntaxhighlight lang="java">
@Override
public void act() {

...
sluchajNaMenu();
}
</syntaxhighlight>
Brakuje nam jeszcze uruchomienia rzeczywistych akcji, które pozwolą nam na dodanie nowego przedmiotu i ucznia. Zrobimy to za pomocą wyskakujących okien.<br />
Dodaj pole typu '''InputWindow''', które pozwoli zapytać nam o nazwę przedmiotu. Zainicjuj je przy deklaracji.
<syntaxhighlight lang="java">
private Menu menuGlowne;
private InputWindow wprowadzPrzedmiotOkno = new InputWindow("Podaj nazwe przedmiotu", 15);
</syntaxhighlight>
Teraz powinniśmy pokazać to okno przy kliknięciu na odpowiednie menu. W metodzie '''sluchajNaMenu''' wywołaj wyświetlanie okna w miejscu gdzie wcześniej był komentarz.
<syntaxhighlight lang="java">
} else if (wybraneMenu.equals("Dodaj przedmiot")) {
// otwarcie okna dodawania przedmiotu
wprowadzPrzedmiotOkno.toggleShow("");
}
</syntaxhighlight>
Okno powinno wyglądać jak poniżej.
[[File:jmk-dziennik_image41.png| center]]
Teraz dodaj metode '''sluchajNaDodaniePrzedmiotu''', umieść sprawdzanie czy dodano nowy przedmiot. Jeśli tak się stało - dodaj go do listy. Zmień także pole '''aktualneWyniki''', aby wskazywało na nowo dodany przedmiot. Jakie pola musisz zaktualizować? Konieczne będzie także odświeżenie ekranu.
<syntaxhighlight lang="java">
private void sluchajNaDodaniePrzedmiotu() {
String nazwaPrzedmiotu = wprowadzPrzedmiotOkno.getResult();
if (nazwaPrzedmiotu != null && !nazwaPrzedmiotu.isEmpty()) {
przedmioty.add(nazwaPrzedmiotu);
aktualnyPrzedmiot = przedmioty.size() - 1;

aktualneWyniki = aktualnyUczen.pobierzWyniki(nazwaPrzedmiotu);

odswiezEkran();
}
}
</syntaxhighlight>
Pamiętaj o wywołaniu metody w metodzie '''act'''.
<syntaxhighlight lang="java">
public void act() {
...
sluchajNaDodaniePrzedmiotu();
}
</syntaxhighlight>
Uruchom aplikację i sprawdź czy dodawanie nowego przedmiotu działa.<br />
Druga część ćwiczenia będzie trochę trudniejsza. Stworzymy w niej nowe okno, w którym dodamy dwa pola '''imię''' i '''nazwisko''' do wprowadzania ucznia.<br />
Nowe okno to będzie nowa klasa - aktor, która będzie dziedziczyć z klasy '''Window'''. Przykładem takiej klasy jest '''TextWindow''', którą możesz podejrzeć w '''GUI Components'''.<br />
Stwórz nową klasę przez kliknięcie PPM na klasę aktora i wybranie '''New subclass'''.
[[File:jmk-dziennik_image04.png| center]]
Swoją klasę nazwij '''NowyUczenWindow'''. Nowa klasa będzie miała metodę '''act''' i będzie dziedziczyć po klasie '''Actor'''. Zmień kod tak, aby dziedziczyła z klasy '''Window'''. Musisz też dodać odpowiedni konstruktor, który wywoła konstruktor klasy '''Window'''. Gdybyś tego nie zrobił, pojawiłby się błąd kompilacji.<br />
Klasa okna powinna wyglądać jak poniżej.
<syntaxhighlight lang="java">
public class NowyUczenWindow extends Window
{
public NowyUczenWindow() {
super("Dodaj ucznia");
}

public void act()
{
// Add your action code here.
}
}
</syntaxhighlight>
Teraz dodaj dwa pola typu '''TextBox''': '''imieBox''', '''nazwiskoBox''' oraz dwa przyciski: '''Enter''' i '''Cancel'''.
<syntaxhighlight lang="java">
public class NowyUczenWindow extends Window {

private TextBox imieBox, nazwiskoBox;

private Button enterBtn = new Button("Enter", new Point(50, 23));
private Button cancelBtn = new Button("Cancel", new Point(50, 23));
</syntaxhighlight>
Zainicjuj je w konstruktorze. Zrób to tak samo jak w klasie '''Dziennik''', aby były widoczne na ekranie. Pamiętaj, że metoda '''super'''(..), musi być wywołana jako pierwsza instrukcja w konstruktorze.
<syntaxhighlight lang="java">
public NowyUczenWindow() {
super("Dodaj ucznia");

imieBox = new TextBox(new Point(50, 20), "");
nazwiskoBox = new TextBox(new Point(50, 20), "");
</syntaxhighlight>
Teraz przejdziemy do trudniejszej części, a mianowicie rozmieszczenia wszystkiego nowym oknie. W tym celu wykorzystamy klasę '''Container''', która działa trochę jak siatka. Np. takie wywołanie
<syntaxhighlight lang="java">
new Container(new Point(2, 3));
</syntaxhighlight>
Mówi, że kontener jest podzielony na dwie kolumny i trzy wiersze.<br />
Kontener, podzielony na dwie kolumny i trzy wiersze.<br />
Dodajmy teraz w konstruktorze takie kontenery, które pozwolą na umieszczenie w nich etykiet i pól.
<syntaxhighlight lang="java">

Container c = new Container(new Point(1, 3));
Container kontenerImie = new Container(new Point(2, 1));
kontenerImie.addComponent(new Label("Imie:"));
kontenerImie.addComponent(this.imieBox);
c.addComponent(kontenerImie);
Container kontenerNaziwsko = new Container(new Point(2, 1));
kontenerNaziwsko.addComponent(new Label("Nazwisko:"));
kontenerNazwisko.addComponent(this.nazwiskoBox);
c.addComponent(kontenerNazwisko);
Container btns = new Container(new Point(2, 1));
btns.addComponent(this.enterBtn);
btns.addComponent(this.cancelBtn);
c.addComponent(btns);
addContainer(c);
</syntaxhighlight>
W pierwszej części tworzymy kontener z jedną kolumną i trzema wierszami.<br />
Później tworzymy kontener z dwiema kolumnami i jendym wierszem, do którego dodajemy najpierw etykietę '''imię''' i pole tekstowe.<br />
Następnie dodajemy ten kontener do głównego kontenera.<br />
W kolejnym kroku postępujemy podobnie: w drugim wierszu dodamy kolejny kontener z nazwiskiem.
Ostatni kontener przechowa nam dwa przyciski, które zamkną okno.<br />
Na końcu główny kontener umieszczamy w oknie, wywołując addContainer(c).<br />
Najpierw obsłużmy przypadek, gdy ktoś kliknie przycisk '''Cancel'''. W metodzie '''act''' dodaj nasłuchiwanie na wciśnięcie przycisku. Pamiętaj, żeby w metodzie dodać wywołanie metody '''super.act()'''. Wywołuje to metodę '''act''' z klasy nadrzędnej. Jest to konieczne do prawidłowego działania.
<syntaxhighlight lang="java">
public void act() {
super.act();
if (this.cancelBtn.wasClicked()) {
this.toggleShow();
}
</syntaxhighlight>
Teraz, na wzór poprzednich okien, potrzebujemy metody, która zwróci nam dane, jeśli wciśnięty zostanie przycisk '''Enter'''. Najpierw dodaj pole '''nowyUczen''', które będzie przechowywać wynik okna.
<syntaxhighlight lang="java">
public class NowyUczenWindow extends Window {
...
private Uczen nowyUczen;
</syntaxhighlight>
Teraz w metodzie '''act''' dodaj sprawdzanie czy wciśnięto przycisk '''Enter''', jeśli tak, to niech utworzy nowy obiekt '''Uczen''', oraz wyczyści pola tekstowe i ukryje wyświetlane okno.
<syntaxhighlight lang="java">
if (this.enterBtn.wasClicked()) {
this.nowyUczen = new Uczen(imieBox.getText(), nazwiskoBox.getText());
imieBox.setText("");
nazwiskoBox.setText("");
this.toggleShow();
}
</syntaxhighlight>
Teraz potrzebujemy metody '''getResult''', która zwróci obiekt ucznia, jeśli taki został utworzony. Po wywołaniu tej metody, musimy to pole wyczyścić.
<syntaxhighlight lang="java">
public Uczen getResult() {
Uczen temp = nowyUczen;
nowyUczen = null;
return temp;
}
</syntaxhighlight>
Musimy do zmiennej tymczasowej '''temp''' przypisać aktualną wartość ucznia, następnie czyścimy pole ucznia i zwracamy obiekt, który był wcześniej.<br />
Skompiluj projekt, jeśli wszystko jest ok, możemy wykorzystać nasze własne okno. Przejdź do klasy '''DziennikUcznia'''.<br />
Najpierw dodajmy nasze nowe okno, podobnie jak okno do wprowadzania przedmiotu.
<syntaxhighlight lang="java">
private InputWindow wprowadzPrzedmiotOkno = ...

private NowyUczenWindow wprowadzUczniaOkno = new NowyUczenWindow();
</syntaxhighlight>
Teraz wyświetl to okno w metodzie '''sluchajNaMenu'''.
<syntaxhighlight lang="java">
if (wybraneMenu.equals("Dodaj ucznia")) {
// otwarcie okna dodawania ucznia
wprowadzUczniaOkno.toggleShow();
</syntaxhighlight>
Teraz zostaje nam już tylko dodanie słuchacza na oknie ucznia, który doda ucznia, jeśli ten został wybrany. Stwórz metodę '''sluchajNaDodanieUcznia'''.
<syntaxhighlight lang="java">
private void sluchajNaDodanieUcznia() {

Uczen nowyUczen = wprowadzUczniaOkno.getResult();
if (nowyUczen != null) {
uczniowie.add(nowyUczen);
aktualnyUczen = nowyUczen;
indeksAktualnegoUcznia = uczniowie.size() - 1;
odswiezEkran();
}
}
</syntaxhighlight>
Podobnie jak w innych metodach, sprawdzamy czy okno zwraca rezultat. Jeśli tak, to dodajemy ucznia na listę uczniów. Nowego ucznia ustawiamy także jako aktualnego, ustawiamy też '''indeksAktualnegoUcznia''' na ostatni element w liście, czyli na numer nowo dodanego ucznia. Na końcu odświeżamy ekran.<br />
Skompiluj program. Sprawdź czy przycisk dodawania ucznia działa. Przetestuj aplikację, czy wszystko jest ok?

== Zapis danych do pliku ==
W tym kroku nauczysz się jak korzystać z plików - jak zapisać dane do pliku i je odczytać. Dzięki temu, będzie można dodawać uczniów, przedmioty, wstawiać oceny, a po zamknięciu aplikacji będzie można ponownie te dane wczytać, podając nazwę pliku.<br />
Na początku, przygotujmy aplikację do obsługi zapisu i odczytuj do pliku. W następnych krokach zrobimy właściwy odczyt i zapis pliku.<br />
Dodajmy najpierw dwa pola okien w klasie '''DziennikUcznia''' z pytaniami o nazwę pliku. Jedno pole z nazwą eksportowanego pliku, drugie z nazwą importowanego.
<syntaxhighlight lang="java">
private InputWindow plikEksportOkno = new InputWindow("Podaj nazwe pliku", 30);
private InputWindow plikImportOkno = new InputWindow("Podaj nazwe pliku", 30);
</syntaxhighlight>
Następnie dodajmy do menu nowe opcje wyboru. W metodzie '''inicjujMenu''' zmień kod na:
<syntaxhighlight lang="java">
private void inicjujMenu() {

ArrayList<String> pozycjeMenu = new ArrayList<String>();
pozycjeMenu.add("Dziennik");
pozycjeMenu.add("Operacje");

menuGlowne = new Menu(pozycjeMenu);
addObject(menuGlowne, 50, 5);

menuGlowne.addItem("Dziennik/Dodaj ucznia");
menuGlowne.addItem("Dziennik/Dodaj przedmiot");

menuGlowne.addItem("Operacje/Eksportuj");
menuGlowne.addItem("Operacje/Importuj");
}
</syntaxhighlight>
Dodaliśmy nowe menu główne '''Operacje''', poszerzyliśmy menu (metoda '''addObject'''), aby ładnie się wyświetlało i na końcu dodaliśmy dwa podmenu: Importuj i Eksportuj.<br />
Uruchom aplikację i zobacz czy menu jest widoczne.<br />
Teraz do metody '''sluchajNaMenu''' dodaj kod, który pokaże nowo dodane okna. Niech w oknach pojawią się domyślne wartości '''dane.txt'''.
<syntaxhighlight lang="java">
if (wybraneMenu.startsWith("Dziennik/")) {

...

} else if (wybraneMenu.startsWith("Operacje")) {
//wybranie nazwy po ukosniku /
wybraneMenu = wybraneMenu.substring(wybraneMenu.indexOf('/') + 1);

if (wybraneMenu.equals("Eksportuj")) {
//pokazanie okna z domyslna wartoscia
plikEksportOkno.toggleShow("dane.txt");

} else if (wybraneMenu.equals("Importuj")) {

//pokazanie okna z domyslna wartoscia
plikImportOkno.toggleShow("dane.txt");

}
}
</syntaxhighlight>

Kod jest analogiczny do tego z poprzedniego ćwiczenia. Metoda
<syntaxhighlight lang="java">plikImportOkno.toggleShow("dane.txt");</syntaxhighlight>
wyświetla nowe okno z ustawionym tekstem "dane.txt".<br />
Uruchom aplikację i zobacz czy wyświetlanie okien działa prawidłowo.<br />
Teraz dodajmy kod, który wywoła się, gdy ktoś wprowadzi nazwę pliku do eksportu.<br />
Stwórz metodę '''sluchajNaImportExport'''.
<syntaxhighlight lang="java">
private void sluchajNaImportExport() {

String nazwaPlikuExportu = plikEksportOkno.getResult();
//jesli wprowadzono nazwe
if (nazwaPlikuExportu != null) {
//eksportuje do pliku
}
}
</syntaxhighlight>
W miejscu komentarza ''//eksportuje do pliku'' będziemy musieli wstawić kod do eksportu. Wywołaj jeszcze metodę w metodzie act.
<syntaxhighlight lang="java">
public void act() {
...
sluchajNaImportExport();
}
</syntaxhighlight>
Teraz napiszmy kod do eksportu danych do pliku.<br />
Stwórz nową klasę '''Eksporter''', w której umieścimy kod do zapisu pliku. Usuń niepotrzebne metody. Na górze pliku umieść importy tak, jak w przykładzie poniżej.
<syntaxhighlight lang="java">
import java.io.*;
import java.util.*;

import org.apache.commons.io.*;

public class Eksporter {

public Eksporter() {

}
}
</syntaxhighlight
Importy będą nam potrzebne przy używaniu klas związanych z plikami oraz list.<br />
Teraz dodajmy dwa pola '''uczniowe''' i '''przedmioty''', które będę przechowywać dane, które będziemy zapisywać w pliku. Zróbmy tak, aby te dane trzeba było podać przy tworzeniu obiektu- musimy więc stworzyć odpowiedni konstruktor.
<syntaxhighlight lang="java">
public class Eksporter {
private ArrayList<Uczen> uczniowie;
private final ArrayList<String> przedmioty;

public Eksporter(ArrayList<Uczen> uczniowie, ArrayList<String> przedmioty) {
this.uczniowie = uczniowie;
this.przedmioty = przedmioty;
}
</syntaxhighlight>
Wyrażenie this.uczniowe = uczniowe mówi, żeby do pola (nazwa poprzedzona słówkiem kluczowym '''this'''.) przypisać parametr '''uczniowie'''.<br />
Teraz musimy napisać metodę, która przygotuje nam dane do zapisu. Zróbmy metodę, która będzie zwracała listę napisów. Później, każdy element z listy będzie zapisany w oddzielnym wierszu w pliku.<br />
Musimy jeszcze ustalić jak zapisać dane, żeby były łatwe do odczytania. Zróbmy to w ten sposób:
# W pierwszej linii wypiszmy wszystkie przedmioty po przecinku
# W kolejnych liniach będziemy zapisywać uczniów jeden pod drugim wraz z ich wynikami
## w pierwszej linii ucznia zapiszmy jego imię
## w drugiej nazwisko
## następnie dla każdego przedmiotu będziemy pobierać wyniki i zapisywać w ten sposób
##* najpierw nazwa przedmiotu
##* później lista ocen po przecinku
##* następnie lista uwag po przecinku
Zaczynajmy!<br />
Najpierw zapiszmy nazwę przedmiotów w pierwszej linii. Stwórz metodę '''pobierzDane''' i do listy zapisz przedmioty po przecinku.
<syntaxhighlight lang="java">
private ArrayList<String> pobierzDane() {

ArrayList<String> linie = new ArrayList<String>();

//inicujuje pusta linie
String liniaPrzedmiotu = "";

//zapis plikow po przecinku w pierwszej linii
for (int i = 0; i < this.przedmioty.size(); i++) {
String przedmiot = this.przedmioty.get(i);

if (i > 0) {
liniaPrzedmiotu = liniaPrzedmiotu + ",";
}
liniaPrzedmiotu = liniaPrzedmiotu + przedmiot;

}
//zapisuje linie z przedmiotami
linie.add(liniaPrzedmiotu);
return linie;
}
</syntaxhighlight>
Jak możesz zauważyć, najpierw tworzona jest pusta lista i pusty napis.<br />
Następnie w pętli for pobieramy każdy przedmiot i zapisujemy na liście.<br />
Jeśli przedmiot nie jest pierwszym if (i > 0) { to dodajmy przecinek przed nowym przedmiotem.<br />
Na końcu tak stworzony napis dodajemy do listy '''linie'''. Zwracamy tę listę.<br />
Teraz dodajmy metodę publiczną, która zapisze zwracaną listę do pliku.
<syntaxhighlight lang="java">
public void eksportuj(String nazwaPliku) {

//pobranie listy wierszy do zapisania
ArrayList<String> zawartosc = pobierzDane();

try {
//zapis do pliku
FileUtils.writeLines(new File(nazwaPliku), zawartosc);
} catch (IOException e) {
//zlapanie wyjatku
System.out.println("Zapis do pliku" + nazwaPliku + " nieudany!");
e.printStackTrace();
}

}
</syntaxhighlight>
Metoda jest publiczna, po to, aby można było wywołać ją z innych klas.<br />
W pierwszym kroku metoda pobiera listę wierszy do zapisania.<br />
Następnie w znanym nam już bloku try{ } catch() łapie wyjątek IOException, który jest wyjątkiem występującym, gdy zapis pliku się nie powiedzie. W takim wypadku, na konsoli za pomocą System.out.println wyświetlamy komunikat o błędzie.<br />
Z kolei e.printStackTrace() wyświetla szczegóły wyjątku.<br />
Najważniejsze dla nas jest jednak wywołanie:
<syntaxhighlight lang="java">
FileUtils.writeLines(new File(nazwaPliku), zawartosc);
</syntaxhighlight>
które zapisuje w nowym pliku o podanej w parametrze nazwie (new File(nazwaPliku)), zawartość, czyli listę wierszy.<br />
Metoda jest gotowa, wywołajmy ją z klasy '''DziennikUcznia''', w metodzie '''sluchajNaImportExport'''.
<syntaxhighlight lang="java">
if (nazwaPlikuExportu != null) {
//eksportuje do pliku
Eksporter exporter = new Eksporter(uczniowie, przedmioty);
exporter.eksportuj(nazwaPlikuExportu);
}
</syntaxhighlight>
Najpierw tworzymy obiekt eksportera z listą uczniów i przedmiotów. Następnie wywołujemy eksport podając nazwę wprowadzonego pliku.<br />
Skompiluj program. Dodaj jakiś przedmiot i uruchom eksport. Podaj domyślną nazwę pliku.
[[File:jmk-dziennik_image42.png| center]]
Kliknij '''Enter'''. Teraz przejdź do katalogu w którym tworzyłeś projekt. Powinien pojawić się nowy plik o nazwie '''dane.txt'''.
[[File:jmk-dziennik_image43.png| center]]
Otwórz go. Powinieneś ujrzeć taki wpis:
[[File:jmk-dziennik_image20.png| center]]
Świetnie. Teraz możemy zapisać resztę danych.<br />
Przejdź do klasy '''Exporter'''. Musimy zmodyfikować metodę '''pobierzDane''', tak aby wpisała resztę pozycji.<br />
Dodajmy pętlę, która przejdzie po wszystkich uczniach:
<syntaxhighlight lang="java">
for (int i = 0; i < uczniowie.size(); i++) {

Uczen uczen = uczniowie.get(i);

}
return linie;
</syntaxhighlight>
Teraz po pobraniu ucznia dodajmy dwie linie z imieniem i nazwiskiem.
<syntaxhighlight lang="java">
Uczen uczen = uczniowie.get(i);

// linia z imieniem
linie.add(uczen.pobierzImie());

// linia z nazwiskiem
linie.add(uczen.pobierzNazwisko());
</syntaxhighlight>
Teraz skompiluj ponownie aplikację. Wyeksportuj ponownie dane, w pliku powinny pojawić się teraz imiona i nazwiska uczniów.
[[File:jmk-dziennik_image39.png| center]]
Teraz pozostało nam już tylko dodanie wyników z poszczególnych przedmiotów.<br />
W metodzie '''pobierzDane''' w środku pętli uczniów, pod dodaniem nazwiska ucznia do listy, dodaj nową pętlą chodzącą po przedmiotach.
<syntaxhighlight lang="java">
// linia z nazwiskiem
linie.add(uczen.pobierzNazwisko());

// dla kazdego przedmiotu dodaje wpis ucznia
for (int j = 0; j < przedmioty.size(); j++) {

String przedmiot = przedmioty.get(j);

}
</syntaxhighlight>
Następnie zapisz w kolejnej linii nazwę przedmiotu.
<syntaxhighlight lang="java">
String przedmiot = przedmioty.get(j);

//nazwa przedmiotu
linie.add(przedmiot);
</syntaxhighlight>
Teraz dodajmy metodę pomocniczą, która zapisze listę napisów po przecinku. Dzięki temu nie będziemy musieli przy zapisie uwag i ocen powtarzać tych samych czynności. Stwórz metodę '''listaDoLinii'''.
<syntaxhighlight lang="java">
private String listaDoLinii(ArrayList<String> lista) {
//inicujuje pusta linie
String linia = "";

//zapis plikow po przecinku w pierwszej linii
for (int i = 0; i < lista.size(); i++) {
String wartosc = lista.get(i);

if (i > 0) {
linia = linia + ",";
}
linia = linia + wartosc;

}
return linia;
}
</syntaxhighlight>
Metoda działa podobnie jak pętla do zapisu przedmiotów. W parametrze przyjmuje listę napisów. Następnie tworzy pustą linię, do której w pętli dodaje każdy element z listy. Pomiędzy elementami wstawia przecinek. Na końcu zwraca stworzoną linię.<br />
Wykorzystajmy to teraz przy zapisie wyników ucznia. Poniżej linii dodawania przedmiotu dodaj pobieranie wyników z przedmiotu
<syntaxhighlight lang="java">
linie.add(przedmiot);

// pobieram przedmiot
WynikiUcznia wynikiUcznia = uczen.pobierzWyniki(przedmiot);

if (wynikiUcznia == null) {
//jesli uczen nie ma wynikow z danego przedmiotu to zapisuje 2 puste linie
linie.add("");
linie.add("");
} else {
//zapisuje linie ocen
String liniaOcen = listaDoLinii(wynikiUcznia.pobierzOceny());
linie.add(liniaOcen);

//zapisuje linie uwag
String liniaUwag = listaDoLinii(wynikiUcznia.pobierzUwagi());
linie.add(liniaUwag);
}
</syntaxhighlight>
Najpierw pobieramy wyniki.<br />
Następnie sprawdzamy, czy uczeń posiada jakieś oceny z danego przedmiotu<br />
if (wynikiUcznia == null) {<br />
Jeśli nie ma żadnych ocen, dodajemy po prostu dwie puste linie.<br />
Jeśli jednak posiada oceny } else {<br />
to tworzymy wiersze z linią ocen i uwag.<br />
Najpierw za pomocą nowej metody listę zamieniamy na napis listaDoLinii(wynikiUcznia.pobierzOceny())<br />
Następnie linię dodajemy do listy<br />
linie.add(liniaOcen);<br />
To już jest wszystko. Zapisaliśmy wszystkie dane, które są nam potrzebne aby odtworzyć dziennik ucznia.<br />
Skompiluj program, dodaj kilka uwag i kilka ocen. Wyeksportuj plik i zobacz czy wszystko działa poprawnie. Plik powinien wyglądać mniej więcej tak, jak poniżej.
[[File:jmk-dziennik_image60.png| center]]
Świetnie mamy już eksport. Teraz musimy dodać funkcjonalność odczytu pliku.<br />
Zacznijmy od stworzenia klasy '''Importer'''. Podobnie jak '''Eksporter''' będzie przechowywać listę uczniów i przedmiotów. Nie zapomnij o importach.
<syntaxhighlight lang="java">
import java.io.*;
import java.util.*;
import org.apache.commons.io.*;
public class Importer {

private ArrayList<Uczen> uczniowie;

private ArrayList<String> przedmioty;

}
</syntaxhighlight>
Dodajmy teraz metodę '''importuj''', która z pliku odczyta linie i stworzy odpowiednie obiekty. Parametrem metody będzie nazwa pliku. Zacznijmy od wczytania linii z pliku. Robimy to podobnie do metody zapisującej, z tą różnicą, że nie podajemy linii, które chcemy zapisać, a odczytujemy je i przypisujemy do zmiennej lokalnej.
<syntaxhighlight lang="java">
private void importuj(String nazwaPlik) {

//odczyt pliku do linii
try {
List<String> linie = FileUtils.readLines(new File(nazwaPlik));


} catch (IOException e) {
System.out.println("Nie udalo sie odczytac pliku " + nazwaPlik);
e.printStackTrace();
}

}
</syntaxhighlight>
Podobnie jak przy eksporcie, operując na plikach, musimy łapać wyjątek IOException. Odczyt zwraca obiekt '''List''', a nie '''ArrayList''', ale praca z nim jest dokładnie taka sama.<br />
Następnym krokiem będzie odczytanie listy przedmiotów. Zróbmy w pierwszej kolejności metodę pomocniczą, która linię podzieli po przecinkach (rozdzielają one kolejne wpisy). Dodajmy metodę '''liniaDoListy'''.
<syntaxhighlight lang="java">
private ArrayList<String> liniaDoListy(String linia) {

//przepisanie tablicy na liste
ArrayList<String> lista = new ArrayList<String>();

//jesli linia jest pusta to nie dodajemy
if (linia.length() > 0) {

//rozdzielenie listy po przecinku
String[] wartosci = linia.split(",");

for (String wartosc : wartosci) {
lista.add(wartosc);
}
}

return lista;
}
</syntaxhighlight>
Na początku tworzona jest pusta lista. Następnie sprawdzamy czy linia jest nie jest pusta. Jeśli są w niej wartości to rozdzielamy je po przecinku linia.split(","). W efekcie otrzymujemy tablicę wartości. Kolejny krok to już tylko przejście po tablicy i dodanie kolejnych elementów do listy. Na końcu zwracamy listę.<br />
Tę metodę wykorzystamy przy odczytywaniu wartości. Zacznijmy od pobierania przedmiotów.
<syntaxhighlight lang="java">
List<String> linie = FileUtils.readLines(new File(nazwaPlik));

// pierwsza linia to przedmioty
String liniaPrzedmiotow = linie.get(0);
przedmioty = liniaDoListy(liniaPrzedmiotow);
</syntaxhighlight>
Wiemy, że w pierwszej linni znajdują się przedmioty, dlatego możemy ją pobrać po indeksie 0. Następnie wywołujemy metodę, która rozdzieli napis po przecinkach i zwróci listę. Od razu przypisujemy tę listę do pola przedmioty.<br />
Następnie musimy wczytać uczniów. Zacznijmy od zainicjalizowania listy uczniów. Następnie w pętli czytajmy kolejne liniie.
<syntaxhighlight lang="java">
//inicujuje liste uczniow
uczniowie = new ArrayList<Uczen>();

// startuje od drugiej linii, czyli indeksu 1 wczytujac kolejno: imie, nazwisko, przedmiot1, oceny, uwagi (powtarzam dla kazdego przedmiotu), nastepnie kolejny uczen
// uwaga! ostatnia linia jest pusta, wiec musze wczytac o 1 linie mniej, stad line.size() -1
for (int i = 1; i < linie.size() - 1; ) {

}
</syntaxhighlight>
Zauważ, że w trzecia część pętli for jest pusta. Wartość i będziemy dla wygody zmieniać samemu.<br />
Odczytajmy teraz imię i nazwisko, stwórzmy nowego ucznia i dodajmy go do listy.
<syntaxhighlight lang="java">
//najpierw jest imie
String imie = linie.get(i);
String nazwisko = linie.get(i + 1);

// tworze ucznia i dodaje do listy
Uczen uczen = new Uczen(imie, nazwisko);
uczniowie.add(uczen);

// zwiekszam licznik o 2 odczytane linie
i = i + 2;
</syntaxhighlight>
Wczytujemy kolejne dwie linie '''i''' oraz '''i+1'''. Tworzymy nowy obiekt '''Ucznia''' z tymi wartościami i dodajemy go do listy. Na końcu zwiększamy '''i''' o '''2''', bo odczytaliśmy 2 linie.

Teraz zmieńmy trochę klasę '''Uczen'''. W tej chwili metoda '''dodajWynik''' nie zwraca wartości. Zmieńmy ją, aby zwracała nam tworzone wyniki. Przejdź do klasy uczeń i zmień metodę '''dodajWynik''', aby wyglądała jak poniżej.
<syntaxhighlight lang="java">
public WynikiUcznia dodajWyniki(String przedmiot) {
//dodaje nowy przedmiot na mape, tworzy nowy obiekt, aby moc korzystac z przedmiotu
WynikiUcznia wynikiUcznia = new WynikiUcznia();

przedmioty.put(przedmiot, wynikiUcznia);

return wynikiUcznia;
}
</syntaxhighlight>
Przejdź teraz do klasy '''WynikiUcznia''' i dodaj tam dwie metody '''dodajUwagi''' i '''dodajOceny''', które pozwolą na dodanie całej listy.
<syntaxhighlight lang="java">
public void dodajUwagi(ArrayList<String> uwagi) {
this.uwagi.addAll(uwagi);
}

public void dodajOceny(ArrayList<String> oceny) {
this.oceny.addAll(oceny);
}
</syntaxhighlight>
Wróćmy teraz do klasy '''Importer''' i metody '''import'''. Dodajmy pętlę po liście wczytanych przedmiotów, dzięki temu będziemy wiedzieli ile wyników mamy odczytać.
<syntaxhighlight lang="java">
// dla kazdego przedmiotu odczytuje odpowiednio nazwe, oceny i uwagi, tak jak wczesniej zapisalismy
for (int j = 0; j < przedmioty.size(); j++) {
}
</syntaxhighlight>
Zwróć uwagę, że mamy tutaj zmienną '''j'''. Jednak w pętli będziemy korzystać ze zmiennej i.
Wczytajmy najpierw nazwę przedmiotu i dodajmy wyniki. Przypiszmy zwróconą wartość do zmiennej '''wynikiUcznia'''.
<syntaxhighlight lang="java">
for (int j = 0; j < przedmioty.size(); j++) {

String nazwa = linie.get(i);
//dodaje przedmiot to ucznia i podstawam zmienna pod wyniki
WynikiUcznia wynikiUcznia = uczen.dodajWyniki(nazwa);
</syntaxhighlight>
Teraz odczytaj kolejną linię - jest to linia ocen. Przekształć ją na listę i dodaj oceny do wyników.
<syntaxhighlight lang="java">
String liniaOcen = linie.get(i + 1);
ArrayList<String> oceny = liniaDoListy(liniaOcen);
//dodaje liste ocen
wynikiUcznia.dodajOceny(oceny)
</syntaxhighlight>
Następnie zrób to samo z uwagami.
<syntaxhighlight lang="java">
String liniaUwag = linie.get(i + 2);
ArrayList<String> uwagi = liniaDoListy(liniaUwag);
wynikiUcznia.dodajUwagi(uwagi);
</syntaxhighlight>
Na końcu program zwiększy '''i''' o '''3''' odczytane linie.
<syntaxhighlight lang="java">
i = i + 3;
</syntaxhighlight>
Mamy już metodę, która odczyta dane. Teraz wywołajmy ją w konstruktorze, aby po stworzeniu obiektu plik został odczytany i pola były zainicjowane. Dodaj konstruktor z parametrem - '''nazwaPliku''' i wywołaniem metody.
<syntaxhighlight lang="java">
public Importer(String nazwaPlik) {
//laduje dane
importuj(nazwaPlik);
}
</syntaxhighlight>
Zostało już nam tylko dodać dwie metody zwracające wczytane przedmioty i uczniów. Pamiętaj, że muszą być publiczne.
<syntaxhighlight lang="java">
public ArrayList<Uczen> pobierzUczniow() {
return uczniowie;
}

public ArrayList<String> pobierzPrzedmioty() {
return przedmioty;
}
</syntaxhighlight>
Świetnie, zostało już nam tylko użycie tej klasy w klasie '''DziennikUcznia''', po wywołaniu okna importu i podaniu pliku. Przejdź do klasy '''DziennikUcznia''' i metody '''sluchajNaImportExport'''. Dodaj tam sprawdzanie czy podano nazwę pliku w oknie importu. Jeśli tak, stwórz nowy importer.
<syntaxhighlight lang="java">
String nazwaPlikuImportu = plikImportOkno.getResult();
//jesli wprowadzono nazwe
if (nazwaPlikuImportu != null) {
// importuje
Importer importer = new Importer(nazwaPlikuImportu);

...
}
</syntaxhighlight>
Świetnie, teraz pozostaje nam zaktualizowanie pól dziennika. Najpierw zaktualizujmy przedmioty. Zarówno listę, jak i indeks aktualnie wyświetlanego.
<syntaxhighlight lang="java">
//przypisuje wartosci przedmiotu
przedmioty = importer.pobierzPrzedmioty();
// resetuje aktualnie widoczne zmienne
aktualnyPrzedmiot = 0;
</syntaxhighlight>
Następnie, pobierzmy uczniów.
<syntaxhighlight lang="java">
//aktualizuje uczniow
uczniowie = importer.pobierzUczniow();
//resetuje wartosci, aby prawidlowo wyswietlaly sie na ekranie
indeksAktualnegoUcznia = 0;
aktualnyUczen = uczniowie.get(0);
String nazwaAktualnegoPrzedmiotu = przedmioty.get(aktualnyPrzedmiot);
aktualneWyniki = aktualnyUczen.pobierzWyniki(nazwaAktualnegoPrzedmiotu);
</syntaxhighlight>
Aktualizujemy tutaj listę uczniów, indeks aktualnego ucznia, a także aktualnego ucznia i aktualne wyniki.<br />
Na końcu wywołajmy odświeżenie ekranu.
<syntaxhighlight lang="java">
//na koniec odswiezam ekran
odswiezEkran();
</syntaxhighlight>
Skompiluj projekt i uruchom aplikację. Dodaj oceny, uwagi, przedmioty, uczniów. Na koniec wyeksportuj plik.<br />
Następnie zamknij aplikację i uruchom ponownie. Jak zwykle wszystkie dane są puste. Wywołaj import, podając tę samą nazwę pliku, do którego eksportowałeś i kliknij '''Enter'''.
[[File:jmk-dziennik_image19.png| center]]
W tym momencie aplikacja powinna się odświeżyć i powinieneś zobaczyć dane, które zapisałeś.

== Szyfrowanie pliku ==
W poprzednim rozdziale zapisywaliśmy dane do pliku. Wadą rozwiązania, jest to, że każdy może otworzyć plik i zobaczyć jego zawartość.<br />
Dodajmy opcję szyfrowania pliku. W Greenfoot dodaliśmy klasę pl.org.coderdojo.ceasar.ROT13Encoder
która dostarcza nam opcję szyfrowania według prostego algorytmu.<br />
Klasa ROT13Encoder jest realizacją tak zwanego Szyfru Cezara - całość opiera się zamianie każdej litery w szyfrowanym słowie inną, oddaloną o stałą liczbę pozycji. W tym wypadku jest to 13 pozycji, przez co litera A zamienia się w N, litera B w O itd. Dodatkowo, implementacja tej klasy nie ogranicza się do liter - szyfrowane są też znaki przestankowe oraz cyfry.<br />
Klasa ROT13Encoder dziedziczy z klasy AbstractCeasarEncoder. Klasa AbstractCeasarEncoder jest dość specjalna - kiedy ją otworzysz, zauważysz pewnie, że niektóre metody… istnieją tylko jako nagłówki! O co chodzi? Zwróć uwagę na słowo, które stoi przed nazwą klasy:
<syntaxhighlight lang="java">
public abstract class AbstractCeasarEncoder {
</syntaxhighlight>
Jest to tak zwana klasa abstrakcyjna. Składa się ona zarówno z “pełnych” metod, które zostaną odziedziczone i można z nich bez problemów korzystać w klasach potomnych, jak również i metody abstrakcyjne. Takie metody posiadają również słowo '''abstract''' w deklaracji, jak w naszym wypadku dla klasy AbstractCeasarEncoder:
<syntaxhighlight lang="java">
public abstract char encode(char toEncode);
public abstract String encode(String toEncode);
public abstract char decode(char toDecode);
public abstract String decode(String toDecode);
</syntaxhighlight>
Słowo '''abstract''' przy nazwie klasy oznacza, że gdyby klasa mogła mówić, to powiedziałaby nam coś takiego: “Hej! Jestem klasą abstrakcyjną! Oprócz metod, z których możesz korzystać i które mogą odziedziczyć inne klasy, mam też metody abstrakcyjne. Nie wiem, jak działają, bo mnie to nie obchodzi. Wiem natomiast, że klasy dziedziczące po mnie muszą je posiadać i muszą to być metody pełne, już bez słowa abstract!”. Natomiast każda metoda, przy której znajduje się słowo '''abstract''' mówi nam coś takiego: “Jestem metodą abstrakcyjną! Nie wiem, jaki kod mam miedzy klamrami, bo mnie to nie obchodzi. Wiem natomiast, jak się nazywam, jakiego typu dane zwracam oraz jakie dane przyjmuję na wejście. Metoda, która będzie mnie tworzyła w klasie potomnej MUSI już mieć kod i nie może mieć słowa abstract”. Może trochę pyszałkowate (bo są ale nie wiedzą nic), ale tego typu klasy i metody są programistom potrzebne.<br />
Skoro już znamy zasadę działania klas abstrakcyjnych, przyjrzyjmy się zatem polom i pełnym metodom w klasie AbstractCeasarEncoder. Na początek pola i ich opisy:
{| class="wikitable" border="1"
|-
! typ zmiennej
! nazwa
! rola
|-
| private int
| shiftBy
| Mówi, o ile miejsc należy przesunać się aby zaszyfrować lub odszyfrować znak.
|-
| protected int
| asciiMaxValue
| Określa rozmiar tablicy, w której będą przechowywane znaki wykorzystywane do szyfrowania iodszyfrowania.
|-
| protected static char[]
| asciiTable
| Tabela, w której przechowywane będą znaki - domyślnie nie zainicjalizowana.
|}
Konstruktor klasy jest jeden i bezparametrowy:
<syntaxhighlight lang="java">
public AbstractCeasarEncoder() {
if (asciiTable == null) {
initializeASCIITable();
}
}
</syntaxhighlight>
Jak widać, jeśli tabela nie jest utworzona, wywołana zostanie metoda initializeASCIITable(), która wygląda w ten sposób:
<syntaxhighlight lang="java">
protected void initializeASCIITable () {
asciiTable = new char[asciiMaxValue];
for (int index = 0; index < asciiMaxValue; index++) {
asciiTable[index] = (char)index;
}
}
</syntaxhighlight>
Rola tej metody jest również prosta - na początku zostanie utworzona tabela znaków, o rozmiarze zadeklarowanym w zmiennej asciiMaxValue. Następnie, w pętli for, która iteruje zmienną index od zera do momentu osiągnięcia równości ze zmienną asciiMaxValue, zapisany zostanie w komórce tabeli w pozycji oznaczonej numerem index znak, który jest określony za pomocą aktualnej wartości zmiennej index w pętli.<br />
Następnie metody getShiftBy() i setShiftBy()
<syntaxhighlight lang="java">
public int getShiftBy() {
return shiftBy;
}

public void setShiftBy(int shiftBy) {
this.shiftBy = shiftBy;
}
</syntaxhighlight>
które służą odpowiednio do pobierania i ustawiania wartości zmiennej shiftBy. Ostatnią z metod jest getCharAtPosition(), której zadaniem jest pobranie znaku z określonego miejsca w tablicy.
<syntaxhighlight lang="java">
protected char getCharAtPosition(int index) {
return asciiTable[index];
}
</syntaxhighlight>
Pozostałe metody - decode() oraz encode() są abstrakcyjnymi metodami i zostaną zaimplementowane w klasie-potomku.<br />
Kolej omówić klasę ROT13Encoder. Jak wielokrotnie było wspominane, dziedziczy ona z klasy AbstractCeasarEncoder. Posiada zatem zaimplementowane metody decode() oraz encode().
<syntaxhighlight lang="java">
@Override
public char encode(char toEncode) {
int charIndex = (int)toEncode;
int charIndexAfterEncoding = charIndex + getShiftBy();
if (charIndexAfterEncoding >= asciiMaxValue) {
charIndexAfterEncoding = charIndexAfterEncoding % asciiMaxValue;
}
return getCharAtPosition(charIndexAfterEncoding);
}

@Override
public String encode(String toEncode) {
char[] chars = toEncode.toCharArray();
StringBuilder stringBuild = new StringBuilder();
for (int i = 0 ; i < chars.length; i++) {
char encoded = encode(chars[i]);
stringBuild.append(encoded);
}
return stringBuild.toString();
}


@Override
public char decode(char toDecode) {
int charIndex = (int)toDecode;
int charIndexAfterEncoding = charIndex - getShiftBy();
if (charIndexAfterEncoding < 0) {
charIndexAfterEncoding = asciiMaxValue + charIndexAfterEncoding;
}
return getCharAtPosition(charIndexAfterEncoding);
}

@Override
public String decode(String toDecode) {
char[] chars = toDecode.toCharArray();
StringBuilder stringBuild = new StringBuilder();
for (int i = 0 ; i < chars.length; i++) {
char decoded = decode(chars[i]);
stringBuild.append(decoded);
}
return stringBuild.toString();
}
</syntaxhighlight>
Od razu widać jedną rzecz - metody decode() oraz encode() dostępne są zarówno dla obiektów typu String oraz pojedynczych liter. W wypadku metody encode dla ciągu znaków sytuacja jest prosta - na wstępie ciąg znaków, który ma zostać zaszyfrowany, jest rozbijany za pomocą metody toCharArray() na tablicę znaków. Potem inicjalizowany jest obiekt klasy StringBuilder - służy on do połączenia liter w jeden ciąg znakowy. Następnie w pętli, która iteruje po wszystkich kolejnych znakach w tablicy wywoływana jest metoda encode() dla znaku. Na koniec każdej iteracji do stringbuildera trafia zaszyfrowany znak. Po wykonaniu pętli, dzięki metodzie toString() stringbuildera, zwracany jest zaszyfrowany ciąg znaków. W wypadku metody encode() dla pojedynczego znaku wykonywany jest algorytm szyfrujący. Na początku znak zamieniany jest w liczbę, reprezentującą go w tabeli. Następnie do tej wartości dodawana jest liczba będąca reprezentacją, o ile należy przesunąć się w tabeli - getShiftBy(). Indeks znaku, po zaszyfrowaniu, w tablicy przechowywany jest w zmiennej charIndexAfterEncoding. Następnie sprawdzane jest czy wartość indeksu charIndexAfterEncoding nie przekracza pojemności tabeli. Jeśli tak jest (popatrzmy na zawartość instrukcji if), to w takim wypadku pobierana jest reszta z dzielenia zmiennej charIndexAfterEncoding przez zmienną reprezentującą maksymalną wielkość tabeli znaków. Dzięki temu osiągamy cykliczność - ostatnie znaki z tabeli zostaną zastąpione pierwszymi w kolejności z tabeli znaków. Po wszystkim za pomocą metody getCharAtPosition() zwracany jest znak-szyfr spod wyznaczonej pozycji.<br />
Podobnie jest w wypadku metod decode(). Ciąg znaków metoda decode() rozbija za pomocą metody toCharArray() na tablicę znaków. Potem inicjalizowany jest obiekt klasy StringBuilder - służy on do połączenia liter w jeden ciąg znakowy. Następnie w pętli, która iteruje po wszystkich kolejnych znakach w tablicy wywoływana jest metoda decode() dla znaku. Na koniec każdej iteracji do stringbuildera trafia odszyfrowany znak. W wypadku metody decode() dla pojedynczego znaku na początku znak jest konwertowany na liczbę reprezentującą jego pozycję w tabeli. następnie odejmowana od tego jest wartość zmiennej przesunięcia pobranej za pomocą metody getShiftBy(). Jeśli wynik odejmowania wyniesie mniej niż zero - to ta ujemna liczba jest dodawana do wielkości tablicy - dzięki temu znowu mamy cykliczność. Po wszystkim za pomocą metody getCharAtPosition() zwracany jest znak spod wyznaczonej pozycji.<br />
Ostatnią metodą obecną w tej klasie jest konstruktor. Służy on do wywołania konstruktora klasy-rodzica (za pomocą metody super()) oraz ustawienia zmiennej shiftBy za pomocą metody setShiftBy() na wartość 13 (stąd inna nazwa szyfru Cezara: ROT-13).
<syntaxhighlight lang="java">
public ROT13Encoder() {
super();
setShiftBy(13);
}
</syntaxhighlight>
Wiemy już jak działa samo szyfrowanie, dlatego teraz dodajmy opcję eksportu i importu z szyfrowaniem do naszej aplikacji. Zacznijmy od zmiany klasy <br />
Eksporter.<br />
Dodajmy do importów wpis:
<syntaxhighlight lang="java">
import pl.org.coderdojo.ceasar.ROT13Encoder;
import org.apache.commons.io.*;
</syntaxhighlight>
Zmieńmy metodę '''eksportuj''', aby przyjmowała parametr typu '''boolean''', czyliWartość logiczną '''true'''/'''false''' - czy szyfrować dane czy nie.
<syntaxhighlight lang="java">
public void eksportuj(String nazwaPliku, boolean szyfruj) {
</syntaxhighlight>
Dodajmy jeszcze metodę, która będzie szyfrować dane. Niech metoda przyjmuje listę napisów i zwraca również listę, ale już zaszyfrowanych napisów. Zainicjujmy drugą listę, do której dodamy zaszyfrowane wiersze.
<syntaxhighlight lang="java">
private ArrayList<String> szyfruj(ArrayList<String> lista) {
ArrayList<String> szyfrowaneDane = new ArrayList<String>();


return szyfrowaneDane;
}
</syntaxhighlight>
Samo szyfrowanie będzie polegało da wywołaniu metody '''encode''', na każdym z wierszy i wstawieniu tego wiersza do nowej listy.
<syntaxhighlight lang="java">
ROT13Encoder szyfrant = new ROT13Encoder();
for (int i = 0; i < lista.size(); i++) {
String linia = lista.get(i);

String zaszyfrowanaLinia = szyfrant.encode(linia);
szyfrowaneDane.add(zaszyfrowanaLinia);

}

return szyfrowaneDane;
</syntaxhighlight>
Na początku tworzymy obiekt szyfranta, następnie idziemy wiersz po wierszu i każdy wiersz szyfrujemy, dodając go jednocześnie na zwracaną listę.<br />
Teraz pozostało nam wywołać tę metodę, jeśli dane mają być szyfrowane w metodzie '''eksportuj'''.
<syntaxhighlight lang="java">
public void eksportuj(String nazwaPliku, boolean szyfruj) {

//pobranie listy wierszy do zapisania
ArrayList<String> zawartosc = pobierzDane();

if (szyfruj) {
zawartosc = szyfruj(zawartosc);
}
</syntaxhighlight>
Sprawdzamy tutaj flagę '''szyfruj''' i jeśli jest aktywna (wartość '''true'''), po prostu wwywołujemy metodę szyfruj, która zmieni dane, które zostaną zapisane.

Teraz jeszcze w klasie '''DziennikUcznia''' zmieńmy metodę eksportującą - wywołajmy ją z flagą '''true'''.
<syntaxhighlight lang="java">
private void sluchajNaImportExport() {
...

Eksporter exporter = new Eksporter(uczniowie, przedmioty);
exporter.eksportuj(nazwaPlikuExportu, true);
</syntaxhighlight>
Skompiluj projekt i wyeksportuj dane. Otwórz plik. Czy potrafisz powiedzieć co zostało tam zapisane?
[[File:jmk-dziennik_image35.png| center]]
Zaszyfrowany plik. Nie znając szyfru trudno go odczytać Człowiekowi bez komputera zajęłoby to też dużo czasu.<br />
Spróbujmy to zrobić w importerze, aby można było ponownie pobrać wyeksportowane dane. W klasie '''Importer''' dodajmy na górze import do naszej klasy szyfrującej.
<syntaxhighlight lang="java">
import pl.org.coderdojo.ceasar.ROT13Encoder;
</syntaxhighlight>
Następnie w konstruktorze dodajmy informację czy dane są szyfrowane. Zmieńmy też wywołanie metody '''importuj'''.
<syntaxhighlight lang="java">
public Importer(String nazwaPlik, boolean szyfrowane) {
//laduje dane
importuj(nazwaPlik, szyfrowane);
}
</syntaxhighlight>
Zmieńmy teraz metodę '''importuj'''. Niech w parametrze przyjmuje '''boolean''' z informacją czy dane są szyfrowane.
<syntaxhighlight lang="java">
private void importuj(String nazwaPlik, boolean szyfrowane) {
</syntaxhighlight>
Dodajmy teraz metodę '''odszyfruj'''.
<syntaxhighlight lang="java">
private ArrayList<String> odszyfruj(List<String> linie) {

ArrayList<String> odszyfrowane = new ArrayList<String>();
ROT13Encoder szyfrant = new ROT13Encoder();

for (String linia : linie) {
odszyfrowane.add(szyfrant.decode(linia));
}

return odszyfrowane;
}
</syntaxhighlight>
Metoda przyjmuje listę linii i zwraca odszyfrowane dane. Tworzymy w niej nowy obiekt szyfranta i każdą kolejną linijkę odszyfrowujemy za pomocą metody '''decode'''.
<syntaxhighlight lang="java">
szyfrant.decode(linia)
</syntaxhighlight>
Dodajmy teraz tylko wywołanie metody odszyfrywującej dane w metodzie '''importuj'''.
<syntaxhighlight lang="java">
private void importuj(String nazwaPlik, boolean szyfrowane) {

//odczyt pliku do linii
try {
List<String> linie = FileUtils.readLines(new File(nazwaPlik));

if (szyfrowane) {
linie = odszyfruj(linie);
}
</syntaxhighlight>
Teraz zostaje nam tylko poprawić kod w klasie '''DziennikUcznia''' w metodzie '''sluchajNaImportExport'''. Zmieńmy w niej sposób tworzenia importera, dodając flagę '''true''', oznaczającą, że szyfrujemy dane.
<syntaxhighlight lang="java">
if (nazwaPlikuImportu != null) {
// importuje
Importer importer = new Importer(nazwaPlikuImportu, true);
//przypisuje wartosci przedmiotu
przedmioty = importer.pobierzPrzedmioty();
</syntaxhighlight>
To już wszystko. Skompiluj projekt i zaimportuj zaszyfrowany plik. Zobacz czy wszystko działa jak wcześniej.

== Wysyłanie maila z raportem ==
W tym rozdziale wyślemy mail z aplikacji. Będzie nam do tego potrzebne konto mailowe. Założymy je razem na gmail.com. Najpierw jednak przygotujmy naszą aplikację, aby pozwalała na podanie maila. Będziemy wysyłać ten sam raport, który zapisujemy w pliku.<br />
W wypadku języka Java, jego twórcy zadbali także o funkcjonalność wysyłania i odbierania emaili. W tym celu stworzona została biblioteka Java Mail. Została ona wykorzystana w klasie MailSender. Przyjrzyjmy się zatem, jakie pola i metody posiada ta klasa.<br />
Poniżej znajduje się tabela z opisem poszczególnych pól klasy MailSender.
{| class="wikitable" border="1"
|-
! typ zmiennej
! nazwa
! rola
|-
| private Properties
| mailProperties
| Obiekt przechowujący podstawowe parametry służące do łączenia się z serwerem pocztowym.
|-
| private String
| subject
| Określa temat emaila.
|-
| private String
| content
| Określa zawartość emaila
|-
| private String
| fromMail
| Określa adres, z którego zostanie wysłany email.
|-
| private ArrayList<String>
| toMails
| Lista przechowująca adresy email, pod które ma zostać wysłany mail.
|-
| private File
| attachment
| Określa plik, który ma zostać wysłany jako załącznik do emaila.
|-
| private Multipart
| mainMessageBody
| Przechowywane jest w niej ciało (czyli treść i załącznik) maila
|-
| private MimeMessage
| mainMessage
| Reprezentuje wiadomość email.
|}
Klasa ta posiada też cztery konstruktory - każdy z nich rozszerza zakres inicjalizowanych zmiennych wewnętrznych:
<syntaxhighlight lang="java">
public MailSender(Properties props) {
this.setMailProperties(props);
}






public MailSender(Properties props, String subject, String content, String fromMail) {
this.setMailProperties(props);
this.setSubject(subject);
this.setContent(content);
this.setFromMail(fromMail);
}

public MailSender(Properties props, String subject, String content, String fromMail, File attachment) {
this.setMailProperties(props);
this.setSubject(subject);
this.setContent(content);
this.setFromMail(fromMail);
this.setAttachment(attachment);
}

public MailSender(Properties props, String subject, String content, String fromMail, File attachment, ArrayList<String> toMailList) {
this.setMailProperties(props);
this.setSubject(subject);
this.setContent(content);
this.setFromMail(fromMail);
this.setAttachment(attachment);
this.setToMails(toMailList);
}
</syntaxhighlight>
Pierwszy z nich tylko ustawia obiekt opisujący parametry niezbędne do nawiązania połączenia z serwerem pocztowym. Drugi dodaje do tego tytuł i zawartość wiadomości oraz adres email nadawcy. Trzeci konstruktor ustawia także plik załącznika. Ostatni zaś - potrafi również ustawić listę adresów email odbiorców.<br />
Dodatkowo klasa MailSender potrafi również stworzyć (a w zasadzie dodać do już istniejącego obiektu - maszyna wirtualna Javy, która wykonuje programy posiada już utworzony taki obiekt) obiekt klasy Properties. Trzymane w nim będą ustawienia niezbędne do ustanowienia połączenia naszego programu z serwerem do wysyłania poczty. Dostępne są dwie metody getPropertiesForMail() - metoda z dwoma parametrami wejściowymi służy do konfiguracji ustawień pod serwery Google gMail. Druga metoda, z większą ilością parametrów, przeznaczona jest dla innych serwerów poczty wychodzącej.
<syntaxhighlight lang="java">
public static Properties getPropertiesForMail (String login, String pass) {
Properties props = getPropertiesForMail(login, pass, "smtp.gmail.com", "465", "true");
props.put("mail.smtp.auth", true);
props.put("mail.smtp.password", pass);
props.put("mail.smtp.starttls.enable", "true");
return props;
}

public static Properties getPropertiesForMail (String login, String pass, String host, String port, String enableSSL) {
Properties props = System.getProperties();
props.put("mail.smtp.host", host); props.put("mail.smtp.user", login); props.put("mail.smtp.port", port); props.put("mail.smtp.ssl.enable", enableSSL); props.put("mail.password", pass);
return props;
}
</syntaxhighlight>
Jak widać, wersja metody getPropertiesForMail() z większą ilością parametrów najpierw za pomocą metody System.getProperties() pobiera obiekt typu Properties. Następnie za pomocą metody put() dodawane są wpisy do obiektu typu Properties. Są to kolejno:
* mail.smtp.host - adres serweru email, który będzie odpowiedzialny za wysyłanie naszego emaila,
* mail.smtp.user - nazwa użytkownika, za pomocą której serwer email sprawdzi, czy możemy wysłać z niego email,
* mail.smtp.port - tak zwany port, po którym zostanie przesłany nasz mail do serwera,
* mail.smtp.ssl.enable - informuje o tym, czy chcemy włączyć szyfrowanie naszego połączenia z serwerem email,
* mail.password - hasło użytkownika, za pomocą którego serwer email sprawdzi, czy możemy wysłać z niego email,

Natomiast metoda getPropertiesForMail() z dwoma parametrami czyli loginem i hasłem użytkownika jest wykorzystywana tylko w wypadku wysyłania emaili przez serwery Google gMail. Jak widać, na początku wywołuje ona wersję tej metody z większą ilością parametrów, przy czym kilka wartości jest zapisanych na stałe - są to adres serweru email, port oraz włączenie szyfrowania połączenia. Dodatkowo, ze względu na wymagania Google’a, należy ustawić też kilka innych parametrów w obiekcie typu Properties:
* mail.smtp.auth - zezwolenie na tak zwaną autoryzację użytkownika (oznacza to, że serwer email sprawdzi, czy użytkownik ma prawo do wysłania maila za pomocą naszej aplikacji),
* mail.smtp.password - hasło użytkownika, za pomocą której serwer email sprawdzi, czy możemy wysłać z niego email,
* mail.smtp.starttls.enable - dodatkowe szyfrowanie przed wysłaniem danych do logowania.

Oprócz metod związanych z ustawianiem bądź pobieraniem wartości zmiennych, klasa MailSender zawiera też metodę sendMail(). Służy ona do stworzenia i wysłania wiadomości email na podstawie danych, które zostały do niej wcześniej podane.
<syntaxhighlight lang="java">
public void sendMail() {
try {
Session sendSession = Session.getInstance(this.getMailProperties(),null);
mainMessage = new MimeMessage(sendSession);
mainMessage.setFrom(getFromMail());
mainMessage.setSubject(getSubject());
for (String mail : getToMails()) {
mainMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(mail));
}
MimeBodyPart contentPart = new MimeBodyPart();
contentPart.setText(this.getContent());
mainMessageBody = new MimeMultipart();
mainMessageBody.addBodyPart(contentPart);
if (this.getAttachment() != null) {
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.attachFile(getAttachment());
mainMessageBody.addBodyPart(attachmentPart);
}
mainMessage.setContent(mainMessageBody);
mainMessage.setSentDate(Calendar.getInstance().getTime());
Transport.send(mainMessage, this.getMailProperties().getProperty("mail.smtp.user"), this.getMailProperties().getProperty("mail.password"));
} catch (Exception ex) {
ex.printStackTrace();
}
}
</syntaxhighlight>
Na samym początku metoda sendMail() tworzy obiekt klasy Session - służył on będzie do nawiązania połączenia z serwerem pocztowym Google - zwrócić należy uwagę, iż metoda Session.getInstance() przyjmuje obiekt klasy Properties. Następnie tworzony jest obiekt naszego emaila (można to potraktować jako “kopertę”, do której włożymy nasz list, zaadresujemy go i na końcu wyślemy) za pomocą konstruktora new MimeMessage(), który przyjmuje wcześniej utworzony obiekt Session. Dalej ustawiane są: adres email nadawczy, temat emaila oraz - za pomocą pętli for, która iteruje po liście adresów email - adresy osób, do których mail ma zostać wysłany. Dalej tworzymy obiekt będący zawartością naszego emaila (czyli treść). Dzieje się to za sprawą wywołania konstruktora klasy MimeBodyPart. Jak sama nazwa klasy wskazuje - jest to część emaila. Może zdarzyć się tak, że będzie trzeba razem z treścią wysłać do kogoś jakiś plik, który przecież też jest częścią tego maila. Dlatego, aby połączyć te części, wykorzystana została klasa MimeMultipart. Służy ona do scalania tekstu wiadomości oraz jednego (bądź też większej ilości załączników) w jeden email (czyli jest jakby “zszywaczem”, za pomocą którego zszywamy wiele stron listu). Za pomocą metody addBodyPart() dodajemy do obiektu MimeMultipart naszą treść emaila. Następnie, za pomocą pętli if sprawdzamy, czy mamy też jakiś załącznik do wysłania (czyli czy metoda getAttachement() nie zwróci nam wartości null) - jeśli mamy plik, to postępujemy podobnie jak w wypadku tekstu emaila. Kiedy mamy już wszystko “zszyte”, wkładamy to do naszej “koperty” (czyli obiektu MimeMessage) i za poomocą metody send() z klasy Transport wysyłamy go. I gotowe!<br />
Wiedząc już, jakie są podstawowe funkcjonalności klasy do wysyłania maili, zmodyfikujmy klasę '''Eksporter''', gdzie dodamy metodę, która wygeneruje raport z ocenami.
<syntaxhighlight lang="java">
public String generujRaport() {

ArrayList<String> zawartosc = pobierzDane();

StringBuilder sb = new StringBuilder();
for (String linia : zawartosc) {
sb.append(linia).append("\r\n");
}
return sb.toString();
}
</syntaxhighlight>
Metoda ta najpierw pobiera listę z liniami, czyli dane do zapisania.<br />
Następnie tworzy obiekt '''StringBuilder''', który pozwala nam łączyć napisy.<br />
Następnie pętla '''for''' idzie linijka po linijce po liście wierszy.<br />
Linijka sb.append(linia).append("\r\n") dodaje do napisu, który budujemy najpierw linię, a później sekwencję "\r\n", która oznacza znak nowej linii.<br />
W ostatnim kroku metoda zwraca zbudowany napis, przez wywołanie metody '''toString()'''.<br />

Teraz przejdźmy do klasy '''DziennikUcznia'''. Na górze dodajmy kolejny import, który jest nam potrzebny.
<syntaxhighlight lang="java">
import pl.org.coderdojo.mail.Mailer;
</syntaxhighlight>
Dodajmy też nowe okno z pytaniem o adres mail. Nowe okno będzie typu '''EmailWindow'''.
<syntaxhighlight lang="java">
private EmailWindow adresEmailOkno = new EmailWindow("Podaj adres email do wyslania raportu", 50);
</syntaxhighlight>
W metodzie '''inicujMenu''' dodajmy kolejny element do menu '''Operacje''', o nazwie '''Wyslij raport'''.
<syntaxhighlight lang="java">
menuGlowne.addItem("Operacje/Eksportuj");
menuGlowne.addItem("Operacje/Importuj");
menuGlowne.addItem("Operacje/Wyslij raport");
</syntaxhighlight>
Teraz dodajmy kod, który otworzy okno po kliknięciu na raport. W metodzie '''sluchajNaMenu''' dodaj wywołanie okna, tak jak poniżej
<syntaxhighlight lang="java">
} else if (wybraneMenu.equals("Importuj")) {
...
} else if (wybraneMenu.equals("Wyslij raport")) {
adresEmailOkno.toggleShow("twoj.email@gmail.com");
}
</syntaxhighlight>
Zamiast twoj.email@gmail.com możesz wprowadzić swój mail, na którym będziesz testować działanie aplikacji. Na ten email, będzie wysyłana wiadomość.<br />
Dodajmy teraz kod, który po wprowadzeniu adres mail, wyśle raport.<br />
Dodaj nową metodę '''sluchajNaRaport'''.
<syntaxhighlight lang="java">
private void sluchajNaRaport() {

String adresEmail = adresEmailOkno.getResult();
//jesli wprowadzono mail
if (adresEmail != null) {
//pobiera raport
Eksporter exporter = new Eksporter(uczniowie, przedmioty);
String raport = exporter.generujRaport();

Mailer mailer = new Mailer("test@gmail.com", "haslo");

mailer.wyslijEmail(adresEmail, "Raport ocen", raport);
}
}
</syntaxhighlight>
W pierwszych dwóch linijkach pobieramy adres email i sprawdzamy czy został wprowadzony.
W kolejnych dwóch linijkach tworzymy obiekt eksportera i wywołujemy na nim generowanie raportu. Otrzymujemy raport w postaci napisu.<br />
Następny krok to użycie klasy '''Mailer'''. Wywołując ją, podajemy adres email i hasło skrzynki mailowej z której wyślemy wiadomość. '''Uwaga, powyższy kod działa tylko z kontem gmail.
Za chwilę zmodyfikujemy login i hasło, musimy tylko założyć testowe konto.'''<br />
Następny krok to użycie wcześniej skonfigurowanego mailer-a, do wysłania raportu na wskazany adres email.
Nie zapomnij wywołać metody w metodzie '''act'''.
<syntaxhighlight lang="java">
public void act() {
...

sluchajNaRaport();

}
</syntaxhighlight>

=== Zakładanie konta gmail. ===
Wejdź na adres gmail.com
[[File:jmk-dziennik_image32.png| center]]
Wybierz Create account / Utwórz konto
[[File:jmk-dziennik_image27.png| center]]
Wprowadź dane, nie muszą być prawdziwe. Zapamiętaj użytkownika oraz hasło. Wybierz następny krok.
[[File:jmk-dziennik_image11.png| center]]
W następnym kroku kliknij '''Zgadzam się'''
[[File:jmk-dziennik_image10.png| center]]
Konto zostanie założone
[[File:jmk-dziennik_image14.png| center]]
Kliknij '''Przejdź do Gmail'''
Teraz musimy jeszcze pozwolić, aby nasza plikacja mogła uzyć serwera '''smtp'''. Przejdź pod adres [[https://www.google.com/settings/security/lesssecureapps| https://www.google.com/settings/security/lesssecureapps]] i kliknij '''Włącz''', jeśli jest wyłączone.
[[File:jmk-dziennik_image58.png| center]]
Teraz masz skonfigurowane konto google z którego możesz wysłać email. Przejdź do metody '''sluchajNaRaporti''' zmień ustawienia '''Mailer'''-a na własne.
<syntaxhighlight lang="java">
Mailer mailer = new Mailer("test@gmail.com", "haslo");
</syntaxhighlight>
Skompiluj aplikację. Wyślij raport (pamiętaj o '''klikaniu''' w oknie na '''Enter''', wciśnięcie klawisza może powodować błędy).<br />
Powinieneś w swoje skrzynce mailowe zobaczyć wiadomość z raportem.
[[File:jmk-dziennik_image18.png| center]]
W skrzynce wychodzącej konta testowego, także pojawi się ta wiadomość.
Biurokrata, administrator
530
edycji