Crearea interfetelor grafice cu  pachetul swing

  1. Introducere
  2. Arhitectura Model-View-Controller(MVC)
  3. Look and Feel
  4. Utilizarea componentelor Swing
  5. JLabel
  6. JButton
  7. JTextComponent
  8. JPasswordField
  9. JScrollBar
  10. JSlider
  11. JProgressBar
  12. JComboBox
  13. JList
  14. Timer

Introducere

Daca  ati incercat sa creati componente proprii  in AWT, probabil ca ati extins clasa Canvas sau Panel. Aceasta metoda functioneaza insa ridica cateva probleme si anume atat Canvas cat si Panel sunt componente netransparente si daca o componenta contine prea multe componente native atat scade viteza de executie a programului. De exemplu sa consideram un checkbox, ori de cate ori modificam starea acestuia, acest obiect comunica cu un obiect checkbox nativ si cele doua obiecte trebuie sa fie ibtotdeauna in aceasi stare, adica starile lor se modifica in mod sincron. Acesasta soncronizare incetineste viteza de executie a aplicatiei. O alta problema cu componentele AWT este ca modul de afisare a acestora depinde de platforma pe care se ruleaza aplicatia. Deci cum arata ele nu depinde de programator ci de componentele native. Pentru aceste neajunsuri JavaSoft introduce componentele "lightweight". Aceste componente sunt scrise 100% in Java si modul de afisare astfel depinde doar de programator. In primele versiuni Java clasele Component si Container erau clase finale, adica nu se puteau extinde. Incepand cu versiunea JDK 1.1 aceste clase nu mai sunt finale, devenind posibila extinderea lor. Aceste componente nu vor acea perechea lor nativa, astfel nefiind necesara comunicarea acestora cu perechile lor native si toate acestea conducand la programe mai rapide, iar ele arata pe orice platforma la fel.  Intr-o aplicatie se poate combina utilizarea componentelor AWT cu cele "lightweight" (desi nu se recomanda), dar trebuie tinut cont de faptul ca componentele AWT sunt netransparente, ele vor fi intotdeauna in prim plan, acoperind cele "lightweight".

Exemplul urmator prezinta o componenta ligtweight, care este de fapt un Button, care arata ca si harta statului Texas. Pentru implementarea acestei componente am utilizat doua clase. O clasa Texas care contine doar doua variabile statice de tip tablou cu intregi si contin coordonatele punctelor pentru desenarea unui poligon si o alta clasa care implementeaza componenta. exista si o atreia clasa care afiseaza o fereastra cu trei asemenea butoane. La apasarea acestor butoane se afiseaza informatii despre buton la iesirea standard.

public class Texas {

  static int X[] = {
    150, 150, 150, 149, 149, 149, 150, 151, 152, 151,
    151, 151, 151, 151, 152, 152, 153, 153, 152, 152, 151, 151, 151, 151, 150,
    149, 150, 149, 147, 147, 147, 147, 145, 144, 143, 142,
    142, 141, 141, 140, 135, 134, 133, 131, 130, 129, 127, 125, 124, 123, 121,
    120, 120, 120, 119, 119, 118, 117, 117, 116, 115, 115, 115, 114, 114, 114,
    114, 113, 112, 111, 109, 108, 108, 106, 106, 105, 104, 104, 101, 101, 99,
    99, 98, 98, 97, 96, 93, 93, 93, 93, 92, 91, 90, 88, 88, 86, 86, 84, 85, 55,
    54, 50, 12, 11, 12, 12, 11, 12, 12, 14, 14, 15, 17, 23, 28, 28, 29, 29, 29,
    30, 30, 30, 32, 39, 45, 46, 47, 48, 49, 51, 52, 53, 55, 55, 56, 56, 57, 58,
    61, 62, 64, 64, 67, 67, 68, 68, 68, 69, 71, 73, 74, 74, 77, 78, 81, 82, 84,
    85, 86, 86, 86, 87, 87, 88, 90, 91, 92, 94, 97, 100, 105, 106, 109, 110, 111,
    112, 113, 113, 112, 112, 111, 109, 108, 110, 109, 109, 109, 109, 109, 109,
    110, 111, 111, 110, 111, 112, 114, 114, 114, 114, 114, 115, 115, 116, 117,
    117, 117, 117, 117, 118, 118, 119, 121, 121, 122, 121, 120, 120, 122, 123,
    124, 124, 124, 124, 124, 125, 129, 135, 135, 138, 139, 137, 138, 139, 140,
    140, 140, 142, 143, 147};

  static int Y[] = {
    98, 98, 98, 97, 96, 96, 95, 94, 91, 90,
    89, 89, 88, 86, 86, 85, 82, 81, 79, 78, 77, 76, 76, 75, 74,
    74, 73, 71, 69, 68, 56, 49, 48, 49, 49, 48,
    48, 48, 47, 47, 44, 45, 45, 45, 45, 45, 45, 46, 46, 47, 46, 46, 45, 45, 45,
    46, 46, 45, 44, 44, 45, 46, 47, 47, 46, 45, 45, 45, 46, 46, 44, 44, 45, 45,
    44, 43, 42, 42, 42, 42, 42, 41, 41, 41, 41, 41, 40, 40, 39, 39, 38, 37, 38,
    38, 38, 36, 36, 35, 11, 9, 9, 66, 63, 63, 63, 64, 64, 65, 66, 67, 68, 70, 71,
    78, 82, 83, 84, 85, 86, 87, 91, 93, 96, 101, 104, 105, 104, 103, 102, 97, 95,
    95, 95, 95, 95, 94, 94, 95, 95, 96, 96, 96, 97, 98, 99, 99, 99, 100, 102, 104,
    105, 106, 114, 116, 120, 121, 123, 124, 125, 126, 128, 129, 132, 133, 138,
    140, 140, 141, 142, 144, 145, 145, 147, 147, 146, 146, 146, 145, 144, 144,
    143, 137, 134, 131, 130, 129, 129, 129, 129, 129, 128, 124, 124, 122, 121,
    122, 121, 119, 118, 118, 118, 118, 117, 118, 118, 118, 117, 116, 115, 115,
    115, 116, 115, 115, 114, 114, 113, 113, 112, 111, 111, 111, 112, 112, 112,
    112, 111, 107, 105, 102, 102, 100, 99, 98, 98, 99, 100, 100, 100, 98};
  }

import java.awt.*;
import java.awt.event.*;

public class texasButton extends Component {

  boolean inPoly = false;
  boolean mouseDown = false;
  ActionListener listener;
  Polygon poly = new Polygon(Texas.X, Texas.Y, Texas.X.length);

  public texasButton() {
    enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    setBackground(Color.blue);
    }

  public synchronized void addActionListener(ActionListener l) {
    listener = AWTEventMulticaster.add(listener, l);
    }

  public synchronized void removeActionListener(ActionListener l) {
    listener = AWTEventMulticaster.remove(listener, l);
    }

  public void paint(Graphics g) {
    if (inPoly && mouseDown)
      g.setColor(getBackground().darker().darker());
    else
      g.setColor(getBackground());
    g.fillPolygon(poly);
    }

  public void processMouseMotionEvent(MouseEvent e) {
    if (poly.contains(e.getPoint())) {
      if (!inPoly && mouseDown)
        repaint();
      inPoly = true;
      }
    else {
      if (inPoly && mouseDown)
        repaint();
      inPoly = false;
      }
    }
 
  public void processMouseEvent(MouseEvent e) {
    switch (e.getID()) {

      case MouseEvent.MOUSE_PRESSED :
        if (inPoly) {
          mouseDown = true;
          repaint();
          }
        break;

      case MouseEvent.MOUSE_RELEASED :
        if (inPoly == true && mouseDown == true) {
          if (listener != null)
            listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ""));
          repaint();
          }
        mouseDown = false;
        break;
      }
    super.processMouseEvent(e);
    }

  public Dimension getPreferredSize() {
    return getMinimumSize();
    }

  public Dimension getMinimumSize() {
    return new Dimension(160,150);
    }
 
  }

import java.awt.*;
import java.awt.event.*;

public class texasTest extends Frame implements ActionListener {
  public static void main(String args[]) {
    texasTest t = new texasTest();
    }

  public void actionPerformed(ActionEvent ae) {
    System.out.println(ae.getSource());
    }

  public texasTest() {
    setBackground(Color.lightGray);
    setLayout( new GridLayout(1, 3));
 
    texasButton cb[] = new texasButton[ 3 ];
    for( int i =0; i<cb.length; i++)
    {
         cb[ i ] = new texasButton();
         cb[ i ].addActionListener(this);
         add(cb[ i ]);
    }
    addWindowListener( new WindowAdapter(){
         public void windowClosing( WindowEvent e ){
             setVisible( false );
             System.exit( 0 );
         }
    });
    setBounds(1,1,500,200);
    setVisible( true );
    }
  }
 
 
 

Arhitectura Model-View-Controller(MVC)

Clasele din pachetul Swing sunt bazate pe arhitectura MVC. Intr-o interfata grafica MVC intotdeauna exista trei obiecte model, view si controller (structura, reprezentare si comportament).  Modelul este reprezentarea logica, view-ul este reprezentarea vizuala si controller-ul specifica comportamentul. Separarea acestor elemente permite urmatoarele: Pentru modelul unui buton putem avea o variabila intreaga cu doua valori 0 si 1, sau o variabila logica cu valorile false si true.

Look and Feel

In AWT fiecare componenta grafica are o clasa nativa ( clasa peer) care comunica cu componenta nativa a sistemul de operare. De aceea un obiect Button apare ca si un buton Win95, daca este rulat sub Win95, iar daca este rulat pe Solaris atunci arata ca un buton Motif. Componentele din pachetul Swing nu mai au aceste clase peer, si pot arata in mod diferit pe aceasi platforma din cauza ca respecta arhitectura MVC.

Pentru a schimba modul de afisare a componentelor swing se utilizeaza metoda setLookAndFeel() a clasei UIManager.

try {
    UIManager.setLookAndFeel( "javax.swing.plaf.metal.MetalLookAndFeel");
}

catch (Exception e) {
        System.err.println("Couldn't use the metal "+ "look and feel: " + e);
}

Utilizarea componentelor Swing

Pachetul swing contine doua tipuri de componente: Containerele reprezinta cadrul in care aceste componente pot exista. Imaginea urmatoare prezinta ierarhia componentelor unei aplicatii cu o fereastra, doua butoane, o caseta text si o lista.


 
 

Obiectul contentpane din figura precedenta este un obiect Container obisnuit si este sub containerul swing.

Codul pentru crearea suprafetei grafice:

JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.add(textField);
panel.add(list);

Container contentPane = this.getContentPane();
contentPane.setLayout(new FlowLayout());
contentPane.add(button1);
contentPane.add(button2);
contentPane.add(panel);
Observatie: Nu se pot adauga componentele direct la containerul swing.

Figura urmatoare contine cateva componente impreuna cu ierarhia de clase in care se incadreaza:


 
 

JLabel

Un JLabel este similar cu o eticehta java.awt.Label, avand o serie de functionalitati in plus: Exemplul urmator utilizeaza interfata Icon:
public interface Icon{
    void paintIcon( Component c, Graphics g, int x, int y );
    int getIconWidth();
    int getIconHeight();
}
Clasa ImageIcon este o implementare a clasei Icon, care creaza o iconita dintr-un obiect de tip Image.
Icon iconPicture = new ImageIcon("Apple.gif");
 

 
 
 

Un exemplu:

Sursa:

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

public class SwingPanel extends JPanel
{

  public SwingPanel()
  {
                setLayout( new GridLayout( 3, 1 ));
                
                JLabel simplelabel = new JLabel("This is a simple label");
                add( simplelabel );
                
                JLabel iconlabel = new JLabel("This is a fancy label");
                Icon icon = new ImageIcon("icon.gif");
                iconlabel.setIcon( icon );
                Font font = new Font("Serif",Font.BOLD|Font.ITALIC,30);
                iconlabel.setFont( font );
                add( iconlabel );

                JLabel myiconlabel = new JLabel("This is my icon label");
                Icon myicon = new RedOval();
                myiconlabel.setIcon( myicon );
                add( myiconlabel );
  }
}

import javax.swing.JFrame;

public class SwingFrame extends JFrame
{
    private SwingPanel p;

    public SwingFrame()
    {
        p = new SwingPanel();
        getContentPane().add( p );
    }

    public static void main( String args[] )
    {
        SwingFrame f = new SwingFrame();
        f.addWindowListener( new java.awt.event.WindowAdapter(){
            public void windowClosing( java.awt.event.WindowEvent e )
            {
                System.exit( 0 );
            }
        });
        f.setBounds(1,1,400,300);
        f.setVisible( true );
    }
}
In toate exemplele demonstrative pentru componente se vor utiliza doua clase. O clasa care extinde clasa JPanel si care contine componenta specifica si una de tip container care extinde clasa JFrame si afiseaza panelul cu componentele specifice. De aceea in cazul urmatoarelor exemple vom descrie doar constructorul clasei SwingPanel.

JButton

Un buton JButton se instantieaza analog ca si un butom java.awt.Button. Evenimentul care se produce la apasarea acestuia este tot un ActionEvent si evenimentul se livreaza tuturor receptorilor inregistrati.Culoarea butonului este identic cu culoarea containerului, deci trebuie modificat la SystemColor.control.
setLayout( new FlowLayout() );
JButton simplebutton = new JButton("Simple");
add( simplebutton );
simplebutton.setBackground(SystemColor.control);
Icon icon = new ImageIcon("icon.gif");
JButton iconbutton = new JButton("Icon", icon );
iconbutton.setBackground(SystemColor.control);
add( iconbutton );

JTextComponent

JTextComponent este o clasa generala cu toate functiunile unui editor de text simplu. Metodele cele mai utilizate: Subclasele clasei JTextComponent sunt: JTextField, JTextArea, JTextPane.
setLayout( new BorderLayout());
JTextPane tp = new JTextPane();
MutableAttributeSet attr = new SimpleAttributeSet();
StyleConstants.setFontFamily(attr,"Serif");
StyleConstants.setFontSize(attr,18);
StyleConstants.setBold(attr,true);
tp.setCharacterAttributes( attr, false );
add( tp, BorderLayout.CENTER );

JPasswordField

Este subclasa clasei JTextField. Cu metoda setEchoChar() se poate specifica caracterul care se afiseaza in locul caracterelor introduse. Implicit este '*'.
setLayout( new FlowLayout() );
JPasswordField pf = new JPasswordField(20);
pf.setEchoChar('*');
add(pf );

JScrollBar

Este varianta "lightweight" a componentei java.awt.Scrollbar.
setLayout( new BorderLayout() );
JScrollBar vertical = new JScrollBar(JScrollBar.VERTICAL,0,5,0,100);
add(vertical, BorderLayout.EAST );
JScrollBar horizontal = new JScrollBar(JScrollBar.HORIZONTAL,0,5,0,100);
add(horizontal, BorderLayout.SOUTH );

JSlider

JSlider este similar cu JScrollBar, dar permite afisarea marcjelor minore respectiv a celor majore precum si  desenarea unui chenar Border in jurul componentei.

setLayout( new BorderLayout() );

JSlider s1 = new JSlider( JSlider.VERTICAL, 0, 100, 50 );
s1.setPaintTicks( true );
s1.setMajorTickSpacing(10);
s1.setMinorTickSpacing(2);
add( s1, BorderLayout.EAST);

JSlider s2 = new JSlider( JSlider.VERTICAL, 0, 100, 50 );
s2.setPaintTicks( true );
s2.setMinorTickSpacing(5);
add( s2, BorderLayout.WEST);

JSlider s3 = new JSlider( JSlider.HORIZONTAL, 0, 100, 50 );
s3.setPaintTicks( true );
s3.setMajorTickSpacing(10);
add( s3, BorderLayout.SOUTH);


JSlider s4 = new JSlider( JSlider.HORIZONTAL, 0, 100, 50 );
s4.setPaintTicks( true );
s4.setBorder( LineBorder.createBlackLineBorder());
add( s4, BorderLayout.NORTH);

JProgressBar

Se utilizeaza pentru vizualizarea evolutiei unei operatii Pentru a lucra cu o asemenea componenta prima data trebuie s-o intializam
JProgressBar p = new JProgressBar();
p.setMinimum( 0 );
setMaximum( numar_operatii );
 iar dupa aceea intr-un ciclu sa modificam starea componentei
p.setValue( p.getMinimum());
for( int i=0; i<numar_operatii; i++)
{
//se executa o operatie
p.setValue( i );
}
Exemplu:
import javax.swing.JFrame;

public class SwingFrame extends JFrame
{
        private SwingPanel p;

        public SwingFrame()
        {
                p = new SwingPanel();
                getContentPane().add( p );
                Thread t = new Thread( p );
                t.start();
        }

        public static void main( String args[] )
        {

                SwingFrame f = new SwingFrame();
                f.addWindowListener( new java.awt.event.WindowAdapter(){
                        public void windowClosing( java.awt.event.WindowEvent e )
                        {
                                System.exit( 0 );
                        }
                }
                );
                f.setBounds(1,1,400,300);
                f.setVisible( true );
        }
}
import javax.swing.*;
import java.awt.*;


public class SwingPanel extends JPanel implements Runnable
{

        JProgressBar pb;

        public SwingPanel()
        {
                setLayout( new FlowLayout() );
                pb = new JProgressBar();
                pb.setMinimum(0);
                pb.setMaximum(100);
                pb.setValue( pb.getMinimum());
                add( pb );
        }

        public void run()
        {
                for( int i=0; i<pb.getMaximum(); i++ )
                {
                        pb.setValue( i );
                        try{
                                Thread.sleep( 100 );
                        }
                        catch( InterruptedException e ){}
                }
        }


}

JComboBox

Componenta este asemanatoare cu Choice din AWT, dar permite si editarea, adica introducerea unei valori care nu exista in lista.

Exemplu de utilizare:

Sursa:

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

public class SwingPanel extends JPanel
{

        String elements[] ={"One","Two","Three","Four","Five"};

        public SwingPanel()
        {
                setLayout( new FlowLayout() );
                JComboBox c1 = new JComboBox();
                JComboBox c2 = new JComboBox();
                for( int i =0; i< elements.length; i++ )
                {
                        c1.addItem( elements[ i ] );
                        c2.addItem( elements[ i ] );
                }
                c1.setEditable( false );
                c2.setEditable( true );
                add( c1 );
                add( c2 );
        }
}

JList

Componenta are doua implementari, una care respecta arhitectura MVC si una care nu, se comporta la fel ca si o lista AWT. Totusi si aceasta implementare mai simpla s-a imbunatatit. Putem adauga elementele prin contructor transmitandu-i un tablou de stringuri. JList nu suporta  scroll, adica daca dorim aceasta facilitate atunci trebuie  s-o punem intr-un ScrollPane.
import javax.swing.*;
import java.awt.*;


public class SwingPanel extends JPanel
{

        String elements[] = {"Orange","Lemon","Strawberry","Raspberry","Apple","Banana"};

        public SwingPanel()
        {
                setLayout( new BorderLayout() );
                JList list = new JList(elements);
                ScrollPane pane = new ScrollPane();
                pane.add( list );
                add( pane, BorderLayout.CENTER );

        }
}

Timer

Pe langa componente grafice, pachetul swing vine si cu o serie de utilitare. Un astfel de utilitar este clasa Timer. Exemplul urmator este o fereastra cu trei butoane si cu trei obiecte de tip Timer. Fiecare obiect Timer controleaza un buton, adica la trecerea intervalului specific fiecarui timer se incrementeaza numarul de pe butonul corespunzator. La crearea  obiectelor de tip Timer specificam intervalul de timp la care actioneaza respectiv si obiectul listener, adica componenta care este notificata. Timerul trimite mesajul actionPerformed() dupa trecerea  intervalului de timp.Obiectul Timer trebuie pornit cu metoda start().

Diagrama de clasa

Diagrama de secventiere

Sursa

import javax.swing.JFrame;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.Timer;
import java.awt.event.ActionEvent;

public class MyWindow extends JFrame implements ActionListener 
{
        private JButton b[];
        private Timer t[];

        public MyWindow() 
        {
                final int n = 3;
                int i;
                getContentPane().setLayout( new java.awt.FlowLayout() );
                b = new JButton[ n ];
                for(i=0;i<n;i++)
                {
                        b[i] = new JButton(Integer.toString( i ));
                        getContentPane().add( b[ i ] );
                }
                addWindowListener( new java.awt.event.WindowAdapter(){
                        public void windowClosing( java.awt.event.WindowEvent e )
                        {
                                setVisible( false );
                                System.exit( 0 );
                        }
                });
                t = new Timer[ n ];
                for(i=0;i<n;i++)
                {
                        t[ i ] = new Timer( (i+1)*100, this );
                        t[ i ].start();
                }
        }

        /**
        @roseuid 3A72EE9202F8
        */
        public static void main(String[] args) 
        {
                MyWindow w = new MyWindow();
                w.setBounds(1,1,400,300);
                w.setVisible( true );
        }

        /**
        @roseuid 3A72F8A50122
        */
        public void actionPerformed(ActionEvent arg0) 
        {

                if( arg0.getSource() == t[0] )
                {
                        System.out.println( "Button: "+0);
                        b[0].setLabel( Integer.toString( Integer.parseInt(b[0].getLabel())+1 ));

                }
                else
                        if( arg0.getSource() == t[1] )
                        {
                                System.out.println( "Button: "+1);
                                b[1].setLabel( Integer.toString( Integer.parseInt(b[1].getLabel())+1 ));

                        }
                        else
                        if( arg0.getSource() == t[2] )
                        {
                                System.out.println( "Button: "+2);
                                b[2].setLabel( Integer.toString( Integer.parseInt(b[2].getLabel())+1 ));

                        }
        }
}