Swing

Z FI WIKI
Přejít na: navigace, hledání

Swing je knihovna tříd a komponent pro tvorbu grafického uživatelského rozhraní v Javě. Byla poprvé představena v roce 1997 na konferenci JavaOne a od verze 1.2 je standardní součástí JDK.

Swing a AWT

Swing rozšiřuje původní knihovnu pro uživatelské rozhraní, která se nazývala AWT. Jeden ze základních rozdílů je v tom, že zatímco AWT sloužila pouze jako mezivrstva mezi programem a nativními komponentami uživatelského rozhraní daného operačního systému, swing má vlastní knihovnu komponent a komponenty operačního systému nevyužívá. Výhodou je fakt, že aplikace vypadá všude stejně a také se stejně chová. Nevýhodou je to, že se aplikace odlišuje od ostatních aplikací v daném systému. Další nevýhodou jsou vyšší nároky na strojový čas procesoru, takže zejména na starších počítačích se aplikace ve swingu mohou jevit jako pomalé.

Swing má však také řadu výhod:

  • je mnohem lépe a čistěji navržen, takže poskytuje velkou flexibilitu;
  • kromě základních komponent typu tlačítko nebo titulek obsahuje i složitější a inteligentní komponenty jako je např. tabulka (JTable) nebo strom (JTree);
  • umožňuje měnit vzhled a chování aplikací prostřednictvím tzv. Look and Feel;
  • podporuje pomocné technologie pro nevidomé jako jsou screen readery nebo Braillovy displeje (viz Java Accessibility);
  • podporuje snadné programování 2D grafiky (viz Java2D);
  • podporuje technologii Drag and Drop;
  • podporuje snadné vytváření internacionalizovaných a lokalizovaných aplikací.

Zpracování událostí

Jedním ze základních konceptů swingu je událostmi řízené programování. To znamená, že jakékoliv akce nebo změny v aplikaci jsou řízeny tzv. událostmi. Zdrojem události může být uživatel (např. prostřednictvím klávesnice, myši nebo jiné vstupní periferie) nebo některá z komponent v aplikaci. Tyto události jsou doručeny příslušným komponentám, které na ně mohou zareagovat.

Existují dva základní typy událostí:

  • nízkoúrovňové události, jejichž zdrojem je obvykle myš nebo klávesnice;
  • vysokoúrovňové události, které jsou generovány některou komponentou v aplikaci většinou jako reakce na nějakou nízkoúrovňovou událost.

Příkladem nízkoúrovňové události je událost typu bylo stisknuto levé tlačítko myši na souřadnici 11,27. Jako reakce na tuto událost může vzniknout vysokoúrovňová událost bylo stisknuto tlačítko OK.

Veškeré události jsou zpracovávány ve zvláštním vlákně, které se nazývá message dispatcher. To obhospodařuje tzv. frontu událostí, do níž jsou události zařazovány. Vlákno message dispatcher tyto události postupně zpracovává v pořadí, v jakém byly do fronty zařazeny.

Reakce na událost

Pokud chceme reagovat na nějakou událost (což je v podstatě jediný způsob, jak programovat chování aplikace), musíme nejdříve vytvořit tzv. posluchače (EventListner), který bude na danou událost reagovat. Tento posluchač se pak musí zaregistrovat jako příjemce události u příslušné komponenty.

// Vytvoříme instanci tlačítka
JButton button = new JButton("Moje");
 
// Vytvoříme posluchače události
ActionListener actionListener = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        // Zobrazíme dialogový box s informací o stisknutí tlačítka
        JOptionPane.showMessageDialog(null,"Stisknuto tlačítko: " +
            e.getActionCommand());
        }
    };
 
// Zaregistrujeme posluchače u komponenty, která událost může generovat
button.addActionListener(actionListener);

Posluchač události musí implementovat příslušné rozhraní (v tomto případě ActionListener) a implementovat příslušné metody. Některé posluchače mohou reagovat na více typů událoství a tudíž mít více metod (např. MouseListener má metody mouseClicked, mouseEntered, mouseExited, mousePressed a mouseReleased).

Swing a vlákna

Důležitým aspektem programování aplikací využívajících swing je správné používání vláken. V podstatě je nutné dodržovat dvě základní pravidla:

  • Všechny operace s komponentami GUI musí být prováděny ve vlákně message dispatcher.
  • Časově náročné operace (např. načítání dat, výpočty, apod.) by neměly být prováděny ve vlákně message dispatcher.

První pravidlo je důležité z toho důvodu, že třídy komponent v knihovně swing nejsou (zejména z důvodu rychlosti a prevence před deadlocky) synchronizovány. Prováděním všech manipulací s těmito komponentami z vlákna message dispatcher je zaručeno, že nedojde ke kolizím mezi vlákny. K tomuto účelu slouží metoda SwingUtilities.invokeAndWait(java.lang.Runnable) nebo SwingUtilities.invokeLater(java.lang.Runnable). Parametrem je objekt, jehož metoda run bude provedena v rámci vlákna message dispatcheru.

// Tento kód probíhá mimo vlákno message dispatcheru
SwingUtilities.invokeLater(new Runnable() {
    public void run() {
        // tento kód se provede ve vlákně message dispatcheru
        button.setEnabled(true);
    }
});

Druhé pravidlo zabrání tomu, aby aplikace přestala reagovat na uživatelské vstupy. Všechny události jsou totiž zpracovávány vláknem message dispatcheru a pokud je toto vlákno zaneprázdněno nějakým dlouhotrvajícím procesem, události nejsou zpracovávány a aplikace nereaguje na žádné podněty uživatele (lidově řečeno zamrzne).

V souvislosti s vlákny je nutné ještě zmínit jeden důležitý aspekt. Pokud má aplikace více vláken, ukončí se až když se ukončí všechna vlákna (s výjimkou vláken s nastaveným příznakem daemon), nebo když je zavolána metoda System.exit(int). Protože vlákno message dispatcheru běží v nekonečné smyčce, aplikace běží dokud není aplikace ukončena voláním této metody. Tuto metodu je možné volat buď přímo, nebo je možné pomocí metody JFrame.setDefaultCloseOperation(int) zajistit její automatické zavolání při zavření příslušného okna.

Swing worker

public class SwingWorkerExample extends javax.swing.JFrame {
    
    private MySwingWorker worker;
    
    private class MySwingWorker extends SwingWorker<Integer,Integer> {
        
        protected Integer doInBackground() throws Exception {
            int result = 0;
            for(int i=0; i <= 100; i++) {
                if (isCancelled()) {
                    break;
                }
                Thread.sleep(10);
                result += i;
                publish(i);
                setProgress(i);
            }
            return result;
        }
        
        protected void done() {
            jButtonStart.setEnabled(true);
            if (!isCancelled()) {
                try {
                    jTextArea1.append("Result is: " + get());
                } catch (Exception ex) {
                    jTextArea1.append("Error: " + ex);
                }
            }
        }
        
        protected void process(List<Integer> chunks) {
            jTextArea1.append(chunks.toString() + "\n");
        }
        
    }
    
    private PropertyChangeListener progressListener = new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
            if (evt.getPropertyName().equals("progress")) {
                jProgressBar1.setValue((Integer) evt.getNewValue());
            }
        }
    };
    
    public SwingWorkerExample() {
        initComponents();
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc=" Generated Code ">                          
    private void initComponents() {
        jButtonStart = new javax.swing.JButton();
        jButtonCancel = new javax.swing.JButton();
        jProgressBar1 = new javax.swing.JProgressBar();
        jScrollPane1 = new javax.swing.JScrollPane();
        jTextArea1 = new javax.swing.JTextArea();
 
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        jButtonStart.setText("Start");
        jButtonStart.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonStartActionPerformed(evt);
            }
        });
 
        jButtonCancel.setText("Cancel");
        jButtonCancel.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonCancelActionPerformed(evt);
            }
        });
 
        jTextArea1.setColumns(20);
        jTextArea1.setRows(5);
        jScrollPane1.setViewportView(jTextArea1);
 
        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE)
                    .addComponent(jProgressBar1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                        .addComponent(jButtonStart)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jButtonCancel)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 225, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jButtonStart)
                    .addComponent(jButtonCancel))
                .addContainerGap())
        );
        pack();
    }// </editor-fold>                        
    
    private void jButtonCancelActionPerformed(java.awt.event.ActionEvent evt) {                                              
        worker.cancel(false);
    }                                             
    
    
    private void jButtonStartActionPerformed(java.awt.event.ActionEvent evt) {                                             
        jButtonStart.setEnabled(false);
        worker = new MySwingWorker();
        worker.addPropertyChangeListener(progressListener);
        worker.execute();
    }                                            
    
    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new SwingWorkerExample().setVisible(true);
            }
        });
    }
    
    // Variables declaration - do not modify                     
    private javax.swing.JButton jButtonCancel;
    private javax.swing.JButton jButtonStart;
    private javax.swing.JProgressBar jProgressBar1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextArea jTextArea1;
    // End of variables declaration                   
    
}

První okno

Nyní můžeme přistoupit k první jednoduché aplikaci.

public class Main {
 
    public static void main(String[] args) {
 
        // Inicializaci GUI provedeme ve vlákně message dispatcheru,
        // ne v hlavním vlákně!
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                // Vytvoříme instanci třídy Frame, která reprezentuje okno
                JFrame frame = new JFrame();
                // Zajistíme, aby se po zavření okna ukončila i aplikace
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                // Nastavíme titulek okna
                frame.setTitle("Moje první okno");
                // Zobrazíme okno
                frame.setVisible(true);
            }
        });
    }
}

Model 1

Swing využívá koncept který se nazývá Model 1, což je zjednodušená verze konceptu MVC (Model-View-Controller). Základním principem je, že je oddělena komponenta pro zobrazení dat od jejich reprezentace (která se nazývá model).

Např. komponenta JList, která reprezentuje seznam položek, nemá tento seznam uložený přímo v sobě, ale zobrazovaná data získává prostřednictvím jiné komponenty, která implementuje rozhraní ListModel. To umožňuje poskytovat data z různých zdrojů, aniž by je bylo nutné kopírovat. Je samozřejmě k dispozici základní implementace tohoto rozhraní, která data ukládá v jednoduchém seznamu (viz DefaultListModel).



Zdroje informací