Belajar Membuat Komponen Baru Di Swing


NetBeans Swing GUI Builder boleh dibilang adalah visual editor terbaik untuk Swing. Berkat NetBeans Swing GUI Builder, pengguna dapat merancang GUI berbasis Swing dengan mudah dan cepat seperti pada seri ‘visual’ -nya Microsoft. Masalah yang kemudian muncul adalah kampus lokal kemudian membuat kurikulum pemograman Swing dan menggantinya dengan visual builder. Mereka berusaha mengajarkan Swing dan Java seperti pada teknik Rapid Application Development (RAD) layaknya Visual Basic.

Memilih untuk memakai GUI Builder atau membuat GUI secara kode program adalah selera ‘hidup’ masing-masing developer yang tidak boleh dipaksakan. Tapi, mempelajari komponen Swing tanpa menyentuh arsitektur Swing dan memperlakukan mereka layaknya komponen terbatas seperti di seri ‘visual’ adalah sebuah kesalahan besar. Mengapa demikian? Hal ini karena justru kelebihan utama Swing adalah sifatnya yang modular dan extensible.

Sebagai contoh, saya menemukan keterbatasan JToolBar pada kasus yang saya hadapi. Bila ada banyak icon di JToolBar dan ukuran layar terbatas, maka mereka akan terpotong. Sebagai contoh, pada JToolBar saya, terdapat 9 icon besar. Tapi bila JFrame diperkecil, maka icon yang tidak terlihat akan ‘hilang’ seperti yang ditunjukkan pada gambar berikut ini:

JToolBar akan menyembunyikan icon bila tidak muat

JToolBar akan menyembunyikan icon bila tidak muat

Saya membutuhkan fasilitas scrolling sehingga saya tetap dapat memilih icon yang tidak ditampilkan. Tapi, tidak ada properties di JToolBar yang bisa saya atur untuk keperluan ini! Apakah ini berarti ‘kiamat’? Pada teknologi GUI lain, bila sudah tidak ada properties yang bisa diatur, jawabannya mungkin ‘iya’. Tapi tidak untuk Swing! Pada Swing, saya bisa membuat komponen baru dengan mudah. Saya bisa men-reuse elemen dari sebuah komponen tanpa harus membuat segala sesuatunya dari awal.

Sebagai contoh, saya membuat versi JToolBar yang bisa di-scroll dengan nama ScrollableToolBar yang kode programnya terlihat seperti berikut ini:

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.BasicArrowButton;
import java.awt.*;
import java.awt.event.*;

public class ScrollableToolBar extends JPanel {

    private JToolBar toolBar;
    private JScrollPane scrollPane;
    private JButton btnLeft;
    private JButton btnRight;

    public ActionLeft actionLeft = new ActionLeft();
    public ActionRight actionRight = new ActionRight();

    public ScrollableToolBar() {
        toolBar = new JToolBar();
        scrollPane = new JScrollPane(toolBar, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPane.getViewport().addChangeListener(new DisplayButtonChangeListener());
        btnLeft = new BasicArrowButton(SwingConstants.WEST);
        btnLeft.setAction(actionLeft);
        btnLeft.addMouseListener(new PressButtonMouseAdapter(actionLeft));
        btnRight = new BasicArrowButton(SwingConstants.EAST);
        btnRight.addMouseListener(new PressButtonMouseAdapter(actionRight));

        setLayout(new BorderLayout());
        add(scrollPane, BorderLayout.CENTER);
        add(btnLeft, BorderLayout.LINE_START);
        add(btnRight, BorderLayout.LINE_END);
    }

    public JToolBar getToolBar() {
        return toolBar;
    }

    @Override
    public Component add(Component comp) {
        return toolBar.add(comp);
    }

    public class ActionLeft extends AbstractAction {

        @Override
        public void actionPerformed(ActionEvent e) {
            JScrollBar scrollBar = scrollPane.getHorizontalScrollBar();
            scrollBar.setValue(scrollBar.getValue() - scrollBar.getBlockIncrement());
        }

    }

    public class ActionRight extends AbstractAction {

        @Override
        public void actionPerformed(ActionEvent e) {
            JScrollBar scrollBar = scrollPane.getHorizontalScrollBar();
            scrollBar.setValue(scrollBar.getValue() + scrollBar.getBlockIncrement());
        }

    }

    public class PressButtonMouseAdapter extends MouseAdapter implements ActionListener {

        private Action action;
        private Timer timer;

        public PressButtonMouseAdapter(final Action action) {
            this.action = action;
            timer = new Timer(20, this);
        }

        @Override
        public void mousePressed(MouseEvent e) {
            timer.start();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            timer.stop();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            action.actionPerformed(null);
        }
    }

    private class DisplayButtonChangeListener implements ChangeListener {

        @Override
        public void stateChanged(ChangeEvent e) {
            JViewport viewport = scrollPane.getViewport();
            boolean buttonVisible = (toolBar.getWidth() > viewport.getWidth());
            btnLeft.setVisible(buttonVisible);
            btnRight.setVisible(buttonVisible);
        }
    }

}

Sekarang, saya dapat melakukan scrolling pada JToolBar seperti yang diperlihatkan pada gambar berikut ini:

Komponen baru dengan JToolBar yang dapat di-scroll

Komponen baru dengan JToolBar yang dapat di-scroll

ScrollableToolBar diturunkan dari class JPanel sehingga merupakan komponen yang terdiri dari beberapa komponen lainnya. Pada kode program di atas, ScrollableToolBar terdiri atas 2 JButton, sebuah JScrollPane dan sebuah JToolBar yang masing-masing diwakili oleh variabel seperti yang terlihat pada visualisasi berikut ini:

Komposisi dari ScrollableToolBar

Komposisi dari ScrollableToolBar

Saya membutuhkan JScrollPane untuk melakukan scrolling. Tapi, saya tidak ingin memperlihatkan scrollbar. Oleh sebab itu, saya membuat JScrollPane dengan constructor seperti pada:

scrollPane = new JScrollPane(toolBar, 
   ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, 
   ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

btnLeft dan btnRight adalah sebuah JButton biasa yang memiliki gambar panah. Untuk itu, saya memakai BasicArrowButton yang merupakan komponen yang dipakai oleh JScrollBar. Yup, scrollbar sudah punya tombol panah, saya tidak perlu membuat lagi dari awal!. Disini terlihat bahwa saya bisa me-reuse elemen sebuah komponen dengan mudah. Btw, visual editor biasanya tidak akan menampilkan BasicArrowButton untuk di-drag sehingga komponen seperti ini hanya bisa dipakai melalui kode program! Pada constructor BasicArrowButton, saya menentukan arah tanda panah seperti yang terlihat pada kode program berikut ini:

btnLeft = new BasicArrowButton(SwingConstants.WEST);
btnRight = new BasicArrowButton(SwingConstants.EAST);

Untuk melakukan scrolling pada btnLeft atau btnRight di-klik, saya memanipulasi scrollbar tak terlihat milik JScrollPane seperti yang terlihat pada kode program berikut ini:

public class ActionLeft extends AbstractAction {

  @Override
  public void actionPerformed(ActionEvent e) {
    JScrollBar scrollBar = scrollPane.getHorizontalScrollBar();
    scrollBar.setValue(scrollBar.getValue() - scrollBar.getBlockIncrement());
  }

}

public class ActionRight extends AbstractAction {

   @Override
   public void actionPerformed(ActionEvent e) {
     JScrollBar scrollBar = scrollPane.getHorizontalScrollBar();
     scrollBar.setValue(scrollBar.getValue() + scrollBar.getBlockIncrement());
   }

}

Saya tidak perlu khawatir bila nilai yang berikan melalui JScrollBar.setValue() melewati batas yang diperbolehkan karena setValue() akan memakai nilai maksimal atau nilai minimum bila ada nilai yang melewati batas yang diperbolehkan.

Action di atas didaftarkan agar dikerjakan bila JButton di-klik. Pada saat melakukan scrolling, pengguna cenderung tidak hanya men-klik, melainkan menahan tombol sehingga proses scrolling dapat berlangsung terus menerus. Oleh sebab itu, saya memakai MouseAdapter untuk menjalankan sebuah timer yang aktif bila pengguna menahan tombol (pada handler mousePressed) dan tidak aktif setelah pengguna melepaskan tombol (pada handler mouseReleased). Hal ini terlihat pada cuplikan kode program berikut ini:

public class PressButtonMouseAdapter extends MouseAdapter implements ActionListener {

  private Action action;
  private Timer timer;

  public PressButtonMouseAdapter(final Action action) {
    this.action = action;
    timer = new Timer(20, this);
  }

  @Override
  public void mousePressed(MouseEvent e) {
    timer.start();
  }

  @Override
  public void mouseReleased(MouseEvent e) {
    timer.stop();
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    action.actionPerformed(null);
  }
}

Class javax.swing.Timer (jangan tertukar dengan java.util.Timer!) adalah sebuah class yang sangat berguna untuk mengerjakan sebuah aksi pada GUI secara periodik. Class ini tidak terlihat di NetBeans Swing GUI Editor sehingga mahasiswa yang mempelajari Swing sebagai komponen drag-n-drop cenderung mengabaikannya. Ini adalah salah satu alasan mengapa saya selalu menyarankan mahasiswa mulai membuat GUI berbasis Swing melalui kode program terlebih dahulu. Setelah akrab dengan Swing, mereka boleh menyentuh Swing GUI Editor (analogi yang sama pada dunia web: mahasiswa belajar menulis HTML dan CSS terlebih dahulu baru boleh menyentuh GUI designer seperti Adobe Dreamweaver).

Btw, salah satu cara jitu untuk mempelajari Swing adalah dengan mempelajari kode program untuk komponen Swing itu sendiri. Yup, source untuk seluruh komponen Swing (dan juga API Java lainnya) bisa dibaca secara bebas!. Sebagai contoh, saya meniru cara memakai Timer saat tombol mouse ditahan dengan mempelajari kode program untuk JScrollBar itu sendiri yang bisa ditemui pada class javax.swing.plaf.basic.BasicScrollBarUI. Ini adalah cuplikan kode programnya:

...

/**
 * Listener for cursor keys.
 */
protected class ArrowButtonListener extends MouseAdapter
{
   // Because we are handling both mousePressed and Actions
   // we need to make sure we don't fire under both conditions.
   // (keyfocus on scrollbars causes action without mousePress
   boolean handledEvent;

   public void mousePressed(MouseEvent e)          {
     if(!scrollbar.isEnabled()) { return; }
       // not an unmodified left mouse button
       //if(e.getModifiers() != InputEvent.BUTTON1_MASK) {return; }
       if( ! SwingUtilities.isLeftMouseButton(e)) { return; }
       int direction = (e.getSource() == incrButton) ? 1 : -1;

       scrollByUnit(direction);
       scrollTimer.stop();
       scrollListener.setDirection(direction);
       scrollListener.setScrollByBlock(false);
       scrollTimer.start();

       handledEvent = true;
       if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) {
          scrollbar.requestFocus();
       }
   }

   public void mouseReleased(MouseEvent e)         {
       scrollTimer.stop();
       handledEvent = false;
       scrollbar.setValueIsAdjusting(false);
   }
}
...

Sebagai langkah terakhir, saya ingin tombol btnLeft dan btnRight hanya muncul bila wilayah yang ada tidak cukup. Bila masih ada banyak tempat kosong, maka kedua tombol tersebut tidak perlu muncul. Untuk itu, saya perlu berinteraksi dengan JViewPort yang dimiliki oleh JScrollPane. Class JViewPort mewakili wilayah yang terlihat di JScrollPane. Saya bisa mengerjakan sebuah aksi setiap kali wilayah ini berubah dengan membuat kode program seperti berikut ini:

scrollPane.getViewport().addChangeListener(
   new DisplayButtonChangeListener());

Pada ChangeListener yang ada, saya akan membandingkan lebar JViewPort dengan lebar JToolBar. Bila JViewPort masih cukup menampung seluruh JToolBar, maka saya menyembunyikan btnLeft dan btnRight seperti yang terlihat pada cuplikan kode program berikut ini:

private class DisplayButtonChangeListener implements ChangeListener {

   @Override
   public void stateChanged(ChangeEvent e) {
     JViewport viewport = scrollPane.getViewport();
     boolean buttonVisible = (toolBar.getWidth() > viewport.getWidth());
     btnLeft.setVisible(buttonVisible);
     btnRight.setVisible(buttonVisible);
   }

}

Sampai disini, saya sudah memperoleh sebuah komponen baru sesuai dengan keinginan saya, cukup dengan membuat sebuah class baru. Sifat reusable dan extensible merupakan kelebihan Swing yang membuatnya jauh lebih tangguh dari sekedar komponen visual biasa yang di-drag n drop pada GUI editor.

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: