Posts tagged ‘Swing’
Pengenalan SwingX
Akhirnya liburan semester tiba juga.. Tidak terasa sudah menjadi seorang dosen untuk satu semester.. Mendidik mahasiswa memang tidak mudah, sulit untuk membuat mereka aktif meneliti dan terus mencari pengetahuan dari berbagai sumber. Banyak yang hanya ingin meraih nilai tertinggi saja, sampai melupakan tujuan mereka belajar. Padahal dunia IT adalah dunia yang luas dan terus berkembang, sehingga tidak mungkin hanya mengandalkan orang lain untuk bisa tetap mengikuti perkembangan. Lagipula, waktu yang terbatas selama satu semester, membuat aku tidak sempat mengajarkan topik-topik tambahan. Misalnya, di pemograman Java, aku mengajarkan Swing. Jika bosan dengan Swing, atau menginginkan fitur-fitur tambahan pada Swing, seorang programmer Java bisa menggunakan SwingX, salah satu ‘turunan‘ dari Swing yang dapat di-download terpisah di http://www.swinglabs.org/
Apa kelebihan SwingX? SwingX menawarkan fitur-fitur ekstra yang tidak ditemui di Swing secara siap jadi. Sebagai contoh, ada yang disebut sebagai Highlighter, untuk memberi highlight (bisa berupa warna background berbeda dan sebagainya) pada sel tertentu di JXTable, JXList, JXTree, dan sebagainya. Komponen yang diawali JX kebanyakan adalah turunan dari komponen Swing standar, misalnya JXTable adalah turunan dari JTable. Demikian juga, JXComboBox adalah turunan dari JComboBox. JXComboBox sudah mendukung fitur highlight dengan adanya fungsi addHighlighter(). Jika kita ingin menampilkan nilai dibawah 50 dengan background merah di JXComboBox, kita dapat menggunakan ColorHighlighter, salah satu implementasi dari Highlighter, seperti pada contoh berikut ini:
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import org.jdesktop.swingx.JXComboBox;
import org.jdesktop.swingx.decorator.ColorHighlighter;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.HighlightPredicate;
public class App extends JFrame
{
private JXComboBox cboTest;
public App() {
super("Latihan SwingX");
cboTest = new JXComboBox(new Integer[] {100, 50, 30, 80, 70, 45, 50, 60});
ColorHighlighter colorHighlight = new ColorHighlighter();
colorHighlight.setBackground(Color.RED);
colorHighlight.setForeground(Color.WHITE);
colorHighlight.setHighlightPredicate(new HighlightPredicate() {
@Override
public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
if ((Integer)adapter.getValue() < 50) {
return true;
} else {
return false;
}
}
});
cboTest.addHighlighter(colorHighlight);
setLayout(new FlowLayout());
add(cboTest);
add(new JButton("TEST"));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500,500);
setVisible(true);
}
public static void main( String[] args )
{
new App();
}
}
Hasil dari tampilan program di atas akan terlihat seperti:
Pada program di atas, fungsi setHighlightPredicate() di ColorHighlighter akan memeriksa apakah suatu nilai perlu di-highlight atau tidak (berdasarkan nilai kembalian true atau false).
Selain itu, SwingX juga menawarkan komponen baru, seperti JXDatePicker. Ini adalah contoh komponen yang sering dibutuhkan oleh aplikasi, tetapi tidak disediakan oleh Swing secara langsung. Berikut ini adalah contoh tampilan JXDatePicker:
SwingX juga memiliki kotak dialog siap pakai, seperti JXTipOfTheDay, yang umum dipakai untuk menampilkan tips di program. Berikut ini adalah contoh potongan kode program yang mempergunakan JXTipOfTheDay:
tipOfTheDay = new JXTipOfTheDay();
tipOfTheDay.setModel(new TipOfTheDayModel() {
private Tip[] tips = {
new Tip() {
@Override
public String getTipName() {
return "Tips 1";
}
@Override
public Object getTip() {
return "Tahukah Anda SwingX menyediakan komponen siap jadi?";
}
},
new Tip() {
@Override
public String getTipName() {
return "Tip 2";
}
@Override
public Object getTip() {
return "Anda bisa membuat table dengan highlighter di SwingX";
}
}
};
@Override
public int getTipCount() {
return tips.length;
}
@Override
public Tip getTipAt(int index) {
return tips[index];
}
});
tipOfTheDay.setCurrentTip(1);
tipOfTheDay.showDialog(this);
Contoh tampilan program di atas akan terlihat seperti:
Swing: Membuat Layout Manager Sendiri
Pada saat masih bekerja dulu, saya ditugaskan sebagai programmer di sebuah proyek berbasis Applet (Swing). Dan layaknya sebagai programmer baru, saya banyak membuat tampilan form atau melakukan modifikasi minor pada form yang sudah dibuat programmer lain. Dan itu semua harus aku lakukan melalui coding, tanpa bantuan visual editor. Dapat dibayangkan betapa banyak waktu yang terbuang untuk men-kode GridBagLayout dan memodifikasi kode yang sudah ada. Salah satu solusi yang aku tempuh dikemudian hari adalah dengan membuat generator (masih bukan editor
berbasis template. Tapi aku merasa masih ada yang kurang, pasti ada cara yang lebih baik untuk menghasilkan kode program Swing yang simple dan mudah di-maintain. Dan mungkin saja, jika aku masih bekerja, aku akan mencoba membuat layout manager. Tampilan untuk aplikasi cenderung memiliki pola yang sama dan sederhana, lagipula applet akan jarang di-resize, sehingga mungkin membuat layout manager bisa menjadi sebuah solusi yang baik. Berikut ini adalah contoh sebuah layout manager sederhana:
package latihan;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager2;
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DialogLayout implements LayoutManager2 {
public static enum FunctionalType {
LABEL, INPUT, BUTTON
}
private Map<Component, ComponentInfo> mapComponent;
private int currentLinePosition;
private FunctionalType lastFunctionalType;
public DialogLayout() {
mapComponent = new HashMap<Component,ComponentInfo>();
currentLinePosition = 0;
lastFunctionalType = null;
}
@Override
public void addLayoutComponent(Component comp, Object constraints) {
if (constraints instanceof FunctionalType) {
if (lastFunctionalType==FunctionalType.INPUT) {
currentLinePosition++;
}
ComponentInfo cInfo = new ComponentInfo(currentLinePosition, (FunctionalType) constraints);
mapComponent.put(comp, cInfo);
lastFunctionalType = cInfo.getFunctionalType();
} else {
throw new IllegalArgumentException("Constraints for DialogLayout must be FunctionalType");
}
}
@Override
public float getLayoutAlignmentX(Container target) {
return 0;
}
@Override
public float getLayoutAlignmentY(Container target) {
return 0;
}
@Override
public void invalidateLayout(Container target) {
}
@Override
public Dimension maximumLayoutSize(Container target) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
@Override
public void addLayoutComponent(String name, Component comp) {
}
@Override
public void layoutContainer(Container parent) {
Component[] components = parent.getComponents();
int totalWidth = parent.getWidth() - 15 ;
int totalHeight = parent.getHeight();
int labelLocation = 5;
double labelWidth = 0.2 * totalWidth;
int inputLocation = 5+(int)labelWidth+5;
double inputWidth = 0.8 * totalWidth;
Dimension size = new Dimension();
Point location = new Point();
int lineLocation;
double preferredHeight = 20;
// simpan Component yang berfungsi sebagai BUTTON untuk
// diproses belakangan (sehingga bisa CENTER)
List<Component> lstButtons = new ArrayList<Component>();
int totalButtonsWidth = 0;
int lineButtonsLocation = 0;
for (Component c : components) {
ComponentInfo cInfo = mapComponent.get(c);
lineLocation = (int)(5 + cInfo.getLinePosition() * (preferredHeight+5)) ;
if (cInfo.getFunctionalType()==FunctionalType.LABEL) {
location.setLocation(labelLocation, lineLocation);
size.setSize(labelWidth, preferredHeight);
} else if (cInfo.getFunctionalType()==FunctionalType.INPUT) {
location.setLocation(inputLocation, lineLocation);
size.setSize(inputWidth, preferredHeight);
} else if (cInfo.getFunctionalType()==FunctionalType.BUTTON) {
lstButtons.add(c);
totalButtonsWidth += c.getPreferredSize().getWidth();
lineButtonsLocation = lineLocation;
}
c.setLocation(location);
c.setSize(size);
}
int startLocation = (totalWidth - totalButtonsWidth)/2;
for (Component c : lstButtons) {
c.setSize(c.getPreferredSize());
c.setLocation(startLocation, lineButtonsLocation);
startLocation+=c.getPreferredSize().getWidth()+5;
}
}
@Override
public Dimension minimumLayoutSize(Container parent) {
return new Dimension(500,500);
}
@Override
public Dimension preferredLayoutSize(Container parent) {
return new Dimension(500,500);
}
@Override
public void removeLayoutComponent(Component comp) {
}
protected class ComponentInfo {
private int linePosition;
private FunctionalType functionalType;
public ComponentInfo(int linePosition, FunctionalType type) {
this.linePosition = linePosition;
this.functionalType = type;
}
public int getLinePosition() {
return linePosition;
}
public void setLinePosition(int linePosition) {
this.linePosition = linePosition;
}
public FunctionalType getFunctionalType() {
return functionalType;
}
public void setFunctionalType(FunctionalType functionalType) {
this.functionalType = functionalType;
}
}
}
Untuk membuat tampilan form baru, seseorang hanya perlu memberikan kode seperti:
DialogLayout layout = new DialogLayout();
setLayout(layout);
add(lblNama, LABEL);
add(txtNama, INPUT);
add(lblAlamat, LABEL);
add(txtAlamat, INPUT);
add(lblTelepon, LABEL);
add(txtTelepon, INPUT);
add(btnTambah, BUTTON);
add(btnEdit, BUTTON);
add(btnHapus, BUTTON);
Kode program menjadi sangat sederhana dan mudah ditelurusi. Misalnya, aku bisa langsung tahu bahwa field Telepon akan ditampilkan setelah field Alamat dan sebelum kumpulan button. Selain itu, keuntungan lainnya adalah jika designer UI merasa ingin merubah peletakan dan jarak antar-komponen, programmer hanya perlu mengubah class DialogLayout saja, dan saat program dijalankan semua tampilan akan mengikuti perubahan tersebut.
Swing: Membuat Thread Pekerja Keras Dan Tetap Responsif?
Setelah berulang kali membuat interface dengan Swing, aku cukup sering mendapatkan tulisan “Warning: Swing is not thread safe” di javadoc. Program dengan GUI Swing yang dibuat tanpa memperhatikan threading mungkin akan jalan dengan lancar, tapi ada kemungkinan ia akan bertingkah aneh suatu hari atau menjadi tidak responsif, dan kesalahan-kesalahan seperti ini sulit direproduksi. Dan masih berkaitan dengan thread, aku pernah membuat sebuah tool sederhana berbasis Swing untuk generate data yang butuh waktu cukup lama. Secara insting, yang ada dibenakku adalah membuat thread baru untuk proses yang lama tersebut. Tapi ternyata itu belum cukup, selama proses, tampilan GUI menjadi tidak responsif dan output diagnosa ke text area setiap item selesai diproses tidak muncul seperti yang aku harapkan.
Untuk mengatasi masalah ini, aku perlu mempelajari konsep threading di Swing. Ada tiga jenis thread disini, yaitu initial thread, event dispatch thread, dan worker thread (background thread). Initial thread adalah thread dimana aplikasi bermula, atau thread yang menjalankan method main. Event disptach thread adalah thread yang bertugas untuk menangani event-handling. Seluruh method milik komponen Swing yang tidak thread safe harus dijalankan di thread ini. Oleh sebab itu, umumnya initial thread akan mengerjakan SwingUtilities.invokeLater atau SwingUtilities.invokeAndWait, agar kode yang membentuk GUI (membuat frame, mengisi layout, dsb) berjalan di event dispatch thread. Contohnya adalah seperti:
public static void main (String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
Untuk kode yang melakukan proses jangka panjang, aku harus mengerjakannya di worker thread. Jika dilakukan di event dispatch thread, maka GUI yang menjadi tidak responsif dan tidak sempat lagi menangangi event (karena sibuk mengerjakan proses jangka panjang tadi). Agar proses dikerjakan oleh worker thread, aku dapat menggunakan SwingWorker (baru di Java 6). Berikut ini adalah contohnya:
protected class TaskBerat extends SwingWorker<Void, Integer> {
@Override
protected Void doInBackground() throws Exception {
for (int i=0; i<100; i++) {
publish(i);
Thread.sleep(100);
}
return null;
}
@Override
protected void process(List<Integer> chunks) {
for (Integer i : chunks) {
txtOutput.append("Processing task #" + i + "\n");
}
}
}
Method doInBackground() akan dikerjakan oleh worker thread, sementara method process() akan dikerjakan oleh event dispatch thread. Aku menggunakan publish() di doInBackground() untuk mengirim nilai ke process(). Demi alasan performance, sekali pemanggilan process() mungkin akan melayani lebih dari satu publish(), dimana nilai yang di-publish disimpan dalam sebuah List. Karena process() dijalankan oleh event dispatch thread, aku bebas mengerjakan method milik Swing disini (sebenarnya method append() milik JTextArea termasuk thread-safe).
Satu lagi class yang berhubungan thread di Swing adalah class javax.swing.Timer. Perbedaannya dengan class java.util.Timer adalah Swing Timer dikerjakan oleh event dispatch thread, sehingga aman untuk memakai method milik komponen Swing di Swing Timer. Berikut ini contoh program yang menampilkan waktu saat ini:
public class LatihanTimer extends JFrame implements ActionListener {
private JLabel lblWaktu;
public LatihanTimer() {
super("Latihan Timer");
lblWaktu = new JLabel("[WAKTU]");
add(lblWaktu);
pack();
setVisible(true);
Timer timer = new Timer(1000, this);
timer.start();
}
public static void main (String args[]) {
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run() {
new LatihanTimer();
}
});
}
@Override
public void actionPerformed(ActionEvent e) {
lblWaktu.setText(DateFormat.getTimeInstance().format(new Date()));
}
}
Swing: Document dan View Untuk JEditorPane
Perancangan komponen Swing menerapkan pemisahan antara Model dan View. Sebagai contoh, lihat saja JEditorPane (dan turunannya, JTextPane). JEditorPane adalah komponen yang mirip seperti JTextArea, hanya saja ia bisa menampilkan tulisan yang diformat (tebal, miring, font size berbeda, dsb). Pada komponen ini, Model-nya adalah sebuah Document, yang dapat berupa PlainDocument, DefaultStyledDocument dan HTMLDocument.
Sebagai contoh, aku menyiapkan JTextPane dengan kode berikut:
HTMLEditorKit editor = new HTMLEditorKit(); HTMLDocument document = (HTMLDocument) editor.createDefaultDocument(); textPane = new JTextPane(); textPane.setEditorKit(editor); textPane.setDocument(document);
HTMLDocument adalah dokumen yang mewakili data HTML pada JTextPane. HTMLEditorKit memiliki fungsi-fungsi yang mempermudah pengolahan HTML, misalnya fungsi untuk menerjemahkan HTMLDocument ke dalam kode-kode HTML dan menyimpannya ke file HTML. Jika perancangan komponen Swing menerapkan pola MVC, mungkin HTMLEditorKit masuk ke dalam controller.
Untuk melakukan modifikasi pada isi JTextPane di kode di atas, aku hanya perlu berinteraksi dengan document. Misalnya, jika aku ingin membuat tulisan yang di-select saat ini menjadi tebal (bold), aku dapat menggunakan kode seperti berikut:
int start = textPane.getSelectionStart();
int end = textPane.getSelectionEnd();
if (start>0) {
MutableAttributeSet attr = new SimpleAttributeSet();
StyleConstants.setBold(attr, true);
document.setCharacterAttributes(start, end-start, attr, false);
}
Jika pada JTextPane terdapat tulisan: “Namaku Solid Snake“, maka hasil output html-nya dari perintah editor.write(new FileOutputStream("c:\\test.html"), document, 0, document.getLength()) adalah:
<html>
<head>
</head>
<body>
<p style="margin-top: 0">
Namaku <b>Solid Snake</b>
</p>
</body>
</html>
Untuk menghasilkan tag HTML sesuai selera, aku dapat meng-extends HTMLEditorKit dan men-override method write. Demikian juga jika aku ingin mengendalikan bagaimana sebuah tag di-render di JTextPane, aku dapat men-override method getViewFactory() milik HTMLEditorKit dan membuat ViewFactory sendiri. Sebagai contoh, jika aku ingin setiap tag PRE ditampilkan di JTextPane dengan background abu-abu (tanpa menambahkan CSS ke tag), aku dapat membuat ViewFactory seperti berikut:
public class LatihanViewFactory extends HTMLEditorKit.HTMLFactory {
@Override
public View create(Element elem) {
Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
if (o==HTML.Tag.PRE) {
return new PreBlockView(elem);
}
return super.create(elem);
}
class PreBlockView extends BlockView {
public PreBlockView(Element elem) {
super(elem, View.Y_AXIS);
}
@Override
public void paint(Graphics g, Shape allocation) {
Rectangle batas = (Rectangle) allocation;
g.setColor(new Color(210,210,210));
g.fillRect((int)batas.getX(), (int)batas.getY(),
(int)batas.getWidth(), (int)batas.getHeight());
super.paint(g, allocation);
}
}
}
Agar LatihanViewFactory bisa dipakai, aku juga membuat versi sendiri dari HTMLEditorKit, seperti pada contoh berikut:
public class LatihanEditorKit extends HTMLEditorKit {
private final static LatihanViewFactory
latihanViewFactory = new LatihanViewFactory();
@Override
public ViewFactory getViewFactory() {
return latihanViewFactory;
}
}
Sebagai langkah terakhir, aku tinggal merubah kode program agar JTextPane tidak lagi memakai HTMLEditorKit melainkan LatihanEditorKit:
private LatihanEditorKit latihanEditorKit = new LatihanEditorKit(); private JTextPane textPane = new JTextPane(); textPane.setEditorKit(latihanEditorKit);


