Gyakorlati alapok IV.
Végre ablakozunk! (AWT, Swing, JavaFX)
Catch me if you can!
Ebben a fejezetben továbbfejlesztjük a Játék: Wollen Sie arbeiten? (Akar Ön dolgozni?) című fejezetben megalkotott kódokat és kissé átalakítva belőle egy másik játékot készítünk.
Sokan láthatták Spielberg mester Catch me if you can! (Kapj el,
ha tudsz!) című mozifilmjét. A játék azonos alapelveket alkalmazva most
az lesz, hogy a főablakból egyetlen gombnyomásra induljon el egy olyan
kisablakfolyam, amelyet ügyesen kattintgatva mindig be kell zárni!
Természetesen a kisablakok mindig máshol, véletlenszerűen jelenjenek
meg.
Alapjában véve a program az említett fejezet kódjaiból könnyen
felépíthető, azonban itt is belefutottam egy előre nem látott problémába
(mert a programozásban legalább ez az egyetlen kiszámíthatatlanság
kiszámítható).
A buttonAction() függvényben lekezelt
WindowInner-objektumok példányosítása és
véletlenszerű megjelenítése egy kvázivégtelen ciklusban nem működött
vizuálisan megfelelően, mert a
JVM
csak akkor rajzolta ki véglegesen az objektumokat, amikor a
buttonAction() függvény teljesen visszaadta
neki a vezérlést. Ez olyan hibajelenségben mutatkozott meg, hogy a
kirajzolás ideje alatt kizárólag üres és fehér kisablakok jelentek meg,
a különböző színek és az egységes felirat nem. A probléma a kisablakok
külön szálakban (A többszálú programozás alapjai (multithread)
című fejezetcsomag) való indításánál sem oldódott meg. Nyilvánvaló volt,
hogy az időzítéssel kell valamit kezdeni, azaz találni kéne egy olyan
rutint, amelyik bár időlegesen visszaadja a vezérlést a JVM-nek, mégis
továbblépteti a kisablakok megjelenítését. Meglepetésszerűen ezt a
rutint a Timer osztályban találhatjuk meg (A Timer
osztály című fejezet).
A megfelelő metódusok megtalálása után a kód már hamar összeállt:
-
WindowInner osztályt szabványosan származtattam a TimerTask osztályból (WindowInner extends TimerTask), és a WindowInner osztály egyszerű példányosítását beletettem a TimerTask osztály run() metódusába:
@Override
public void run() {
new WindowInner();
}
}
-
A Window osztály buttonAction() metódusában egybeszerkesztettem az időzítést, annak paraméterezését, valamint az időzítendő objektumot:
private void buttonAction() {
Timer timer = new Timer();
TimerTask task = new WindowInner();
timer.schedule(task, 0, 1000);
}
}
Mivel a főablak pozícióját nem akartam a véletlenre bízni, a Center osztályt +1 metódussal volt szükséges bővíteni: az egyik az abszolút középre pozicionálásért, a másik a kisablakok véletlenszerű kiszórásáért felel:
void setCenter(JFrame frame) {
Point center =
GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
int x = (int) center.getX() - (frame.getWidth() / 2);
int y = (int) center.getY() - (frame.getHeight() / 2);
Point ablakCenter = new Point(x, y);
frame.setLocation(ablakCenter);
}
void setCenterRandom(JFrame frame) {
Point center =
GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
int randomIntX = (int)(Math.random() * 500);
int randomIntY = (int)(Math.random() * 500);
int x = (int) center.getX() - (frame.getWidth() / 2) +
randomIntX;
int y = (int) center.getY() - (frame.getHeight() / 2) +
randomIntY;
Point ablakCenter = new Point(x, y);
frame.setLocation(ablakCenter);
}
}
Nézzük meg a futtatható Java-kódot:
Main.java
public class Main {
public static void main(String[] args) {
Window window = new Window();
}
}
Center.java
import java.awt.*;
import javax.swing.*;
public class Center {
void setCenter(JFrame frame) {
Point center =
GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
int x = (int) center.getX() - (frame.getWidth() / 2);
int y = (int) center.getY() - (frame.getHeight() / 2);
Point ablakCenter = new Point(x, y);
frame.setLocation(ablakCenter);
}
void setCenterRandom(JFrame frame) {
Point center =
GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
int randomIntX = (int)(Math.random() * 500);
int randomIntY = (int)(Math.random() * 500);
int x = (int) center.getX() - (frame.getWidth() / 2) +
randomIntX;
int y = (int) center.getY() - (frame.getHeight() / 2) +
randomIntY;
Point ablakCenter = new Point(x, y);
frame.setLocation(ablakCenter);
}
}
Window.java
import java.awt.*;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.*;
class Window extends JFrame {
JFrame frame = new JFrame();
JButton button = new JButton("OK!");
JLabel label = new JLabel();
Window() {
super();
button.setBounds(100, 100, 200, 20);
button.addActionListener(event -> buttonAction());
label.setBounds(100, 50, 200, 25);
label.setFont(new Font("Serif", Font.PLAIN, 23));
label.setText("Catch me if you can!");
frame.add(button);
frame.add(label);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Center center = new Center();
frame.setSize(400, 300);
frame.setTitle("PJP - Penzes Java Programming");
center.setCenter(frame);
frame.setLayout(null);
frame.setVisible(true);
}
private void buttonAction() {
Timer timer = new Timer();
TimerTask task = new WindowInner();
timer.schedule(task, 0, 1000);
}
}
WindowInner.java
import java.awt.*;
import javax.swing.*;
import java.util.TimerTask;
public class WindowInner extends TimerTask {
JFrame frameInner = new JFrame();
JLabel labelInner = new JLabel();
WindowInner() {
frameInner.add(labelInner);
frameInner.setDefaultCloseOperation(frameInner.DISPOSE_ON_CLOSE);
labelInner.setText("HERE I AM!");
labelInner.setFont(new Font("", Font.BOLD, 16));
labelInner.setHorizontalAlignment(SwingConstants.CENTER);
labelInner.setVerticalAlignment(SwingConstants.CENTER);
Center center = new Center();
frameInner.setSize(200, 75);
center.setCenterRandom(frameInner);
int randomR = (int)(Math.random() * 255);
int randomG = (int)(Math.random() * 255);
int randomB = (int)(Math.random() * 255);
frameInner.getContentPane().setBackground(new Color(randomR,
randomG, randomB));
frameInner.setVisible(true);
}
@Override
public void run() {
new WindowInner();
}
}
Végeredmény:
-
Az OK gomb megnyomására 1 másodpercenként véletlenszerű pozícióban megjelenik egy kisablak, amelyet be lehet csukni.
Kis gyakorlattal láthatjuk az alkalmazás fogyatékosságait, ezért a
játékon elvégezhető további feladatok a következők lehetnek:
-
a program valójában végtelen ciklusban üzemel, azaz előbb-utóbb “a gép nyer” olyanformán, hogy a képernyőt teleszórja kisablakokkal. Ezt csak a főablak bezárógombja vagy a piros konzolgomb tudja megállítani, ám ekkor végleg bezárul az alkalmazás. Kézenfekvő tehát STOP (játék megáll, de nem lép ki) és REPLAY (újrakezdés) gomb funkcionális beépítése.
-
A program alapértelmezésben 1 másodpercenként dob ki véletlenszerű pozícióban 1 kisablakot, ez az időtartam a timer.schedule(task, 0, 1000) függvényben van beállítva. Ha a felhasználó ezt külön be tudná állítani (mondjuk egy szövegmezőben), akkor ez növelné a játékélményt és persze programozási tudásunkat, mert ezt a komponenst még nem vettük (lásd a TextField című fejezetcsomagot).
-
A játékélmény még úgy is fokozható, ha kiértékelési szempontokat állítunk fel (időre megy, hányat kapott el, stb.).
Mindezen ötlet implementációja legyen a Tisztelt Olvasó házi feladata!