Kapitola 1. Grafické uživatelské rozhraní – GUI

Obsah

1.1. Základní informace
1.2. Zobrazení základního okna a přidání komponent
1.2.1. Používaný způsob zobrazení základního okna
1.2.2. Používaný způsob vložení komponent
1.3. Princip reakce na události
1.3.1. Použití vnitřních tříd
1.3.2. Použití anonymních vnitřních tříd
1.3.3. Použití anonymních vnitřních tříd a privátních metod
1.3.4. Rozhraní posluchače může mít více metod
1.4. Zpřístupnění komponenty
1.5. Viditelnost komponenty

1.1. Základní informace

  • slouží pro zobrazovací vrstvu (presentation layer/presentation tier) v třívrstvé architektuře

  • v Java Core API dva základní frameworky (API = Application Programming Interface):

    1. AWT (Abstract Windowing Toolkit) – starší, jednoduché, jen primitivní komponenty, rychlé (přímo na API OS) – vzhled podle OS, podpora v WWW prohlížečích, příklad jména třídy: java.awt.Button

    2. Swing (kódové jméno) – novější, velmi komplexní (přes 30 různých komponent), všechny typy komponent, pomalé (všechny komponenty se vykreslují samy) – vzhled nezávislý na OS, příklad jména třídy: javax.swing.JButton

      • jeho spodní vrstva je AWT (java.awt.Component)

      • souběžně využívá AWT (událostní model, fonty, barvy, layouty)

  • několik konkurenčních GUI od třetích stran – např. SWT od IBM (používáno v Eclipse)

  • GUI prošlo značným vývojem:

    • JDK 1.0 – AWT se nedokonalou obsluhou událostí

    • JDK 1.1 – AWT se současnou obsluhou událostí, Swing v přípravné fázi jako přídavné balíky

    • J2SE 1.2 – Swing součástí Core API

    • J2SE 1.3 – Swing velký rozvoj knihoven a zrychlení, AWT zcela zavrženo (přesto stále žije ;-)

    • J2SE 1.4 – rozšíření knihoven (15 balíků) a mírná vylepšení

    • J2SE 1.5 – rozšíření knihoven (+3 balíky), přidání mnoha funkcí ke stávajícím třídám, zlepšení rychlosti ...

    • Java SE 6 – rada dalsich vylepseni, SwingWorker soucasti JDK, tisk

    • Java SE 7 – JLayer dekorator, komponenty netradicnich tvaru a s podporou pruhlednosti a k tomu nekolik dalsich vylepseni

  • vztah JFC (Java Foundation Classes) a Swing:

    • JFC komplexní knihovna pro GUI, má části:

      • Swing – nejvýznamnější část

      • Pluggable Look and Feel Support – různý vzhled komponent

      • Java 2D API – 2D grafika

      • Accessibility API – podpůrné technologie (např. Brailův display)

      • Drag and Drop Support – pro spolupráci s jinými aplikacemi

  • základní texty studium jsou na WWW: The Swing Tutorial a dokumentace (Javadoc) k API

  • nezbytné dovednosti ve Swing:

    • zobrazení základního okna a přidání komponent (dnes)

    • princip reakce na události (dnes)

    • rozmístění komponent na ploše (to probereme až na přístí přednášce)

1.2. Zobrazení základního okna a přidání komponent

  • výklad na příkladu tlačítka (třída javax.swing.JButton) – atomické

  • tlačítka zatím nemá žádnou funkčnost -- po stisknutí se nic neděje

  • základní okénko (top-level container) – instance třídy JFrame – kontejnerové okénko

import javax.swing.*;

public class ZakladniDovednostiMain {
  public static void main(String[] args) {
    JFrame oknoF = new JFrame("ZakladniDovednostiMain");
    JButton tlacitkoBT = new JButton("Ahoj");
    oknoF.getContentPane().add(tlacitkoBT);
    oknoF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    oknoF.setSize(250, 70);
    oknoF.setVisible(true);
  }
}
  • je vhodné zavést jednotný způsob pojmenovávání typů objektů, např. tlacitkoBT

  • komponenty se přidávají do mezilehlého (intermediate) kontejneru, nazývaného content pane

    Poznámka

    Od JDK 1.5 lze použít místo:

    oknoF.getContentPane().add(tlacitkoBT);

    pouze jednodušší:

    oknoF.add(tlacitkoBT);

  • tento způsob zobrazení okna se používá pouze pro nejprimitivnější školní příklady

    • neumožňuje dělení na aplikační (= co se má stát po stisknutí tlačítka) a zobrazovací vrstvu

1.2.1. Používaný způsob zobrazení základního okna

  • zobrazovací třída dědí od JFrame

  • pro uložení více komponent používá JPanel

  • nově přidaný kód je vyznačený

import java.awt.*;           // FlowLayout
import javax.swing.*;

class ZobrazovaciTrida extends JFrame {
  ZobrazovaciTrida() {
    this.setTitle("ZakladniDovednostiDveTridy");

    JButton tlacitkoBT = new JButton("Ahoj");

    JPanel vnitrekPN = new JPanel();
    vnitrekPN.setLayout(new FlowLayout());
    vnitrekPN.add(tlacitkoBT);
    vnitrekPN.add(new JButton("Nazdar"));

    this.getContentPane().add(vnitrekPN);

    // this.setLocation(100, 200); ... radeji na další řádce:
    this.setLocationRelativeTo(null);  // střed obrazovky
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setSize(280, 70);
    this.setVisible(true);
  }
}

public class Hlavni {
  public static void main(String[] args) {
    new ZobrazovaciTrida();
  }
}

1.2.2. Používaný způsob vložení komponent

  • komponent je většinou mnoho a různě rozmístěných v JFrame

    • to by pak značně znepřehledňovalo konstruktor třídy JFrame

      • kód pro vložení komponent vyčleníme do samostatné metody

public class ZobrazeniVnitrek extends JFrame {
  JButton ahojBT;
  JButton nazdarBT;
  JLabel  popisLB;

  ZobrazeniVnitrek() {
    this.setTitle(getClass().getName());
    this.getContentPane().add(vytvorVnitrek());
    this.setLocationRelativeTo(null);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setSize(270, 100);
    this.setVisible(true);
  }

  Container vytvorVnitrek() {
    ahojBT = new JButton("Ahoj");
    nazdarBT = new JButton("Nazdar");
    popisLB = new JLabel("Místo pro výsledek");

    JPanel vnitrekPN = new JPanel();
    vnitrekPN.setLayout(new FlowLayout());
    vnitrekPN.add(ahojBT);
    vnitrekPN.add(nazdarBT);
    vnitrekPN.add(popisLB);

    return vnitrekPN;
  }  
}

1.3. Princip reakce na události

  • promyšlený koncept událostí založený na návrhovém vzoru vydavatel-odběratel (Observer design pattern)

    • jedna komponenta (zde tlačítko jako vydavatel -- Observable) vytváří události a všechny ostatní komponenty (zde label jako odběratel -- Observer) je zcela adresně zachycují

    • Observer patter probereme detailně na čtvrté přednášce

  • vydavatel vysílá informaci o události jen odběratelům, které se u něj sami zaregistrovali (takovým oběratelům se dále říká posluchač – listener)

    • registruje si posluchače pomocí své metody addXYZListener()

    • zaregistrovaným posluchačem může být pouze objekt, který má schopnost být posluchačem – musí implementovat rozhraní XYZListener()

      • toto rozhraní má většinou několik metod, z nichž každá reaguje na specifický typ události

      • zaslaná zpráva (událost) pak způsobí vyvolání konkrétní metody tohoto rozhraní (čti: Swing "zařídí", že se vyvolá metoda tohoto rozhraní)

  • události jsou potomci třídy java.awt.AWTEvent (nedůležité)

  • všechny základní události jsou z AWT, Swing přidává speciální události vztažené k některým svým komponentám

    • napriklad J2SE 1.5 rozlišuje celkem 41 typů událostí, z toho 18 je z AWT

    • způsob reakce na událost je zcela stejný pro všechny možné události

  • pro obsloužení jakékoliv události stačí pouze z dokumentace k Java Core API zjistit, jak se jmenuje rozhraní typu Listener od příslušného objektu

    • v popisu tohoto rozhraní se zjistí, jak se jmenují jeho metody

  • tento jednoduchý princip je možné naprogramovat mnoha odlišnými způsoby (pro různé situace jsou vhodné různé postupy)

  • v zásadě existují tři základní způsoby využívající:

    1. vnitřní třídy – nejpřehlednější

    2. anonymní vnitřní třídy – úsporný, ale málo čitelný

    3. anonymní vnitřní třídy a privátní metody – praktický

    4. třída javax.swing.Action (tu ale dnes nebudeme probírat)

  • platí obecná zásada – pro každou událost připravíme samostatnou obsluhu, tzn. kolik je zdrojů, tolik je obslužných tříd

    • nemícháme do sebe podobné věci

    • budeme-li chtít v budoucnu přidat další zdroj událostí, nemusíme žádným způsobem modifikovat kód stávajících obsluh

    • další výhodou je naprosto jasné oddělení činností – výhodné při pozdější změně funkčnosti některého zdroje

  • další ukázky budou rozvíjet program ZobrazeniVnitrek.java

1.3.1. Použití vnitřních tříd

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class UdalostiVnitrniTrida extends JFrame {
  JButton ahojBT;
  JButton nazdarBT;
  JLabel  popisLB;

  UdalostiVnitrniTrida() {
    this.setTitle(getClass().getName());
    this.getContentPane().add(vytvorVnitrek());
    obsluhyUdalosti();
    this.setLocationRelativeTo(null);
     ...
  }
  Container vytvorVnitrek() { ... }

  private void obsluhyUdalosti() {
    ahojBT.addActionListener(new ALahojBT());
    nazdarBT.addActionListener(new ALnazdarBT());    
  }

  private class ALahojBT implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      popisLB.setText("Stisknuto: " + ahojBT.getText());
    }
  }

 private class ALnazdarBT implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      popisLB.setText("Stisknuto: " + nazdarBT.getText());
    }
 }

1.3.2. Použití anonymních vnitřních tříd

  • vhodné, je-li v obsluze událostí pouze jediná a navíc krátká metoda

    • není nutné vymýšlet nové jméno pro vnitřní třídu

  • v případě dlouhých obsluh nepřehledné

import ...

public class UdalostiAnonymniVnitrniTrida extends JFrame {
  JButton ahojBT;
  JButton nazdarBT;
  JLabel  popisLB;

  UdalostiAnonymniVnitrniTrida() {
    this.setTitle(getClass().getName());
    this.getContentPane().add(vytvorVnitrek());
    obsluhyUdalosti();
    ...
  }

  Container vytvorVnitrek() { ... }
  
  private void obsluhyUdalosti() {
    ahojBT.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        popisLB.setText("Stisknuto: " +  ahojBT.getText());
      }
    });

    nazdarBT.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        popisLB.setText("Stisknuto: " + nazdarBT.getText());
      }
    });
  }

1.3.3. Použití anonymních vnitřních tříd a privátních metod

  • odstraňuje nevýhodu nepřehlednosti anonymních tříd

  • v anonymní vnitřní třídě pouze zavoláme jednu metodu – jednu řádku kódu

    • veškerá funkčnost je pak přenesena do těla této metody

      • její pojmenování jasně určuje, k jaké konkrétní komponentě se váže

public class UdalostiMetody extends JFrame {
  ...
  private void obsluhyUdalosti() {
    ahojBT.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        ahojBT_actionPerformed(e);
      }
    });

    nazdarBT.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        nazdarBT_actionPerformed(e);
      }
    });
  }

  private void ahojBT_actionPerformed(ActionEvent e) {
    popisLB.setText("Stisknuto: " + ahojBT.getText());
  }

  private void nazdarBT_actionPerformed(ActionEvent e) {
    popisLB.setText("Stisknuto: " + nazdarBT.getText());
  }

1.3.4. Rozhraní posluchače může mít více metod

  • existuje více typů událostí než jen ActionEvent a tím i více typů rozhraní

    • pro zaregistrování posluchače pomocí metody addActionListener() stačilo, aby implementoval jedinou metodu actionPerformed()

    • některá rozhraní mají i více metod a při implementování rozhraní je nutné implementovat všechny jeho metody

  • problém bude vysvětlen na rozhraní MouseListener, které má celkem pět metod (Pozor – není to FocusListener)

    • cílem všech následujících programů bude, aby při najetí kurzoru myši nad tlačítko se zvýraznil jeho nápis a po opuštění byl opět původní

      • pro tuto akci navíc využijeme vnější třídu, takže jakékoliv tlačítko pak bude mít tuto funkčnost

      • rozmístění komponent a reakce na stisk tlačítek jsou zcela stejné jako u UdalostiVnitrniTrida

1.3.4.1. Implementace prázdných metod rozhraní

  • tři nepoužité metody musí být uvedeny (tj. jsou implementovány), ale mají prázdné tělo

class MujButton extends JButton implements MouseListener {
  Font normalniF, vyraznyF;
  
  MujButton(String popis) {
    super(popis); // volání konstruktoru třídy JButton
    normalniF = this.getFont();
    vyraznyF = new Font(normalniF.getName(), Font.BOLD, normalniF.getSize()*2);
    this.addMouseListener(this);
  }
  
  public void mousePressed(MouseEvent e) { }
  public void mouseReleased(MouseEvent e) { }
  public void mouseClicked(MouseEvent e) { }
  
  public void mouseEntered(MouseEvent e) { 
    this.setFont(vyraznyF);
  }
  public void mouseExited(MouseEvent e) {
    this.setFont(normalniF);
  }
}

public class UdalostiVnejsi extends JFrame {
  MujButton ahojBT;
  MujButton nazdarBT;
  JLabel    popisLB;
  ...

1.3.4.2. Použití adaptéru

  • adaptéry jsou třídy, které implementují příslušné rozhraní, ale všechny jejich metody jsou prázdné

    • existují pro všechny listenery, které mají více než jednu metodu

  • pro implementování rozhraní je nutné pouze zdědit třídu adaptéru a překrýt jen tu metodu, kterou budeme skutečně potřebovat

    • všechny ostatní zdědíme jako prázdné

  • tento příklad není typický

class MujButtonAdapter extends JButton {
  Font normalniF, vyraznyF;
  
  MujButtonAdapter(String popis) {
    super(popis);
    normalniF = this.getFont();
    vyraznyF = new Font(normalniF.getName(), Font.BOLD, normalniF.getSize()*2);
    this.addMouseListener(new Obsluha());
  }
  
  private class Obsluha extends MouseAdapter {
    public void mouseEntered(MouseEvent e) { 
      MujButtonAdapter.this.setFont(vyraznyF);
    }
    public void mouseExited(MouseEvent e) {
      MujButtonAdapter.this.setFont(normalniF);
    }
    // ostatní tři metody není třeba implementovat
  }
}

public class UdalostiVnejsi2 extends JFrame {
  MujButtonAdapter ahojBT;
  MujButtonAdapter nazdarBT;
  JLabel  popisLB;
  ..

1.3.4.3. Adaptér a předchozí způsoby reakce na události

  • adaptéry lze využít i ve všech třech dříve uvedených způsobech

  • vnitřní třída -- stejný případ jako v 3.4.2

    JButon ahojBT;
    ahojBT.addMouseListener(new MLahojBT());
    ...
    private class MLahojBT extends MouseAdapter {
      public void mouseEntered(MouseEvent e) { 
        ahojBT.setFont(vyraznyF);
      }
      public void mouseExited(MouseEvent e) {
        ahojBT.setFont(normalniF);
      }
    }
  • anonymní vnitřní třída

    ahojBT.addMouseListener(new MouseAdapter() {
      public void mouseEntered(MouseEvent e) { 
        ahojBT.setFont(vyraznyF);
      }
      public void mouseExited(MouseEvent e) {
        ahojBT.setFont(normalniF);
      }
    });
  • metody volané z anonymní vnitřní třídy

    ahojBT.addMouseListener(new MouseAdapter() {
      public void mouseEntered(MouseEvent e) { 
        ahojBT_mouseEntered(e);
      }
      public void mouseExited(MouseEvent e) {
        ahojBT_mouseExited(e);
      }
    });
    
    void ahojBT_mouseEntered(MouseEvent e) {
      ahojBT.setFont(vyraznyF);
    }
    void ahojBT_mouseExited(MouseEvent e) {
      ahojBT.setFont(normalniF);
    }

1.4. Zpřístupnění komponenty

  • komponenta není aktivní, tj. není pomocí ní možné vygenerovat událost, ale je stále viditelná

    • metoda void setEnabled(boolean b)

      • zda je právě přístupná – boolean isEnabled()

public void actionPerformed(ActionEvent e) {
  pristBT.setEnabled(! pristBT.isEnabled());

  skryjBT.setText(skryjBT.getText().equals("Skryj") ? "Obnov" : "Skryj");
}

1.5. Viditelnost komponenty

  • zobrazenou komponentu lze skrýt a skrytou komponentu zviditelnit

    • void setVisible(boolean b) a boolean isVisible()

public void actionPerformed(ActionEvent e) {
  pristBT.setVisible(! pristBT.isVisible());

  skryjBT.setText(skryjBT.getText().equals("Skryj")? "Obnov" : "Skryj");
}