Category Archives: JavaSwing

Java Swing – Drag và Drop

Trong lập trình GUI thì Drag và Drop (tiếng việt là Kéo Thả) là hành động click và giữ chuột lên một đối tượng rồi “kéo” đối tượng đó lên một vị trí khác hoặc lên một đối tượng khác.

Kéo thả là một trong những tính năng thường thấy trong GUI, cho phép người dùng thực hiện các công việc phức tạp.

Chúng ta có thể kéo thả dữ liệu hoặc các đối tượng có hình thù cụ thể, ví dụ như chúng ta kéo một file ảnh vào cửa sổ chat để gửi file thì đó là kéo dữ liệu, hoặc kéo các tab trong trình duyệt Chrome là kéo đối tượng có hình thù.

Hầu hết các component trong Java Swing đều có phương thức hỗ trợ kéo thả, và chúng ta cũng có thể viết các phương thức kéo thả của riêng chúng ta.

Ví dụ

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.TransferHandler;


public class Example extends JFrame {

    JTextField field;
    JButton button;

    public Example() {

        setTitle("Drag And Drop Example");

        setLayout(null);

        button = new JButton("Button");
        button.setBounds(200, 50, 90, 25);

        field = new JTextField();
        field.setBounds(30, 50, 150, 25);

        add(button);
        add(field);

        field.setDragEnabled(true);
        button.setTransferHandler(new TransferHandler("text"));

        setSize(330, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main(String[] args) {
        new Example();
    }
}

Chúng ta hiển thị một JTextField và một JButton, đoạn text trong text field có thể kéo sang làm text cho button.

field.setDragEnabled(true);

Để dữ liệu của một component có thể kéo được thì chúng ta phải thiết lập trong phương thức setDragEnabled() vì mặc định Swing đã vô hiệu hóa tính năng này.

button.setTransferHandler(new TransferHandler("text"));

Lớp TransferHandler là trái tim của Drag và Drop trong Swing, lớp này có nhiệm vụ vận chuyển dữ liệu qua lại giữa các component, tham số của lớp này là một thuộc tính Bean, nếu bạn chưa biết gì về các thuộc tính bean thì chỉ cần nhớ rằng bất kì thuộc tính nào có phương thức getter  setter đều có thể đưa vào làm tham số cho TransferHandler.

Chẳng hạn như JButton có phương thức getText()setText(), do đó bạn có thể đưa tham số là "text", vì khi chúng ta thiết lập kiểu dữ liệu là "text", TransferHandler sẽ dùng các phương thức getter  setter tương ứng để lấy và nhập dữ liệu với các component. Và vì chúng ta thiết lập kiểu text nên bạn chỉ có thể kéo hoặc thả các chuỗi text vào ra component này thôi.

Capture

Tùy chỉnh khả năng kéo

Không phải tất cả các component trong Swing đều có thể kéo được, JLabel là một ví dụ, chúng ta phải code phương thức kéo riêng. Ở đây chúng ta sẽ thực hiện kéo thả thuộc tính icon.

import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.TransferHandler;


public class Example extends JFrame {


    public Example() {

        setTitle("Drag And Drop Example");

        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 50, 15));

        ImageIcon icon1 = new ImageIcon("C:/sad.png");
        ImageIcon icon2 = new ImageIcon("C:/smile.png");
        ImageIcon icon3 = new ImageIcon("C:/crying.png");

        JButton button = new JButton(icon2);
        button.setFocusable(false);

        JLabel label1 = new JLabel(icon1, JLabel.CENTER);
        JLabel label2 = new JLabel(icon3, JLabel.CENTER);

        MouseListener listener = new DragMouseAdapter();
        label1.addMouseListener(listener);
        label2.addMouseListener(listener);

        label1.setTransferHandler(new TransferHandler("icon"));
        button.setTransferHandler(new TransferHandler("icon"));
        label2.setTransferHandler(new TransferHandler("icon"));

        panel.add(label1);
        panel.add(button);
        panel.add(label2);
        add(panel);

        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    class DragMouseAdapter extends MouseAdapter {
        public void mousePressed(MouseEvent e) {
            JComponent c = (JComponent) e.getSource();
            TransferHandler handler = c.getTransferHandler();
            handler.exportAsDrag(c, e, TransferHandler.COPY);
        }
    }

    public static void main(String[] args) {
        new Example();
    }
}

Chúng ta hiển thị 2 label và một button, cả 3 component này đều hiển thị icon, chúng ta có thể kéo icon từ 2 label vào làm icon cho button.

MouseListener listener = new DragMouseAdapter();
label1.addMouseListener(listener);
label2.addMouseListener(listener);

Như đã nói, JLabel không được hỗ trợ tính năng kéo, hay nói cách khác là không có phương thức setDragEnabled(), nên ở đây chúng ta tự gắn một MouseAdapter vào để mô phỏng sự kiện kéo.

label1.setTransferHandler(new TransferHandler("icon"));
button.setTransferHandler(new TransferHandler("icon"));
label2.setTransferHandler(new TransferHandler("icon"));

Cả 3 component của chúng ta đều có các phương thức getter/setter cho thuộc tính icon. Đối với các lớp có sẵn phương thức hỗ trợ kéo là setDragEnabled() thì bạn có thể không cần phải chỉ ra kiểu dữ liệu vận chuyển trong TransferHandler và Swing sẽ vận chuyển các dữ liệu mặc định (chẳng hạn như text đối với JTextField), còn với các lớp không có sẵn thì chúng ta phải chỉ ra kiểu dữ liệu rõ ràng trong phương thức setTransferHandler().

JComponent c = (JComponent) e.getSource();
TransferHandler handler = c.getTransferHandler();
handler.exportAsDrag(c, e, TransferHandler.COPY);

Ba dòng code trên thiết lập khả năng kéo cho JLabel bằng phương thức exportAsDrag(), phương thức này nhận vào đối tượng được kéo đi (e.getSource()), dữ liệu về sự kiện kéo chuột (e) và cách mà dữ liệu được, ở đây là TransferHandler.COPY, tức là dữ liệu từ component nguồn sẽ được copy sang đối tượng đích. Ngoài copy thì chúng ta còn có một số thao tác khác như MOVE, NONE, MOVE_OR_COPY, COPY, LINK.

Capture12

Tùy chỉnh khả năng thả

Nếu có một số component không có sẵn phương thức hỗ trợ kéo thì cũng có một số component không có phương thức hỗ trợ thả, JList là một ví dụ. Lý do là bởi vì khi chúng ta kéo thả vào JList thì Swing không biết chúng ta muốn thả vào như thế nào, vd như insert vào đầu list, cuối list hay giữa list hay ở vị trí bất kì nào…

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;

import javax.swing.DefaultListModel;
import javax.swing.DropMode;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;


public class Example extends JFrame {

    JTextField field;
    DefaultListModel model;

    public Example() {

        setTitle("Drag And Drop Example");

        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 15));
  
        JScrollPane pane = new JScrollPane();
        pane.setPreferredSize(new Dimension(180, 150));

        model = new DefaultListModel();
        JList list = new JList(model);

        list.setDropMode(DropMode.INSERT);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setTransferHandler(new ListHandler());

        field = new JTextField("");
        field.setPreferredSize(new Dimension(150, 25));
        field.setDragEnabled(true);

        panel.add(field);
        pane.getViewport().add(list); 
        panel.add(pane);

        add(panel);

        pack();

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
    }


    private class ListHandler extends TransferHandler {
        public boolean canImport(TransferSupport support) {
            if (!support.isDrop()) {
                return false;
            }

            return support.isDataFlavorSupported(DataFlavor.stringFlavor);
        }

        public boolean importData(TransferSupport support) {
            if (!canImport(support)) {
                return false;
            }

            Transferable transferable = support.getTransferable();
            String line;
            try {
                line = (String) transferable.getTransferData(DataFlavor.stringFlavor);
            } catch (Exception e) {
                return false;
            }

            JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
            int index = dl.getIndex();

            String[] data = line.split(",");
            for (String item: data) {
                if (!item.isEmpty())
                    model.add(index++, item.trim());
            }
            return true;
       }
    }

    public static void main(String[] args) {
        new Example();
    }
}

Chúng ta sử dụng một JTextField và một JList, các đoạn text trong JTextField có thể được kéo qua JList, đoạn text sẽ dược phân ra thành nhiều text con bằng dấu phẩy.

list.setDropMode(DropMode.INSERT);

Phương thức setDropMode() thiết lập cách dữ liệu được thả vào, ở đây là DropMode.INSERT, tức là chèn vào cuối list.

list.setTransferHandler(new ListHandler());

Mặc dù chúng ta chỉ kéo thả các đoạn text bình thường từ JTextField sang nhưng vì JList không có các phương thức getter/setter tương ứng nên chúng ta phải định nghĩa một lớp TransferHandler riêng là ListHandler.

public boolean canImport(TransferSupport support) {
    if (!support.isDrop()) {
        return false;
    }
    return support.isDataFlavorSupported(DataFlavor.stringFlavor);
}

Khi chúng ta kéo dữ liệu sang một component (chỉ kéo thôi chứ chưa thả), phương thức canImport() của đối tượng TransferHandler đó sẽ được gọi liên tục, phương thức này nhận một đối tượng TransferSupport do Swing tự tạo ra, phương thức này sẽ kiểm tra xem dữ liệu được chuyển sang có được nhận hay không. Chúng ta override lại phương thức này trong lớp ListHandler.

Ở đây chúng ta dùng phương thức isDrop() kiểm tra xem người dùng đã thả dữ liệu ra chưa hay vẫn còn giữ chuột để tiếp tục kéo. Phương thức isDataFlavorSupported() kiểm tra xem dữ liệu được chuyển sang có được hỗ trợ hay không, phương thức này nhận vào một đối tượng lớp DataFlavor, lớp này lưu trữ thông tin về các loại dữ liệu khác nhau, ở đây DataFlavor.stringFlavor lưu thông tin về kiểu string, ngoài ra còn một số kiểu khác như imageFlavor, allHtmlFlavor… bạn có thể tìm hiểu thêm tại đây.

public boolean importData(TransferSupport support) {
...
}

Phương thức importData() trong lớp TransferHandler sẽ được gọi khi người dùng thả chuột ra, tức là thả dữ liệu lên component, phương thức này nhận một đối tượng TransferSupport do Swing tự tạo ra, chúng ta cũng override lại phương thức này.

Transferable transferable = support.getTransferable();

Bên trong đối tượng TransferSupport có chứa một đối tượng Transferable, đây là đối tượng chứa thông tin về dữ liệu được vận chuyển.

line = (String) transferable.getTransferData(DataFlavor.stringFlavor);

Để lấy dữ liệu text thì chúng ta dùng phương thức getTransferData() và đưa vào tham số DataFlavor tương ứng.

JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
int index = dl.getIndex();

Bên trong đối tượng TranferSupport còn chứa một đối tượng TransferHandler.DropLocation, chúng ta lấy ra từ phương thức getDropLocation(), lớp này chứa thông tin về tọa độ chuột mà dữ liệu được thả.

Lớp JList cũng có một lớp DropLocation riêng kế thừa từ lớp này, JList.DropLocation sẽ dựa vào tọa độ được thả ra mà tính vị trí chỉ số trong danh sách các item, chúng ta lấy chỉ số này thông qua phương thức getIndex().

String[] data = line.split(",");
for (String item: data) {
    if (!item.isEmpty())
        model.add(index++, item.trim());
}

Cuối cùng chúng ta phân tích đoạn text được gửi sang rồi insert vào JList.

Capture-13

Java Swing – Mô hình MVC

Trong phần này chúng ta sẽ tìm hiểu về mô hình mà các component trong Java Swing sử dụng.

Bộ thư viện Swing được thiết kế dựa theo mô hình MVC (Model View Controller) cho phép thao tác với dữ liệu một cách tiện lợi và nhanh chóng.

Mô hình MVC

Mô hình MVC mà chúng ta thường thấy chia ứng dụng làm 3 phần: mô hình (Model), View và Controller. Trong đó model đại diện cho dữ liệu trong ứng dụng, view làm nhiệm vụ định dạng hiển thị cho dữ liệu và cuối cùng Controller xử lý sự kiện, có thể là sự kiện từ người dùng hoặc cũng chính nó tự sinh ra sự kiện. Nguyên gốc của mô hình MVC xuất phát từ ý tưởng chia tầng dữ liệu và tầng giao diện bằng một tầng ở giữa là Controller.

Thư viện Swing thì khác một tí, cả 2 phần View và Controller được gom lại làm một và được đặt tên là UI.

Trong Swing, tất cả mọi component đều có mô hình (model) của riêng nó, các mô hình lại được chia làm 2 loại là mô hình trạng thái và mô hình dữ liệu. Mô hình trạng thái lưu trữ trạng thái của component, ví dụ mô hình lưu giữ trạng thái của một button là đang được click hay đang được thả. Mô hình dữ liệu lưu trữ dữ liệu của component, chẳng hạn như một label lưu dữ liệu về text mà nó đang hiển thị.

Bản thân các component trong Swing đều được kế thừa từ một lớp model của riêng chúng, chẳng hạn như JButton kế thừa từ JAbstractButton, bên trong JAbstractButton có một thuộc tính là một đối tượng ButtonModel (bạn có thể tìm đọc trong bộ source của Java Swing), đối tượng này cung cấp các phương thức để lấy dữ liệu, thường thì nếu chúng ta muốn lấy dữ liệu thì chúng ta phải lấy đối tượng model này, nhưng lớp JButton thì lại có các phương thức lấy dữ liệu trực tiếp từ model luôn, nên bạn có thể lấy dữ liệu trực tiếp từ JButton mà không cần phải lấy đối tượng model trước.

public int getValue() { 
    return getModel().getValue(); 
}

Ví dụ như phương thức getValue() ở trên (đây là đoạn code phương thức getValue() của một component trong mã nguồn Java Swing), chúng ta lấy dữ liệu trực tiếp luôn chứ không cần phải lấy một đối tượng component nào đó rồi mới lấy dữ liệu. Hơn nữa việc thao tác trực tiếp với component là không an toàn.

Khi một component được tạo thì mặc định một đối tượng model sẽ được tạo, chẳng hạn như với JButton thì lớp đại diện cho model của nó là DefaultButtonModel.

public JButton(String text, Icon icon) {
  // Create the model
  setModel(new DefaultButtonModel());

  // initialize
  init(text, icon);
}

Ở trên là đoạn code khởi tạo JButton trong bộ mã nguồn của Java Swing.

Model của JButton

Mô hình được tạo sẵn cho JButton còn được dùng cho một số component phổ biến khác như check box, radio box, menu item… Loại mô hình của các component này là mô hình trạng thái.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.DefaultButtonModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ButtonModel extends JFrame {
 
    private JButton okbtn;
    private JLabel enabledLbl;
    private JLabel pressedLbl;
    private JCheckBox cb;
 
    public ButtonModel()
    {
        initUI();
    }
 
    private void initUI()
    {
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);

        okbtn = new JButton("OK");
        okbtn.addChangeListener(new DisabledChangeListener());
        cb = new JCheckBox();
        cb.setAction(new CheckBoxAction());

        enabledLbl = new JLabel("Enabled: true");
        pressedLbl = new JLabel("Pressed: false");
 
        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
            .addGroup(gl.createSequentialGroup()
                .addComponent(okbtn)
                .addGap(80)
                .addComponent(cb))
            .addGroup(gl.createParallelGroup()
                .addComponent(enabledLbl)
                .addComponent(pressedLbl)) 
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
            .addGroup(gl.createParallelGroup()
                .addComponent(okbtn)
                .addComponent(cb))
                .addGap(40)
            .addGroup(gl.createSequentialGroup()
                .addComponent(enabledLbl)
                .addComponent(pressedLbl))
        );

        pack();

        setTitle("ButtonModel");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
 
    private class DisabledChangeListener implements ChangeListener {

        @Override
        public void stateChanged(ChangeEvent e) {

            DefaultButtonModel model = (DefaultButtonModel) okbtn.getModel();

            if (model.isEnabled()) {
                enabledLbl.setText("Enabled: true");
            } else {
                enabledLbl.setText("Enabled: false");
            }

            if (model.isPressed()) {
                pressedLbl.setText("Pressed: true");
            } else { 
                pressedLbl.setText("Pressed: false");
            }
        }
    }
 
    private class CheckBoxAction extends AbstractAction {
 
        public CheckBoxAction() {
           super("Disabled");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (okbtn.isEnabled()) {
                okbtn.setEnabled(false);
            } else {
                okbtn.setEnabled(true);
            }
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ButtonModel ex = new ButtonModel();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một button, một checkbox và 3 label, các label này hiển thị dữ liệu về trạng thái của button như có đang được click hay không, đang bật hay đang bị vô hiệu hóa…

okbtn.addChangeListener(new DisabledChangeListener());

Chúng ta dùng lớp ChangeListener để lắng nghe sự thay đổi trạng thái của button.

DefaultButtonModel model = (DefaultButtonModel) okbtn.getModel();

Để lấy đối tượng model thì chúng ta dùng phương thức getModel().

if (model.isEnabled()) {
    enabledLbl.setText("Enabled: true");
} else {
    enabledLbl.setText("Enabled: false");
}

Chúng ta dùng phương thức isEnabled() để biết button đang dùng được hay đang bị vô hiệu hóa và cập nhật lên label.

if (okbtn.isEnabled()) {
    okbtn.setEnabled(false);
} else {
    okbtn.setEnabled(true);
}

Check box sẽ bật/tắt button nhưng ở đây chúng ta không dùng model mà dùng phương thức setEnabled() để thay đổi trực tiếp luôn.

public void setEnabled(boolean b) {
    if (!b && model.isRollover()) {
        model.setRollover(false);
    } 
    super.setEnabled(b);
    model.setEnabled(b);
}

Đoạn code trên là mã nguồn của phương thức JCheckBox.setEnable(), như bạn thấy thực chất khi gọi phương thức này JCheckBox tự động lấy model của nó và thiết lập thông tin từ model đó.

Capture

Tùy chỉnh Model

Trong phần này chúng ta làm lại những gì đã làm ở trên, chỉ khác là chúng ta sẽ sử dụng model do chúng ta tự thiết kế.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;


public class ButtonModel2 extends JFrame {

    private JButton okbtn;
    private JLabel enabledLbl;
    private JLabel pressedLbl;
    private JCheckBox cb;

    public ButtonModel2() {

        initUI();
    }
    
    private void initUI() {
        
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);       
        
        okbtn = new JButton("OK");
        cb = new JCheckBox();
        cb.setAction(new CheckBoxAction());

        enabledLbl = new JLabel("Enabled: true");
        pressedLbl = new JLabel("Pressed: false");

        OkButtonModel model = new OkButtonModel();
        okbtn.setModel(model);        

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addGroup(gl.createSequentialGroup()
                        .addComponent(okbtn)
                        .addGap(80)
                        .addComponent(cb))
                .addGroup(gl.createParallelGroup()
                        .addComponent(enabledLbl)
                        .addComponent(pressedLbl))
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(okbtn)
                        .addComponent(cb))
                .addGap(40)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(enabledLbl)
                        .addComponent(pressedLbl))
        );

        pack();

        setTitle("MVC");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
    private class OkButtonModel extends DefaultButtonModel {

        @Override
        public void setEnabled(boolean b) {
            if (b) {
                enabledLbl.setText("Enabled: true");
            } else {
                enabledLbl.setText("Enabled: false");
            }

            super.setEnabled(b);
        }

        @Override
        public void setPressed(boolean b) {
            if (b) {
                pressedLbl.setText("Pressed: true");
            } else {
                pressedLbl.setText("Pressed: false");
            }

            super.setPressed(b);
        }
    }
    
    private class CheckBoxAction extends AbstractAction {
        
        public CheckBoxAction() {
            super("Disabled");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (okbtn.isEnabled()) {
                okbtn.setEnabled(false);
            } else {
                okbtn.setEnabled(true);
            }
        }
    }    

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ButtonModel2 ex = new ButtonModel2();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta thiết kế lại giao diện như trong ví dụ trước, chỉ khác là chúng ta không dùng đến các listener nữa mà sử dụng đối tượng model kế thừa do chúng ta tự thiết kế.

ButtonModel model = new OkButtonModel();
okbtn.setModel(model); 

Để thiết lập model thì chúng ta sử dụng phương thức setModel().

private class OkButtonModel extends DefaultButtonModel {
...
}

Chúng ta tạo model button riêng và override một số phương thức cần thiết.

@Override
public void setEnabled(boolean b) {
    if (b) {
        enabledLbl.setText("Enabled: true");
    } else {
        enabledLbl.setText("Enabled: false");
    }

    super.setEnabled(b);
}

Chúng ta override phương thức setEnabled() và cập nhật lại text trên label, sau đó chúng ta phải gọi phương thức setEnabled() của lớp cha để cập nhật sự thay đổi.

Component có 2 model

Có một số component có tới 2 model, JList là một ví dụ, 2 model của JListListModel à ListSelectionModel. Trong đó ListModel là một model dữ liệu, model này lưu trữ danh sách các item trong list, cũng như tính chất của các item đó, ListSelectionModel là một model trạng thái, lưu trữ trạng thái của các item, chẳng hạn như item thứ mấy đang được chọn bởi người dùng…

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;

public class ListModels extends JFrame {

    private DefaultListModel model;
    private JList list;
    private JButton remallbtn;
    private JButton addbtn;
    private JButton renbtn;
    private JButton delbtn;

    public ListModels() {

        initUI();
    }

    private void createList() {

        model = new DefaultListModel();
        model.addElement("Amelie");
        model.addElement("Aguirre, der Zorn Gottes");
        model.addElement("Fargo");
        model.addElement("Exorcist");
        model.addElement("Schindler's list");

        list = new JList(model);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        list.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {

                if (e.getClickCount() == 2) {
                    int index = list.locationToIndex(e.getPoint());
                    Object item = model.getElementAt(index);
                    String text = JOptionPane.showInputDialog("Rename item", item);
                    String newitem = null;
                    if (text != null) {
                        newitem = text.trim();
                    } else {
                        return;
                    }

                    if (!newitem.isEmpty()) {
                        model.remove(index);
                        model.add(index, newitem);
                        ListSelectionModel selmodel = list.getSelectionModel();
                        selmodel.setLeadSelectionIndex(index);
                    }
                }
            }
        });
    }

    private void createButtons() {

        remallbtn = new JButton("Remove All");
        addbtn = new JButton("Add");
        renbtn = new JButton("Rename");
        delbtn = new JButton("Delete");

        addbtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                String text = JOptionPane.showInputDialog("Add a new item");
                String item = null;

                if (text != null) {
                    item = text.trim();
                } else {
                    return;
                }

                if (!item.isEmpty()) {
                    model.addElement(item);
                }
            }
        });

        delbtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent event) {

                ListSelectionModel selmodel = list.getSelectionModel();
                int index = selmodel.getMinSelectionIndex();
                if (index >= 0) {
                    model.remove(index);
                }
            }

        });

        renbtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                ListSelectionModel selmodel = list.getSelectionModel();
                int index = selmodel.getMinSelectionIndex();
                if (index == -1) {
                    return;
                }

                Object item = model.getElementAt(index);
                String text = JOptionPane.showInputDialog("Rename item", item);
                String newitem = null;

                if (text != null) {
                    newitem = text.trim();
                } else {
                    return;
                }

                if (!newitem.isEmpty()) {
                    model.remove(index);
                    model.add(index, newitem);
                }
            }
        });

        remallbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                model.clear();
            }
        });

    }

    private void initUI() {

        createList();
        createButtons();
        JScrollPane scrollpane = new JScrollPane(list);

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(scrollpane)
                .addGroup(gl.createParallelGroup()
                        .addComponent(addbtn)
                        .addComponent(renbtn)
                        .addComponent(delbtn)
                        .addComponent(remallbtn))
        );

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
                .addComponent(scrollpane)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(addbtn)
                        .addComponent(renbtn)
                        .addComponent(delbtn)
                        .addComponent(remallbtn))
        );

        gl.linkSize(addbtn, renbtn, delbtn, remallbtn);

        pack();

        setTitle("JList models");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ListModels ex = new ListModels();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ này, chúng ta hiển thị một JList và 4 button. Các button sẽ điều khiển JList.

model = new DefaultListModel();
model.addElement("Amelie");
model.addElement("Aguirre, der Zorn Gottes");
...

Đầu tiên chúng ta tạo một model cho JList và thêm các item vào đó.

list = new JList(model);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

Tiếp theo chúng ta tạo một đối tượng JList và thiết lập chế độ chỉ cho phép chọn một item tại một thời điểm.

if (text != null) {
    item = text.trim();
} else {
    return;
}

if (!item.isEmpty()) {
    model.addElement(item);
}

Chúng ta chỉ thêm item vào list nếu item đó không rỗng.

ListSelectionModel selmodel = list.getSelectionModel();
int index = selmodel.getMinSelectionIndex();
if (index >= 0) {
    model.remove(index);
}

Đoạn code trên thực hiện xóa các item, đầu tiên chúng ta lấy ra số thứ tự của item đang được chọn bằng phương thức getMinSelectionIndex() từ model của JList. Sau đó dùng phương thức remove() để xóa item này.

Trong ví dụ này mình dùng cả 2 model và dùng cả 2 phương pháp là sử dụng model và dùng phương thức trực tiếp của component. Các phương thức add(), remove(), clear() là dùng trực tiếp phương thức của component, còn lấy item đang được chọn là lấy từ model.

Capture

Java Swing – Hộp Thoại

Trong phần này chúng ta sẽ tìm hiểu cách sử dụng một số hộp thoại cơ bản là MessageBox, JFileChooser, JColorChooser.

Hộp thoại (Dialog) có 2 loại là modal (trạng thái) và modeless (vô trạng thái), hộp thoại modal khi hiện ra sẽ không cho phép người dùng tương tác với cửa sổ cha của nó, còn hộp thoại modeless thì cho phép.

Message Dialog

MessageBox dùng để hiển thị thông báo cho người dùng.

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class MessageDialogsEx extends JFrame {
    
    private JPanel panel;

    public MessageDialogsEx() {

        initUI();
    }

    private void initUI() {
        
        panel = (JPanel) getContentPane();
 
        JMenuBar menubar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
 
        JMenuItem about = new JMenuItem("About");
        about.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                JOptionPane.showMessageDialog(
                    panel, 
                    "This is a modal MessageBox", 
                    "About", 
                    JOptionPane.INFORMATION_MESSAGE);
            }
        });
         
        fileMenu.add(about);
      
        menubar.add(fileMenu);
        setJMenuBar(menubar);
 
        setTitle("Dialog Example");
        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MessageDialogsEx md = new MessageDialogsEx();
                md.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một menu có item là About, click vào item này thì hiển thị một messagebox.

JOptionPane.showMessageDialog(panel, "This is a modal MessageBox", "About", 
                              JOptionPane.INFORMATION_MESSAGE);

Để hiển thị MessageBox thì chúng ta gọi phương thức JOptionPane.showMessageBox(), JOptionPane là một lớp tĩnh nên bạn có thể gọi nó bất kỳ lúc nào mà không cần phải tạo một đối tượng mới. Tham số gồm có đối tượng cửa sổ cha hoặc panel cha, text, title, và kiểu hộp thoại. Kiểu hộp thoại có 4 kiểu sau đây:

  • ERROR_MESSAGE
  • WARNING_MESSAGE
  • QUESTION_MESSAGE
  • INFORMATION_MESSAGE

Capture

Confirm Dialog

Đây là kiểu hộp thoại cho bạn 2 lựa chọn để trả lời cho một câu hỏi nào đó.

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
class ConfirmDialogEx extends JFrame{

    private JPanel panel;
    public ConfirmDialogEx() {        
        initUI();
    }

    private void initUI() {

        panel = (JPanel) getContentPane();
 
        JMenuBar menubar = new JMenuBar();
        JMenu fileMenu = new JMenu("File"); 
 
        JMenuItem exit = new JMenuItem("Exit");
        exit.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                int n = JOptionPane.showConfirmDialog(
                            panel, 
                            "Are you sure you want to quit?", 
                            "Alert", 
                            JOptionPane.YES_NO_OPTION);
                if(n == JOptionPane.YES_OPTION)
                    System.exit(0);
            }
        });
 
        fileMenu.add(exit);
 
        menubar.add(fileMenu);
        setJMenuBar(menubar);
  
        setTitle("Dialog Example");
        setSize(400, 300);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }   

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JDialogEx sd = new JDialogEx();
                sd.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một menu có một item là exit, click vào item này thì hộp thoại sẽ hiện ra để xác nhận.

int n = JOptionPane.showConfirmDialog(
            panel, 
            "Are you sure you want to quit?", 
            "Alert", 
            JOptionPane.YES_NO_OPTION);
if(n == JOptionPane.YES_OPTION)
    System.exit(0);

Chúng ta dùng phương thức showConfirmDialog() với kiểu hộp thoại là JOptionPane.YES_NO_QUESTION. Phương thức này sẽ trả về một giá trị int khi người dùng click vào một trong hai nút Yes hoặc No tương ứng.

Capture

JFileChooser

JFileChooser là hộp thoại cho phép bạn chọn file.

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
import javax.swing.filechooser.FileNameExtensionFilter;

public class FileChooserEx extends JFrame {

    private JPanel panel;
    private JLabel path;

    public FileChooserEx() {

        initUI();
    }

    private void initUI() {

        panel = new JPanel();
        panel.setAlignmentX(Component.LEFT_ALIGNMENT);
        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
 
        JButton openFileButton = new JButton("Open File"); 
        openFileButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                JFileChooser fc = new JFileChooser();
                fc.addChoosableFileFilter(new FileNameExtensionFilter(
                    "Image (jpg, jpeg, png, bmp, gif)", 
                    "jpg", "jpeg", "png", "bmp", "gif"
                ));
                int n = fc.showOpenDialog(panel);
 
                if(n == JFileChooser.APPROVE_OPTION)
                    path.setText(fc.getSelectedFile().getAbsolutePath());
            }
        });
 
        JButton openFolderButton = new JButton("Open Folder"); 
        openFolderButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                JFileChooser fc = new JFileChooser();
 
                int n = fc.showOpenDialog(panel);
                fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                if(n == JFileChooser.APPROVE_OPTION)
                    path.setText(fc.getSelectedFile().getAbsolutePath());
            }
        });
 
        panel.add(Box.createRigidArea(new Dimension(5, 5)));
        panel.add(openFileButton);
        panel.add(Box.createRigidArea(new Dimension(5, 0)));
        panel.add(openFolderButton);
 
        add(panel);
 
        path = new JLabel("Path:"); 
        path.setPreferredSize(new Dimension(-1, 22));
        path.setBorder(LineBorder.createGrayLineBorder());            
        add(path, BorderLayout.SOUTH);
 
        setTitle("Dialog Example");
        setSize(300, 100);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
   
    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                FileChooserEx fcd = new FileChooserEx();
                fcd.setVisible(true);
            }
        });
    }
}

Chúng ta tạo 2 button và một label, một button mở hộp thoại chọn file, một button mở hộp thoại chọn thư mục, đường dẫn đến file và thư mục được chọn sẽ hiển thị trên label.

JFileChooser fc = new JFileChooser();

Khác với 2 messagebox trên, ở đây bạn phải tạo một đối tượng JFileChooser riêng.

fc.addChoosableFileFilter(new FileNameExtensionFilter(
    "Image (jpg, jpeg, png, bmp, gif)", 
    "jpg", "jpeg", "png", "bmp", "gif"
));

Bạn có thể thêm các bộ lọc file theo phần mở rộng của tên file với phương thức addChoosableFileFilter()  và tham số là một đối tượng FileNameExtensionFilter.

int n = fc.showOpenDialog(panel);

Để hiển thị hộp thoại chọn file thì chúng ta gọi phương thức showOpenDialog().

if(n == JFileChooser.APPROVE_OPTION)
    path.setText(fc.getSelectedFile().getAbsolutePath());

Nếu có file được chọn thì phương thức này sẽ trả về một giá trị int và bạn có thể lấy đường dẫn đến file được chọn đó.

fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

Ngoài ra bạn cũng có thể chỉ định là chỉ mở thư mục với phương thức setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY).

Untitled

JColorChooser

Java Swing cung cấp lớp JColorChooser tạo hộp thoại chọn màu rất mạnh.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;

public class ColorChooserEx extends JFrame {

    private JPanel panel; 
    private JPanel display;

    public ColorChooserEx() {

        initUI();
    }

    private void initUI() {

        panel = new JPanel();
 
        JButton openColor = new JButton("Choose Color");
        openColor.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                Color color = JColorChooser.showDialog(
                    panel, 
                    "Choose color", 
                    Color.white);
                display.setBackground(color);
            }
        });
 
        display = new JPanel();
        display.setPreferredSize(new Dimension(150, 150));
        display.setBorder(LineBorder.createGrayLineBorder());
        display.setBackground(Color.black); 
 
        panel.add(openColor);
        panel.add(display);
        add(panel);
  
        pack();
 
        setTitle("Dialog Example"); 
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                ColorChooserEx ccd = new ColorChooserEx();
                ccd.setVisible(true);
            }
        });
    }
}

Để mở hộp thoại chọn màu thì chỉ cần gọi JColorChooser.showDialog(), tham số đầu tiên là đối tượng cửa sổ cha hoặc panel cha, tham số thứ 2 là tiêu đề của hộp thoại, tham số thứ 3 là màu mặc định khi mở.

Color color = JColorChooser.showDialog(panel, "Choose color", Color.white);
display.setBackground(color);

Phương thức này sẽ trả về một đối tượng Color chứa thông tin màu đã được chọn hoặc null nếu người dùng không chọn màu nào.

Capture

Java Swing – Các component cơ bản – phần 2

Trong phần này chúng ta tiếp tục tìm hiểu về một số component trong Java Swing là JList, JTextAreaJTextPane.

JList

JList được dùng để hiển thi một danh sách các đối tượng và cho phép chọn một hoặc nhiều đối tượng.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;


public class ListExample extends JFrame {

    private JLabel label;
    private JList list;


    public ListExample() {

        initUI();
    }

    private void initUI() {

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        GraphicsEnvironment ge =
            GraphicsEnvironment.getLocalGraphicsEnvironment();

        String[] fonts = ge.getAvailableFontFamilyNames();

        list = new JList(fonts);
        list.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                String name = (String) list.getSelectedValue();
                Font font = new Font(name, Font.PLAIN, 12);
                label.setFont(font);                
            }
        });

        JScrollPane pane = new JScrollPane();
        pane.getViewport().add(list);
        pane.setPreferredSize(new Dimension(250, 200));
        panel.add(pane);

        label = new JLabel("War is Hell");
        label.setFont(new Font("Serif", Font.PLAIN, 12));
        add(label, BorderLayout.SOUTH);

        add(panel);

        pack();
        setTitle("Component Example");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                ListExample ex = new ListExample();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một JList hiển thị toàn bộ font chữ có trong máy tính và một JLabel để hiển thị font chữ đó.

GraphicsEnvironment ge = 
    GraphicsEnvironment.getLocalGraphicsEnvironment();

String[] fonts = ge.getAvailableFontFamilyNames();

Đầu tiên chúng ta lấy bộ font chữ trong máy thông qua lớp GraphicsEnvironment.

list = new JList(fonts);

Tiếp theo chúng ta tạo đối tượng JList.

String name = (String) list.getSelectedValue();
Font font = new Font(name, Font.PLAIN, 12);
label.setFont(font);

Khi đã lấy được font rồi thì chúng ta thiết lập font đó cho JLabel.

JScrollPane pane = new JScrollPane();
pane.getViewport().add(list);

Mặc định JList không có thanh trượt, muốn trượt được thì bạn phải đưa vào một đối tượng JScrollPane.

aaa

JTextArea

import java.awt.BorderLayout;
import java.awt.Dimension;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class TextAreaExample extends JFrame {

    public TextAreaExample() {
        
        initUI();
    }

    private void initUI() {

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        JScrollPane pane = new JScrollPane();
        JTextArea area = new JTextArea();

        area.setLineWrap(true);       
        area.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));

        pane.getViewport().add(area);
        panel.add(pane);

        add(panel);

        setTitle("Component Example");
        setSize(new Dimension(350, 300));
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                TextAreaExample ex = new TextAreaExample();
                ex.setVisible(true);
            }
        });
    }
}

JTextArea cho phép tạo các ô gõ văn bản.

area.setLineWrap(true);

Phương thức setLineWrap() cho phép các dòng text tự động xuống dòng khi vượt quá kích thước của TextArea.

pane.getViewport().add(area);

Cũng giống như JList, mặc định JTextArea không thể cuộn được, chúng ta phải đưa chúng vào một đối tượng JScrollPane.

Capture

JTextPane

JTextPane là một component cấp cao dùng để hiển thị text. JTextPane cung cấp nhiều phương thức định dạng text và có thể dùng để hiển thị nguyên cả một trang HTML.

package com.javaswingtut;

import java.awt.BorderLayout;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;

public class TextPaneExample extends JFrame {

    JTextPane textPane;

    public TextPaneExample() {

        initUI();
    }

    private void initUI() {

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        JScrollPane pane = new JScrollPane();
        textPane = new JTextPane();

        textPane.setContentType("text/html");
        textPane.setEditable(false);

        textPane.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));

        loadFile();

        pane.getViewport().add(textPane);
        panel.add(pane);

        add(panel);
        pack();

        setTitle("JTextPane");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void loadFile() {

        try {
            String cd = System.getProperty("user.dir") + "/";
            textPane.setPage("File:///" + cd + "/src/com/javaswingtut/phocode.html");
        } catch (IOException ex) {
            System.err.println("Cannot set page");
        }
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                TextPaneExample ex = new TextPaneExample();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta sẽ tạo một JTextPane và hiển thị một trang HTML lên đó.

<html>
<head>
    <title>Welcome to Pho Code</title>
</head>
<body>

    <h2>JTextPane Example</h2>
    <b>Pho Code</b> provides tutorials on various areas such as GUI, Graphics, Programming Language.
    <p>The website's mission is to provide quick and easy to understand tutorials.</p>   
</body>
</html>
aaa

Java Swing – Các component cơ bản – phần 1

Trong phần này chúng ta tìm hiểu một số component cơ bản gồm có JLabel, JCheckBox, JSlider, JComboBox, JProgressBar, và JToggleButton.

JLabel

JLabel được dùng để hiển thị text và ảnh, không thể bắt được sự kiện.

import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Font;
import javax.swing.GroupLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;


public class LabelEx extends JFrame {

    public LabelEx() {
        
        initUI();
    }
    
    private void initUI() {

        String lyrics =  "<html>It's way too late to think of" + 
        "<br>Someone I would call now" + 
        "<br>And neon signs got tired" + 
        "<br>Red eye flights help the stars out" + 
        "<br>I'm safe in a corner" + 
        "<br>Just hours before me" + 
        "</html>";
 
        JLabel label = new JLabel(lyrics);
        label.setFont(new Font("Serif", Font.PLAIN, 14));
        label.setForeground(new Color(50, 50, 25));

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 
        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                             .addComponent(label));

        gl.setVerticalGroup(gl.createParallelGroup()
                            .addComponent(label));

        pack();

        setTitle("Component Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
       
    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                LabelEx ex = new LabelEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta in lời bài hát lên cửa sổ, có một điều thú vị là chúng ta có thể dùng các tag trong HTML để tùy chỉnh phần text trong JLabel.

JLabel label = new JLabel(lyrics);
label.setFont(new Font("Serif", Font.PLAIN, 14));

Chúng ta tạo đối tượng JLabel, bạn có thể thiết lập kiểu font thông qua phương thức setFont() như trên.

pack();

Phương thức pack() thiết lập kích thước màn hình vừa đủ bao phủ các component.

Capture

JCheckBox

JCheckBox là lớp hỗ trợ tạo các checkbox. Chúng ta có thể dùng ActionListener hoặc ItemListener để lắng nghe sự kiện của JCheckBox, nhưng trong ví dụ này chúng ta sẽ dùng ItemListener.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.GroupLayout;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;

public class CheckBoxEx extends JFrame
        implements ItemListener {

    public CheckBoxEx() {
        
        initUI();
    }

    private void initUI() {

        JCheckBox cb = new JCheckBox("Show title", true);
        cb.addItemListener(this);

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);
        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                             .addComponent(cb));

        gl.setVerticalGroup(gl.createSequentialGroup()
                             .addComponent(cb));
        
        setSize(280, 200);
        setTitle("Component Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    
    @Override
    public void itemStateChanged(ItemEvent e) {
        
        int sel = e.getStateChange();
        
        if (sel==ItemEvent.SELECTED) {
            
            setTitle("Component Example");
        } else {
            
            setTitle("");
        }
    }           

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                CheckBoxEx ex = new CheckBoxEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta sẽ cho ẩn hoặc hiện tiêu đề cửa sổ tùy vào trạng thái của checkbox.

JCheckBox checkbox = new JCheckBox("Show title", true);

Đầu tiên chúng ta tạo đối tượng JCheckBox. Tham số true tức là mặc định checkbox sẽ được check.

cb.addItemListener(this);

Chúng ta gọi phương thức addItemListener() để gắn listener vào checkbox, ở đây listener chính là lớp chứa phương thức main() vì lớp này đã cài đặt giao diện ItemListener.

@Override
public void itemStateChanged(ItemEvent e) {
    
    int sel = e.getStateChange();
    
    if (sel==ItemEvent.SELECTED) {
        
        setTitle("JCheckBox");
    } else {
        
        setTitle("");
    }
}  

Lớp cài đặt giao diện ItemListener sẽ phải override phương thức ảo  getStateChange(), ở đây chúng ta thiết lập lại tiêu đề cửa sổ tùy thuộc vào trạng thái của checkbox.

Capture

JSlider

JSlider hỗ trợ tạo slider, đây là một thanh trượt biểu diễn giá trị. Slider sử dụng listener là lớp ChangeListener với phương thức stateChanged().

import java.awt.Container;
import java.awt.EventQueue;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class SliderEx extends JFrame {

    private JSlider slider;
    private JLabel lbl;
   
    public SliderEx() {
        
        initUI();
    }

    private void initUI() {                      
        
        slider = new JSlider(0, 100, 0);

        slider.addChangeListener(new ChangeListener() {
 
            @Override
            public void stateChanged(ChangeEvent event) { 
                lbl.setText(String.valueOf(slider.getValue()));
            }
        });

        lbl = new JLabel("0", JLabel.CENTER);

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 
 
        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);
 
        gl.setHorizontalGroup(gl.createSequentialGroup()
            .addComponent(slider)
            .addComponent(lbl));

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
            .addComponent(slider)
            .addComponent(lbl)); 

        pack();
 
        setTitle("Example Component");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }    

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                SliderEx ex = new SliderEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta hiển thị một slider biểu diễn giá trị từ 0 đến 100 và 1 label để in giá trị hiện tại lên cửa sổ.

slider = new JSlider(0, 100, 0);

Đầu tiên chúng ta khởi tạo đối tượng JSlider, hai tham số đầu tiên là giá trị min và max, tham số thứ 3 là giá trị khởi tạo của slider.

slider.addChangeListener(new ChangeListener() {
...
});

Chúng ta gắn listener vào slider, listener này sẽ lắng nghe sự thay đổi của slider và cập nhật lại giá trị lên label.

Capture

JComboBox

Java Swing cung cấp lớp JComboBox hỗ trợ tạo combo box.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.BASELINE;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;

public class ComboBoxEx extends JFrame
        implements ItemListener {

    private JLabel display;
    private JComboBox<String> box;
    private String[] distros;

    public ComboBoxEx() {

        initUI();        
    }

    private void initUI() {

        distros = new String[]{"Ubuntu", "Redhat", "Arch",
            "Debian", "Mint"};

        box = new JComboBox<>(distros);
        box.addItemListener(this);

        display = new JLabel("Ubuntu");
        
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
            .addComponent(box)
            .addComponent(display));

        gl.setVerticalGroup(gl.createParallelGroup(BASELINE)
            .addComponent(box)
            .addComponent(display));

        pack();

        setTitle("Component Example"); 
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        setLocationRelativeTo(null);
    }
    
    @Override
    public void itemStateChanged(ItemEvent e) {

        if (e.getStateChange() == ItemEvent.SELECTED) {
            display.setText(e.getItem().toString());
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ComboBoxEx ex = new ComboBoxEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta hiển thị một combo box bao gồm các item là tên của một số bản phân phối khác nhau của hệ điều hành linux, một label hiển thị tên tùy theo item đang được chọn trong combo box.

distros = new String[] {"Ubuntu", "Redhat", "Arch", 
    "Debian", "Mint"};

Các item trong JComboBox được lưu trong một mảng string, tuy nhiên bạn có thể dùng các phương thức riêng của JComboBox để thêm các item vào.

box = new JComboBox<>(distros);
box.addItemListener(this);

Chúng ta khởi tạo một đối tượng JComboBox và gắn listener vào nó.

@Override
public void itemStateChanged(ItemEvent e) {
    
    if (e.getStateChange() == ItemEvent.SELECTED) {
        display.setText(e.getItem().toString());
    }
}    

Phương thức itemStateChanged() sẽ được gọi mỗi khi một item khác được chọn trong combobox, bên trong chúng ta sẽ cho thay đổi giá trị của label tùy thuộc vào item trong combobox.Capture

JProgressBar

“Progress Bar” được dùng để biểu diễn tiến trình hoạt động của một công việc nào đó. Java Swing cung cấp lớp JProgressBar để tạo các progress bar.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.Timer;
public class ProgressBarEx extends JFrame {

    private Timer timer;
    private JProgressBar progressBar;
    private JButton button;

    public ProgressBarEx() {
        
        initUI();
    }

    private void initUI() {

        progressBar= new JProgressBar();
        progressBar.setStringPainted(true);
        
        button= new JButton("Start");
        button.addActionListener(new ClickAction());

        timer = new Timer(50, new UpdateBarListener());

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
            .addComponent(progressBar)
            .addComponent(button));

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
            .addComponent(progressBar)
            .addComponent(button)); 

        pack();
        
        setTitle("Component Example");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }       
    
    private class UpdateBarListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            
                int val = progressBar.getValue();
                
                if (val >= 100) {
                    
                    timer.stop();
                    button.setText("End");
                    return;
                }

                progressBar.setValue(++val);           
        }
    }
    
    private class ClickAction extends AbstractAction {

        @Override
        public void actionPerformed(ActionEvent e) {
            
                if (timer.isRunning()) {
                    
                    timer.stop();
                    button.setText("Start");

                } else if (!"End".equals(button.getText())) {
                    
                    timer.start();
                    button.setText("Stop");
                }       
        }
    }

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            
            @Override
            public void run() {
                ProgressBarEx ex = new ProgressBarEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta hiển thị một progress bar và một button. Button có tác dụng kích hoạt hoặc dừng progress bar.

progressBar = new JProgressBar();
progressBar.setStringPainted(true);

Chúng ta khởi tạo đối tượng JProgressBar, mặc định thì giá trị khởi tạo là từ 0 đến 100. Phương thức setStringPainted(true) có tác dụng hiển thị số % trong progressbar.

timer = new Timer(50, new UpdateBarListener());

Chúng ta tạo một đối tượng Timer có delay là 50ms và gắn một ActionListener vào timer này,

private class UpdateBarListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        
            int val = progressBar.getValue();
            
            if (val >= 100) {
                
                timer.stop();
                button.setText("End");
                return;
            }

            progressBar.setValue(++val);           
    }
}

Bên trong listener chúng ta lấy giá trị hiện tại và tăng lên trong progress bar. Nếu giá trị đạt đến 100 thì dừng timer và thay đổi text bên trong button.

private class ClickAction extends AbstractAction {

    @Override
    public void actionPerformed(ActionEvent e) {
    
        if (timer.isRunning()) {
        
            timer.stop();
            button.setText("Start");

        } else if (!"End".equals(button.getText())) {
        
            timer.start();
            button.setText("Stop");
        }       
    }
}

Button cũng được gắn một listener làm công việc dừng hoặc chạy tiếp timer đồng thời thay đổi giá trị trên button.

Untitled

JToggleButton

JToggleButton cho phép tạo các nút button có 2 trạng thái là bật hoặc tắt, giống như chech box vậy nhưng có hình dạng như button.

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import static javax.swing.LayoutStyle.ComponentPlacement.UNRELATED;
import javax.swing.border.LineBorder;

public class ToggleButtonEx extends JFrame
        implements ActionListener {

    private JToggleButton redButton;
    private JToggleButton greenButton;
    private JToggleButton blueButton;
    private JPanel display;

    public ToggleButtonEx() {

        initUI();
    }

    private void initUI() {

        redButton = new JToggleButton("red");
        redButton.addActionListener(this);

        greenButton = new JToggleButton("green");
        greenButton.addActionListener(this);

        blueButton = new JToggleButton("blue");
        blueButton.addActionListener(this);

        display = new JPanel();
        display.setPreferredSize(new Dimension(120, 120));
        display.setBorder(LineBorder.createGrayLineBorder());
        display.setBackground(Color.black);

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl); 
 
        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);
 
        gl.setHorizontalGroup(gl.createSequentialGroup()
            .addGroup(gl.createParallelGroup()
            .addComponent(redButton)
            .addComponent(greenButton)
            .addComponent(blueButton))
            .addPreferredGap(UNRELATED)
            .addComponent(display));

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
            .addGroup(gl.createSequentialGroup()
            .addComponent(redButton)
            .addComponent(greenButton)
            .addComponent(blueButton)) 
            .addComponent(display)); 
 
        gl.linkSize(redButton, greenButton, blueButton);

        pack(); 
 
        setTitle("Component Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }    

    @Override
    public void actionPerformed(ActionEvent e) {

        Color color = display.getBackground();
        int red = color.getRed();
        int green = color.getGreen();
        int blue = color.getBlue();

        if (e.getActionCommand().equals("red")) {
            if (red == 0) {
                red = 255;
            } else {
                red = 0;
            }
        }

        if (e.getActionCommand().equals("green")) {
            if (green == 0) {
                green = 255;
            } else {
                green = 0;
            }
        }

        if (e.getActionCommand().equals("blue")) {
            if (blue == 0) {
                blue = 255;
            } else {
                blue = 0;
            }
        }

        Color setCol = new Color(red, green, blue);
        display.setBackground(setCol);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ToggleButtonEx ex = new ToggleButtonEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta tạo 3 JToggleButton đại diện cho 3 màu đỏ, xanh lá, xanh lam và một JPanel để hiển thị màu. Button nào được bật thì màu tương ứng sẽ tham gia vào quá trình trộn màu để hiển thị trên panel.

redButton = new JToggleButton("red");
redButton.addActionListener(this);

Chúng ta tạo từng đối tượng JToggleButton, thiết lập text cho chúng và gắn listener vào.

Color color = display.getBackground();
int red = color.getRed();
int green = color.getGreen();
int blue = color.getBlue();

Lấy giá trị RGB từ JPanel.

if (e.getActionCommand().equals("red")) {
    if (red == 0) {
        red = 255;
    } else {
        red = 0;
    }
}

Bên trong listener, chúng ta xác định xem button nào đang được bật để thiết lập giá trị màu tương ứng.

Color setCol = new Color(red, green, blue);
display.setBackground(setCol);

Sau đó trộn chúng lại trong một đối tượng Color và đưa vào JPanel thông qua phương thức setBackground().

Capture

Java Swing – Xử lý sự kiện

Event (sự kiện) là một phần quan trọng của bất kỳ ứng dụng GUI nào. Tất cả các ứng dụng dạng GUI xử lý event trong suốt thời gian mà nó chạy. Event phần lớn được tạo ra bởi người dùng, nhưng cũng có lúc được tạo ra bởi chính ứng dụng. Có ba thành phần tham gia vào hệ thống event:

  • Event nguồn
  • Đối tượng event
  • Đối tượng lắng nghe event

Event nguồn là đối tượng tạo ra sự thay đổi. Cứ có gì đó trong ứng dụng tạo ra sự thay đổi nào đó thì nó chính là event nguồn. Đối tượng event là chính bản thân cái event đó đã được mã hóa. Đối tượng lắng nghe event làm công việc xử lý event đó.

Đối tượng Event

Mỗi khi có thứ gì đó xảy ra thì một đối tượng event sẽ được tạo. Chẳng hạn như khi bạn click vào một button hay chọn một item trong một danh sách. Đối tượng event lưu trữ thông tin về loại sự kiện đã xảy ra. Chúng ta sẽ xem xét thông tin đó trong ví dụ dưới đây.

import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JList;


public class EventObjectEx extends JFrame {

    private JList list;
    private DefaultListModel model;
    

    public EventObjectEx() {
        
        initUI();
    }
    
    private void initUI() {

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);   

        model = new DefaultListModel();
        list = new JList(model);
        list.setMinimumSize(new Dimension(250, 150));
        list.setBorder(BorderFactory.createEtchedBorder());

        JButton okButton = new JButton("OK");
        okButton.addActionListener(new ClickAction());

        gl.setAutoCreateContainerGaps(true);
        
        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(okButton)
                .addGap(20)
                .addComponent(list)
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(okButton)
                .addComponent(list)
        );
        
        pack();

        setTitle("Event Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
    private class ClickAction extends AbstractAction {
        
        @Override
        public void actionPerformed(ActionEvent e) {
            
            Locale locale = Locale.getDefault();
            Date date = new Date(e.getWhen());
            String tm = DateFormat.getTimeInstance(DateFormat.SHORT,
                    locale).format(date);

            if (!model.isEmpty()) {
                model.clear();
            }

            if (e.getID() == ActionEvent.ACTION_PERFORMED) {
                model.addElement("Event Id: ACTION_PERFORMED");
            }

            model.addElement("Time: " + tm);

            String source = e.getSource().getClass().getName();
            model.addElement("Source: " + source);           
        }
    }        

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                EventObjectEx ex = new EventObjectEx();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ này chúng ta tạo một button và một list, list sẽ hiển thị các thông tin về đối tượng event được gây ra bởi button.

okButton.addActionListener(new ClickAction());

Lớp ClickAction lắng nghe sự kiện từ button OK.

private class ClickAction extends AbstractAction {
    
    @Override
    public void actionPerformed(ActionEvent e) {
        ...
    }
}        

Phương thức actionPerformed() được gọi mỗi lần sự kiện xảy ra, phương thức này nhận tham số là một đối tượng ActionEvent.

Locale locale = Locale.getDefault();
Date date = new Date(e.getWhen());
String s = DateFormat.getTimeInstance(DateFormat.SHORT,
        locale).format(date);

Chúng ta có thể lấy thời điểm event xảy ra từ phương thức getWhen(), thời gian trong phương thức này tính theo mili giây nên chúng ta phải định dạng lại cho phù hợp.

String source = e.getSource().getClass().getName();
model.addElement("Source: " + source);

Chúng ta có thể lấy được thông tin về đối tượng gây ra event từ phương thức getSource() và một số thông tin bên trong lớp đó.

Capturaae

Tạo đối tượng lắng nghe sự kiện

Có 3 cách để tạo một đối tượng lắng nghe sự kiện trong Java Swing.

  • Tạo lớp ẩn nội (Anonymous Inner class)
  • Tạo lớp nội (Inner class)
  • Tạo lớp kế thừa (Derived class)

Tạo lớp ẩn nội

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;


public class AnonymousInnerClassEx extends JFrame {


    public AnonymousInnerClassEx() {

        initUI();
    }

    private void initUI() {
        
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);                

        JButton closeButton = new JButton("Close");
        closeButton.setBounds(40, 50, 80, 25);

        closeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                System.exit(0);
            }
        });
        
        gl.setAutoCreateContainerGaps(true);
        
        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(closeButton)
                .addGap(220)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(closeButton)
                .addGap(180)
        );
        
        pack();        

        setTitle("Event Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                AnonymousInnerClassEx ex =
                        new AnonymousInnerClassEx();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ này chúng ta tạo một đối tượng lắng nghe sự kiện từ một button.

JButton closeButton = new JButton("Close");

Khi click button thì thoát chương trình.

closeButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent event) {
        System.exit(0);
    }
});

Để gắn một đối tượng lắng nghe vào một component thì chúng ta dùng phương thức addActionListener(). Ở đây chúng ta dùng lớp “ẩn nội”, nếu bạn chưa biết thì tức là tạo một đối tượng và code các phương thức trừu tượng ngay bên trong một phương thức khác.

Tạo lớp nội

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;

public class InnerClassExample extends JFrame {

    public InnerClassExample() {

        initUI();
    }

    private void initUI() {

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);    

        JButton closeButton = new JButton("Close");

        ButtonCloseListener listener = new ButtonCloseListener();
        closeButton.addActionListener(listener);

        gl.setAutoCreateContainerGaps(true);
        
        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(closeButton)
                .addGap(220)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(closeButton)
                .addGap(180)
        );
        
        pack();        

        setTitle("Event Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private class ButtonCloseListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                InnerClassExample ex = new InnerClassExample();
                ex.setVisible(true);
            }
        });
    }
}

Ví dụ này cũng giống ví dụ trên, chỉ khác là chúng ta dùng lớp “nội”: định nghĩa một lớp bên trong một lớp khác, tuy nhiên các lớp này phải được implements từ lớp ActionListener.

private class ButtonCloseListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        System.exit(0);
    }
}

Tạo lớp kế thừa

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;

public class DerivedClassExample extends JFrame {

    public DerivedClassExample() {

        initUI();
    }

    private void initUI() {
        
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);            
        
        MyButton closeButton = new MyButton("Close");

        gl.setAutoCreateContainerGaps(true);
        
        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(closeButton)
                .addGap(220)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(closeButton)
                .addGap(180)
        );
        
        pack();   
        
        setTitle("Event Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private class MyButton extends JButton 
        implements ActionListener {

        public MyButton(String text) {
            super.setText(text);
            addActionListener(this);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                DerivedClassExample ex = new DerivedClassExample();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ này chúng ta cũng tạo một button có chức năng thoát chương trình, nhưng ở đây chúng ta tạo một lớp kế thừa từ lớp JButton là implements sẵn giao diện ActionListenerTức là bản thân lớp này đã tự động lắng nghe các sự kiện click rồi, không cần phải dùng đến phương thức addActionListener() như với lớp ẩn nội nữa.

private class MyButton extends JButton 
    implements ActionListener {

    public MyButton(String text) {
        super.setText(text);
        addActionListener(this);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        System.exit(0);
    }
}

Gắn một listener vào nhiều component

Một listener có thể lắng nghe từ nhiều component khác nhau.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class MultipleSources extends JFrame {

    private JLabel statusbar;

    public MultipleSources() {

        initUI();
    }

    private void initUI() {
        
        JPanel panel = new JPanel();
        GroupLayout gl = new GroupLayout(panel);
        panel.setLayout(gl);
        
        statusbar = new JLabel("Ready");

        statusbar.setBorder(BorderFactory.createEtchedBorder());
        
        ButtonListener butlist = new ButtonListener();

        JButton closeButton = new JButton("Close");
        closeButton.addActionListener(butlist);

        JButton openButton = new JButton("Open");
        openButton.addActionListener(butlist);

        JButton findButton = new JButton("Find");
        findButton.addActionListener(butlist);

        JButton saveButton = new JButton("Save");
        saveButton.addActionListener(butlist);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);
        
        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(closeButton)
                .addComponent(openButton)
                .addComponent(findButton)
                .addComponent(saveButton)
                .addGap(250)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(closeButton)
                .addComponent(openButton)
                .addComponent(findButton)
                .addComponent(saveButton)
                .addGap(20)
        );
        
        gl.linkSize(closeButton, openButton, 
                findButton, saveButton);
        
        add(panel, BorderLayout.CENTER);
        add(statusbar, BorderLayout.SOUTH);
        
        pack();
                
        setTitle("Event Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private class ButtonListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {

            JButton o = (JButton) e.getSource();
            String label = o.getText();
            statusbar.setText(" " + label + " button clicked");
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MultipleSources ms = new MultipleSources();
                ms.setVisible(true);
            }
        });
    }
}

Chúng ta tạo 4 button và một statusbar. Statusbar sẽ hiển thị thông tin tùy vào button nào được click.

JButton closeButton = new JButton("Close");
closeButton.addActionListener(butlist);

JButton openButton = new JButton("Open");
openButton.addActionListener(butlist);

...

Ở đây chúng ta tạo lớp nội ButtonListener và gắn vào từng button.

private class ButtonListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {

        JButton o = (JButton) e.getSource();
        String label = o.getText();
        statusbar.setText(" " + label + " button clicked");
    }
}

Đoạn text trên statusbar được hiển thị tùy theo từng button.

Capture

Gắn nhiều listener vào một component

Nếu có thể dùng một listener cho nhiều component thì cũng có thể gắn nhiều listener vào một component.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Calendar;
import javax.swing.BorderFactory;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import static javax.swing.GroupLayout.DEFAULT_SIZE;
import static javax.swing.GroupLayout.PREFERRED_SIZE;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;


public class MultipleListeners extends JFrame {

    private JLabel statusbar;
    private JSpinner spinner;
    private int count = 0;

    public MultipleListeners() {

        initUI();
    }

    private void initUI() {
        
        JPanel panel = new JPanel();
        GroupLayout gl = new GroupLayout(panel);
        panel.setLayout(gl);   
        add(panel, BorderLayout.CENTER);

        statusbar = new JLabel("0");
        statusbar.setBorder(BorderFactory.createEtchedBorder());
        add(statusbar, BorderLayout.SOUTH);

        JButton addButton = new JButton("+");
        addButton.addActionListener(new ButtonListener1());
        addButton.addActionListener(new ButtonListener2());

        Calendar calendar = Calendar.getInstance();
        int currentYear = calendar.get(Calendar.YEAR);

        SpinnerModel yearModel = new SpinnerNumberModel(currentYear,
                currentYear - 100,
                currentYear + 100,
                1);

        spinner = new JSpinner(yearModel);
        spinner.setEditor(new JSpinner.NumberEditor(spinner, "#"));

        
        gl.setAutoCreateContainerGaps(true);
        
        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(addButton)
                .addGap(20)
                .addComponent(spinner, DEFAULT_SIZE,
                        DEFAULT_SIZE, PREFERRED_SIZE)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup(CENTER)
                        .addComponent(addButton)
                        .addComponent(spinner, DEFAULT_SIZE,
                                DEFAULT_SIZE, PREFERRED_SIZE))
        );
        
        pack();         
        
        setTitle("Event Example");
        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private class ButtonListener1 implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            Integer val = (Integer) spinner.getValue();
            spinner.setValue(++val);
        }
    }

    private class ButtonListener2 implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            statusbar.setText(Integer.toString(++count));
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MultipleListeners ml = new MultipleListeners();
                ml.setVisible(true);
            }
        });
    }
}

Trong ví dụ này chúng ta tạo một button, một spinner và một statusbar. Chúng ta định nghĩa 2 listener và gắn cả hai listener này vào button, một listener sẽ thay đổi giá trị trên spinner, một listener sẽ thay đổi đoạn text trong statusbar.

SpinnerModel yearModel = new SpinnerNumberModel(currentYear,
        currentYear - 100,
        currentYear + 100,
        1);

spinner = new JSpinner(yearModel);

Chúng ta khởi tạo một đối tượng SpinnerModel, bạn có thể hiểu đối tượng này làm công việc khởi tạo giá trị cho JSpinner, tham số đầu tiên là giá trị khởi tạo, tham số thứ 2 và thứ 3 là giá trị min và max trong listener, tham số thứ 4 là giá trị tăng thêm lên bao nhiêu sau mỗi lần click vào spinner.

spinner.setEditor(new JSpinner.NumberEditor(spinner, "#"));

Dòng code trên có tác dụng loại bỏ dấu phẩy trong định dạng số (2,016 → 2016).

private class ButtonListener1 implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        Integer val = (Integer) spinner.getValue();
        spinner.setValue(++val);
    }
}

Listener thứ nhất thay đổi giá trị của spinner.

private class ButtonListener2 implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        statusbar.setText(Integer.toString(++count));
    }
}

Listener thứ hai làm công việc thay đổi đoạn text trong statusbar.

Capture

Gỡ bỏ listener ra khỏi component

Để làm việc này thì chỉ đơn giản là dùng phương thức removeActionListener().

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class RemoveListenerEx extends JFrame {

    private JLabel lbl;
    private JButton addButton;
    private JCheckBox activeBox;
    private ButtonListener buttonlistener;
    private int count = 0;

    public RemoveListenerEx() {
        
        initUI();
    }
    
    private void initUI() {

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);  

        addButton = new JButton("+");
        buttonlistener = new ButtonListener();

        activeBox = new JCheckBox("Active listener");
        activeBox.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent event) {
                
                if (activeBox.isSelected()) {
                    addButton.addActionListener(buttonlistener);
                } else {
                    addButton.removeActionListener(buttonlistener);
                }
            }
        });

        lbl = new JLabel("0");
        
        gl.setAutoCreateContainerGaps(true);
        
        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(addButton)
                        .addComponent(lbl))
                .addGap(30)
                .addComponent(activeBox)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(addButton)
                        .addComponent(activeBox))
                .addGap(30)
                .addComponent(lbl)
        );
        
        pack(); 

        setTitle("Event Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private class ButtonListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            
            lbl.setText(Integer.toString(++count));
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                RemoveListenerEx ex = new RemoveListenerEx();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ này chúng ta tạo một button, một checkbox và một label. Button được gắn một listener và chỉ khi checkbox được check thì listener này mới lắng nghe sự kiện từ button.

buttonlistener = new ButtonListener();

Lớp listener được tạo ra phải là lớp không ẩn thì mới có thể gỡ ra khỏi component được,

if (activeBox.isSelected()) {
    addButton.addActionListener(buttonlistener);
} else {
    addButton.removeActionListener(buttonlistener);
}

Chúng ta kiểm tra nếu checkbox được check thì gắn listener vào còn không nếu thì gỡ ra.

Capture

Xử lý sự kiện di chuyển

Trong ví dụ này chúng ta tìm hiểu về cách bắt sự kiện có liên quan đến sự hiện diện của component.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;


public class MovingWindowEx extends JFrame
        implements ComponentListener {

    private JLabel labelx;
    private JLabel labely;

    public MovingWindowEx() {

        initUI();
    }

    private void initUI() {

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);  
        
        addComponentListener(this);

        labelx = new JLabel("x: ");
        labelx.setFont(new Font("Serif", Font.BOLD, 14));
        labelx.setBounds(20, 20, 60, 25);

        labely = new JLabel("y: ");
        labely.setFont(new Font("Serif", Font.BOLD, 14));
        labely.setBounds(20, 45, 60, 25);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);
        
        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(labelx)
                .addComponent(labely)
                .addGap(250)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(labelx)
                .addComponent(labely)
                .addGap(130)
        );
        
        pack();   

        setTitle("Event Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    @Override
    public void componentResized(ComponentEvent e) {
    }

    @Override
    public void componentMoved(ComponentEvent e) {
        
        int x = e.getComponent().getX();
        int y = e.getComponent().getY();
        
        labelx.setText("x: " + x);
        labely.setText("y: " + y);
    }

    @Override
    public void componentShown(ComponentEvent e) {
    }

    @Override
    public void componentHidden(ComponentEvent e) {
    }


    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MovingWindowEx ex = new MovingWindowEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta sẽ gắn một listener vào cửa sổ chính để lấy tọa độ của cửa sổ trên màn hình.

public class MovingWindowExample extends JFrame
        implements ComponentListener {

Giao diện ComponentListener là giao diện dùng để lắng nghe các sự kiện liên quan đến sự xuất hiện của component.

@Override
public void componentResized(ComponentEvent e) {
}

@Override
public void componentMoved(ComponentEvent e) {
    
    int x = e.getComponent().getX();
    int y = e.getComponent().getY();
    
    labelx.setText("x: " + x);
    labely.setText("y: " + y);
}

@Override
public void componentShown(ComponentEvent e) {
}

@Override
public void componentHidden(ComponentEvent e) {
}

Theo luật của Java thì chúng ta phải override toàn bộ phương thức ảo nhưng ở đây chúng ta chỉ quan tâm tới phương thức componentMoved() thôi.

int x = e.getComponent().getX();
int y = e.getComponent().getY();

Để lấy tọa độ của component thì chúng ta dùng phương thức getX()getY().

Capture

Sử dụng lớp Adapter thay thế cho Listener

Trong ví dụ trên chúng ta implement giao diện ComponentListener và phải override toàn bộ các phương thức ảo trong giao diện này, như thế rất khó chịu, chính vì thế và Java đã tạo ra các lớp adapter. Adapter đơn giản là các lớp đã implement sẵn các lớp listener tương ứng và override toàn bộ phương thức ảo trước rồi. Vì thế nên khi dùng adapter chúng ta chỉ cần dùng các phương thức mà chúng ta muốn thôi.

Ví dụ:

  • Giao diện ComponentListener bắt buộc bạn phải override các phương thức ảo componentResized(), componentMoved(), componentShown(), và componentHidden() mặc dù bạn không muốn.
  • Lớp ComponentAdapter đã implement sẵn giao diện ComponentListener và override toàn bộ các phương thức ảo trên rồi, bạn chỉ cần override lại phương thức mà bạn muốn.

Ví dụ dưới đây giống như ví dụ ở trên, chỉ khác là chúng ta dùng lớp ComponentAdapter.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class AdapterExample extends JFrame {

    private JLabel labelx;
    private JLabel labely;

    public AdapterExample() {

        initUI();
    }

    private void initUI() {

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);  
        
        addComponentListener(new MoveAdapter());

        labelx = new JLabel("x: ");
        labelx.setFont(new Font("Serif", Font.BOLD, 14));
        
        labely = new JLabel("y: ");
        labely.setFont(new Font("Serif", Font.BOLD, 14));

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);
        
        gl.setHorizontalGroup(gl.createParallelGroup()
                .addComponent(labelx)
                .addComponent(labely)
                .addGap(250)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(labelx)
                .addComponent(labely)
                .addGap(130)
        );
        
        pack();   

        setTitle("Event Example");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);        
    }

    private class MoveAdapter extends ComponentAdapter {

        @Override
        public void componentMoved(ComponentEvent e) {
            
            int x = e.getComponent().getX();
            int y = e.getComponent().getY();
            
            labelx.setText("x: " + x);
            labely.setText("y: " + y);
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                AdapterExample ex = new AdapterExample();
                ex.setVisible(true);
            }
        });
    }
}

Java Swing – Quản lý layout

Các component trong Java Swing được chia làm hai loại là container và children, trong đó container là loại component dùng để chứa các component khác, ví dụ như JFrame, JPanel… còn children là các component cụ thể như JLabel, JButton… khi chúng ta đặt các component con lên các container thì việc đặt vị trí và sắp xếp chúng là một công việc rất mất thời gian, từ đó khái niệm layout ra đời.

Layout có tác dụng tự động bố trí các component trên container, bạn tưởng tượng layout giống như một căn nhà có nhiều phòng và các phòng này có thể tự thay đổi kích thước cũng như vị trí theo một quy luật nào đó và theo đó mà cách vật dụng trong phòng cũng tự thay đổi theo. Đối với người mới bắt đầu lập trình GUI thì layout là một khái niệm khá khó hiểu vì nó không có hình ảnh trực quan như text hay button.

Cũng có những trường hợp chúng ta không cần dùng đến layout. Tuy nhiên nên dùng thì vẫn tốt hơn.

FlowLayout

Đây là loại layout đơn giản nhất, layout này thường được dùng kết hợp với các layout khác. Layout này quy định kích thước của các component con là vừa đủ với nội dung hiển thị của component.

Mặc định các component sẽ được sắp xếp trên một hàng từ trái sang phải, nếu không vừa đủ một hàng thì chúng sẽ xuống hàng. Các component sẽ cách nhau 5 pixel và cách viền cửa sổ 5 pixel. Tuy nhiên chúng ta có thể thay đổi các thuộc tính này.

FlowLayout()
FlowLayout(int align)
FlowLayout(int align, int hgap, int vgap) 

Ở trên là 3 kiểu khởi tạo một FlowLayout, align quy định sắp xếp từ trái sang phải hay ngược lại, hgap là khoảng cách giữa các component theo chiều ngang, vgap là khảng cách giữa các component theo chiều dọc.

import java.awt.Dimension;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.SwingUtilities;


public class FlowLayoutExample extends JFrame {


    public FlowLayoutExample() {

        initUI();
    }

    public final void initUI() {

        JPanel panel = new JPanel();

        JTextArea area = new JTextArea("text area");
        area.setPreferredSize(new Dimension(100, 100));

        JButton button = new JButton("button");
        panel.add(button);

        JTree tree = new JTree();
        panel.add(tree);

        panel.add(area);

        add(panel);

        pack();

        setTitle("Layout Example");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {

            public void run() {

                FlowLayoutExample ex = new FlowLayoutExample();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ trên chúng ta tạo một đối tượng JButton, JTreeJTextArea. Lớp JTextArea tạo một ô gõ văn bản. Lớp JTree hiển thị dữ liệu theo dạng cây, khi chúng ta tạo một đối tượng JTree rỗng thì mặc nhiên trong này có sẵn một số giá trị rồi.

JPanel panel = new JPanel();

Mặc định khi tạo một JPanel thì layout được dùng sẽ là flowLayout luôn nên ở đây chúng ta không cần phải thiết lập lại.

JTextArea area = new JTextArea("text area");
area.setPreferredSize(new Dimension(100, 100));

Mặc định flowLayout sẽ thiết lập kích thước cho các component con vừa đủ để bao bọc nội dung bên trong component đó, nên nếu muốn có kích thước riêng thì chúng ta phải tự thiết lập lại bằng phương thức setPreferredSize().

panel.add(area);

Và để thêm một component con vào một container thì chỉ đơn giản là dùng phương thức add() là được.

Capture

GridLayout

Layout này sắp xếp các component theo dạng bảng. Các component sẽ có kích thước bằng nhau.

import java.awt.GridLayout;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class GridLayoutExample extends JFrame {

    public GridLayoutExample() {

        initUI();
    }

    public final void initUI() {

        JPanel panel = new JPanel();

        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        panel.setLayout(new GridLayout(5, 4, 5, 5));

        String[] buttons = {
            "Cls", "Bck", "", "Close", 
            "7", "8", "9", "/", 
            "4", "5", "6", "*", 
            "1", "2", "3", "-", 
            "0", ".", "=", "+"
        };

        for (int i = 0; i < buttons.length; i++) {

            if (i == 2)
                panel.add(new JLabel(buttons[i]));
            else
                panel.add(new JButton(buttons[i]));
        }

        add(panel);

        setTitle("Layout Example");
        setSize(350, 300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {

            public void run() {

                GridLayoutExample ex = new GridLayoutExample();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta sẽ tạo giao diện máy tính bỏ túi.

panel.setLayout(new GridLayout(5, 4, 5, 5));

Trong phương thức khởi tạo GridLayout, 2 tham số đầu là số hàng và số cột, hai tham số cuối là khoảng cách giữa các component theo chiều ngang và chiều dọc.

Capture

BorderLayout

BorderLayout sắp xếp các component theo vùng, ở đây có 5 vùng là Đông (EAST), Tây (WEST), Nam (SOUTH), Bắc (NORTH), và Chính giữa (CENTER). Nhưng mỗi vùng chỉ được chứa một component, do đó khi muốn đưa nhiều component vào một vùng thì chúng ta đặt một layout khác vào vùng đó rồi đặt các component vào layout mới đó. Khác với các layout khác, kích thước của các component trong mỗi vùng là do chúng ta thiết lập chứ không phải do layout tự co dãn, ngoại trừ vùng CENTER sẽ có kích thước thay đổi tùy thuộc vào 4 vùng còn lại.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Insets;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;


public class BorderLayoutExample extends JFrame {


    public BorderLayoutExample() {

        initUI();
    }

    public final void initUI() {

        // Create menu bar
        JMenuBar menubar = new JMenuBar();
        JMenu file = new JMenu("File");
 
        menubar.add(file);
        setJMenuBar(menubar);
 
        // Create horizontal toolbar (NORTH)
        JToolBar horizontalToolbar = new JToolBar();
        horizontalToolbar.setFloatable(false);
 
        ImageIcon exitIcon = new ImageIcon("F:/exit.png");
        JButton exitButton = new JButton(exitIcon);
        exitButton.setBorder(new EmptyBorder(0, 0, 0, 0));
        horizontalToolbar.add(exitButton);
 
        add(horizontalToolbar, BorderLayout.NORTH);
 
        // Create vertical toolbar (WEST)
        JToolBar verticalToolbar = new JToolBar(JToolBar.VERTICAL);
        verticalToolbar.setFloatable(false);
        verticalToolbar.setMargin(new Insets(10, 5, 5, 5));
 
        ImageIcon driveIcon = new ImageIcon("F:/drive.png");
        ImageIcon computerIcon = new ImageIcon("F:/computer.png");
        ImageIcon printerIcon = new ImageIcon("F:/printer.png");
 
        JButton driveButton = new JButton(driveIcon);
        driveButton.setBorder(new EmptyBorder(3, 0, 3, 0));
 
        JButton computerButton = new JButton(computerIcon);
        computerButton.setBorder(new EmptyBorder(3, 0, 3, 0));
 
        JButton printerButton = new JButton(printerIcon);
        printerButton.setBorder(new EmptyBorder(3, 0, 3, 0));
 
        verticalToolbar.add(driveButton);
        verticalToolbar.add(computerButton);
        verticalToolbar.add(printerButton);
 
        add(verticalToolbar, BorderLayout.WEST);
 
        // Craete Text area (CENTER)
        add(new JTextArea(), BorderLayout.CENTER);
 
        // Create Status bar (SOUTH)
        JLabel statusbar = new JLabel("Statusbar");
        statusbar.setPreferredSize(new Dimension(-1, 22));
        statusbar.setBorder(LineBorder.createGrayLineBorder());
        add(statusbar, BorderLayout.SOUTH);
  
        setSize(350, 300);
        setTitle("Layout Example");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                BorderLayoutExample ex = new BorderLayoutExample();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ này chúng ta sẽ tạo một giao diện giống như các ứng dụng thường thấy bao gồm 1 thanh menu, 1 toolbar nằm ngang (NORTH), 1 toolbar nằm dọc (WEST), 1 text area (CENTER), 1 status bar (SOUTH).

Mặc định JFrame dùng sẵn BorderLayout nên chúng ta không cần phải thiết lập lại.

Capture

BoxLayout

BoxLayout cho phép chúng ta tạo các giao diện phức tạp. Layout này sắp xếp các component theo chiều ngang hoặc chiều dọc, tức là giống với FlowLayout, chỉ khác ở chỗ là BoxLayout cho phép các component có thể tự thay đổi kích thước dựa vào kích thước cửa sổ chính. Ngoài ra bạn còn có thể lồng một BoxLayout vào một BoxLayout khác.

BoxLayout thường đi cùng với một lớp khác nữa là lớp Box, lớp này có tác dụng tạo các khoảng trống để “đẩy” các component về các vị trí khác nhau. Các loại khoảng trống mà Box có thể tạo ra là Glue, StrutRigidArea. Mình sẽ giải thích ngay bên dưới.

Trong ví dụ này chúng ta sẽ tạo 2 button nằm ở góc dưới bên phải cửa sổ.

import java.awt.Dimension;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class TwoButtonsExample extends JFrame {

    public TwoButtonsExample() {

        initUI();
    }

    public final void initUI() {

        JPanel basic = new JPanel();
        basic.setLayout(new BoxLayout(basic, BoxLayout.Y_AXIS));
        add(basic);

        basic.add(Box.createVerticalGlue());

        JPanel bottom = new JPanel();
        bottom.setAlignmentX(Component.RIGHT_ALIGNMENT);
        bottom.setLayout(new BoxLayout(bottom, BoxLayout.X_AXIS));

        JButton ok = new JButton("OK");
        JButton close = new JButton("Close");

        bottom.add(ok);
        bottom.add(Box.createRigidArea(new Dimension(5, 0)));
        bottom.add(close);
        bottom.add(Box.createRigidArea(new Dimension(15, 0)));

        basic.add(bottom);
        basic.add(Box.createRigidArea(new Dimension(0, 15)));

        setTitle("Layout Example");
        setSize(300, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                TwoButtonsExample ex = new TwoButtonsExample();
                ex.setVisible(true);
            }
        });
    }
}

Bạn có thể hình dung sơ đồ bố trí các component như hình dưới.

Untitled11

Chúng ta tạo 2 panel, một panel chính sắp xếp các component theo chiều dọc, một panel thứ 2 sắp xếp các component theo chiều ngang, panel thứ 2 sẽ chứa 2 button căn lề phải. Để panel thứ 2 có thể nằm phía dưới cùng của cửa sổ (hay phía dưới cùng của panel chính) thì chúng ta dùng đến Glue. 

basic.setLayout(new BoxLayout(basic, BoxLayout.Y_AXIS));

Đầu tiên chúng ta tạo panel chính với BoxLayout theo chiều dọc.

basic.add(Box.createVerticalGlue());

Chúng ta tạo khoảng trống glue bằng phương thức createVerticalGlue(). Cơ chế hoạt động ở đây rất đơn giản. Ví dụ bạn đặt một button, sau đó gọi phương thức tạo glue, thì một vùng trống sẽ được thêm vào phía sau button (ở dưới button hoặc bên phải button tùy vào layout ngang hay dọc).

2fill5fill

JPanel bottom = new JPanel();
bottom.setAlignmentX(Component.RIGHT_ALIGNMENT);
bottom.setLayout(new BoxLayout(bottom, BoxLayout.X_AXIS));

Tiếp theo chúng ta tạo panel thứ hai và thiết lập cho các component con trượt qua lề bên phải bằng phương thức setAlignmentX(), panel này dùng BoxLayout theo chiều ngang.

bottom.add(Box.createRigidArea(new Dimension(5, 0)));

Chúng ta thêm một khoảng trống nhỏ giữa 2 button. Nhưng ở đây chúng ta không dùng glue và dùng rigid. Sự khác nhau của glue với rigid là kích thước của glue tự động thay đổi tùy theo các component, còn kích thước của rigid là do chúng ta quy định.

3fill 6fill

basic.add(bottom);

Sau khi đã thiết kế giao diện cho panel thứ hai thì chúng ta đặt panel đó vào panel chính.

basic.add(Box.createRigidArea(new Dimension(0, 15)));

Đồng thời chúng ta cũng tạo một khoảng trống rigid giữa panel thứ 2 với cạnh đáy của cửa sổ.

Capture

Java Swing – Menu và toolbar

Trong bài này chúng ta sẽ học cách làm menu và toolbar.

Java Swing có sẵn 3 lớp hỗ trợ tạo menu là a JMenuBarJMenu và JMenuItem.

Tạo menu

Chúng ta tạo một thanh menu có 1 item là Exit, click vào item này thì thoát chương trình.

JMenuBar menubar = new JMenuBar();

Đầu tiên chúng ta tạo một đối tượng menubar từ lớp JMenuBar.

ImageIcon icon = new ImageIcon("E:/exit.png");

Tiếp theo là tạo một đối tượng ImageIcon để làm ảnh icon của item.

JMenu file = new JMenu("File");
file.setMnemonic(KeyEvent.VK_F);

Chúng ta dùng lớp JMenu để tạo một menu, sau đó thiết lập phím tắt cho menu này là tổ hợp Alt+F.

JMenuItem eMenuItem = new JMenuItem("Exit", icon);
eMenuItem.setMnemonic(KeyEvent.VK_E);

Chúng ta tạo các item trong một menu từ lớp JMenuItem với tên item và ảnh icon, sau đó chúng ta gán phím tắt cho menu con này là tổ hợp Alt+E.

eMenuItem.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent event) {
        System.exit(0);
    }
});

Bản thân JMenuItem là một lớp kế thừa từ lớp AbstractButton, tức là cũng thuộc một loại button đặc biệt nên chúng ta có thể gắn listener cho đối tượng thuộc lớp này, ở đây chúng ta gọi System.exit() để thoát chương trình.

file.add(eMenuItem);
menubar.add(file);

Chúng ta thêm item vào menu rồi thêm menu đó vào menubar.

setJMenuBar(menubar);

Cuối cùng thiết lập menubar từ phương thức setJMenuBar().

Untitled

Tạo menu con

Trong ví dụ này chúng ta tạo một menu con.

JMenu saveMenu = new JMenu("Save");
...
menuFile.add(saveMenu);

Một menu con cũng chỉ đơn giản là một menu item thôi, bạn chỉ cần tạo nó ra rồi dùng phương thức add() của một menu khác để thêm vào làm menu con.

menuFile.addSeparator();

Ngoài ra ở đây chúng ta còn tạo một separator, đây đơn giản chỉ là một đường kẻ ngang để phân nhóm các item có chung mục đích với nhau lại.

Untitled

Tạo phím tắt cho menu item

Trong Java có 2 kiểu phím tắt là MnemonicAccelerator, trong đó mnemonic là tổ hợp của phím Alt với phím do chúng ta chỉ định, khi bấm vào tổ hợp phím này thì menu hoặc menu item sẽ xổ xuống, trong ví dụ đầu tiên chúng ta đã làm quen với mnemonic, còn Accelerator là kiểu phím tắt mà khi bấm tổ hợp phím này thì menu item sẽ được thực thi, tổ hợp phím trong accelerator là do chúng ta chỉ định.

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import static javax.swing.Action.MNEMONIC_KEY;
import static javax.swing.Action.SMALL_ICON;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;


public class ShortCutsEx extends JFrame {

    public ShortCutsEx() {
        
        initUI();
    }

    private void initUI() {

        JMenuBar menuBar = new JMenuBar();
 
        ImageIcon newIcon = new ImageIcon("F:/new.png");
        ImageIcon openIcon = new ImageIcon("F:/open.png");
        ImageIcon saveIcon = new ImageIcon("F:/save.png");
        ImageIcon exitIcon = new ImageIcon("F:/exit.png");
 
        JMenu fileMenu = new JMenu("File");
        fileMenu.setMnemonic(KeyEvent.VK_F);
 
        JMenuItem newItem = new JMenuItem("New", newIcon);
        JMenuItem openItem = new JMenuItem("Open", openIcon);
        JMenuItem saveItem = new JMenuItem("Save", saveIcon);
        JMenuItem exitItem = new JMenuItem("Exit", exitIcon);
 
        exitItem.setMnemonic(KeyEvent.VK_E);
        exitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, 
                                ActionEvent.CTRL_MASK));
        exitItem.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                System.exit(0);
            }
        });
 
        fileMenu.add(newItem);
        fileMenu.add(openItem);
        fileMenu.add(saveItem);
        fileMenu.addSeparator();
        fileMenu.add(exitItem);
 
       menuBar.add(fileMenu);
       setJMenuBar(menuBar);
 
        setTitle("Menu Example");
        setSize(300, 300);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);        
    }        

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                
                ShortCutsEx ex = new ShortCutsEx();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ này chúng ta tạo phím tắt theo cả 2 kiểu.

JMenu fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);

Đầu tiên chúng ta thiết lập mnemonic cho menu File với tổ hợp phím Alt+F.

exitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W,
    ActionEvent.CTRL_MASK));

Để thiết lập phím tắt accelerator thì chúng ta dùng phương thức setAccelerator().

Untitled

Tạo check box item

Chúng ta có thể tạo menu item là check box. Java Swing cung cấp lớp JCheckBoxMenuItem để hỗ trợ làm việc này.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import javax.swing.BorderFactory;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;

public class CheckBoxMenuItemEx extends JFrame {

    private JLabel statusbar;

    public CheckBoxMenuItemEx() {

        initUI();
    }

    private void initUI() {

        JMenuBar menubar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        fileMenu.setMnemonic(KeyEvent.VK_F);
 
        JMenu viewMenu = new JMenu("View");
        viewMenu.setMnemonic(KeyEvent.VK_V);
 
        JCheckBoxMenuItem showStatusBar = new JCheckBoxMenuItem("Show statusbar");
        showStatusBar.setMnemonic(KeyEvent.VK_S);
        showStatusBar.setDisplayedMnemonicIndex(5);
        showStatusBar.setSelected(true);
 
        showStatusBar.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent e) { 
                statusbar.setVisible(e.getStateChange() == ItemEvent.SELECTED);
            }
        });
 
        viewMenu.add(showStatusBar);
        menubar.add(fileMenu);
        menubar.add(viewMenu);
 
        setJMenuBar(menubar);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                CheckBoxMenuItemEx ex = new CheckBoxMenuItemEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta sẽ tạo một check box có chức năng ẩn/hiện thanh trạng thái.

statusbar = new JLabel("Ready");
statusbar.setBorder(BorderFactory.createEtchedBorder());
add(statusbar, BorderLayout.SOUTH);   

Để tạo status bar thì chúng ta tạo một đối tượng JLabel rồi dùng phương thức JFrame.add() để thêm label này vào phía dưới cửa sổ chỉ định bằng lớp BorderLayout, ngoài ra ở đây chúng ta dùng thêm phương thức setBorder() để tạo viền xung quanh label.

JCheckBoxMenuItem showStatusBar = new JCheckBoxMenuItem("Show statubar");
showStatusBar.setMnemonic(KeyEvent.VK_S);
showStatusBar.setDisplayedMnemonicIndex(5);

Chúng ta thiết lập mnemonic cho check box, ngoài ra chúng ta chỉ định cho kí tự được gạch chân thông qua phương thức setDisplayedMnemonicIndex() vì trong đoạn text của check box có nhiều kí tự S.

showStatusBar.setSelected(true);

Chúng ta thiết lập cho check box mặc định là đã được check.

showStatusBar.addItemListener(new ItemListener() {

    @Override
    public void itemStateChanged(ItemEvent e) {        
        statusbar.setVisible(e.getStateChange() == ItemEvent.SELECTED)        
    }

});

Chúng ta tạo một listener cho check box để lắng nghe sự kiện thay đổi trạng thái check và thiết lập ẩn/hiện cho status bar.

Untitled

Tạo popup menu

Popup menu hay còn được gọi là menu ngữ cảnh là loại menu chỉ hiển thị khi chúng ta click chuột lên vùng trống của cửa sổ. Java Swing cung cấp lớp JPopupMenu để hỗ trợ tạo popupmenu.

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

public class PopupMenuEx extends JFrame {

    private JPopupMenu popupMenu;

    public PopupMenuEx() {

        initUI();
    }

    private void initUI() {

        popupMenu = new JPopupMenu();
 
        JMenuItem maxItem = new JMenuItem("Maximize");
        maxItem.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if(getExtendedState() != JFrame.MAXIMIZED_BOTH)
                    setExtendedState(JFrame.MAXIMIZED_BOTH);
            } 
        });
    
        popupMenu.add(maxItem);
 
        JMenuItem quitItem = new JMenuItem("Quit");
        quitItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
            System.exit(0);
            }
        });
 
        popupMenu.add(quitItem);
 
        addMouseListener(new MouseAdapter()
        {
            @Override
            public void mouseReleased(MouseEvent e)
            {
                if(e.getButton() == MouseEvent.BUTTON3)
                popupMenu.show(e.getComponent(), e.getX(), e.getY());
            }
        });

        setTitle("Menu Example");
        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }   

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            
            @Override
            public void run() {
                PopupMenuEx pm = new PopupMenuEx();
                pm.setVisible(true);
            }
        });
    }
}

Trong ví dụ này chúng ta tạo popup menu có 2 item là maximizequit. Chức năng maximize sẽ phóng to cửa sổ.

popupMenu = new JPopupMenu();

Đầu tiên chúng ta tạo một đối tượng JPopupMenu.

JMenuItem maxItem = new JMenuItem("Maximize");
maxItem.addActionListener(new ActionListener() {
    
    @Override
    public void actionPerformed(ActionEvent e) {

        if (getExtendedState() != JFrame.MAXIMIZED_BOTH) {
            setExtendedState(JFrame.MAXIMIZED_BOTH);
        }

    }
});

Chúng ta tạo item maximize mà tạo listener cho item này. Phương thức JFrame.setExtendedState(JFrame.MAXIMIZED_BOTH) sẽ phóng to cửa sổ lên mức tối đa, ngoài ra còn có các kiểu co dãn cửa sổ khác là NORMAL, ICONIFIEDMAXIMIZED_HORIZ, và MAXIMIZED_VERT.

popupMenu.add(maxItem);

Sau đó chúng ta thêm item vào menu như thường.

addMouseListener(new MouseAdapter() {

    @Override
    public void mouseReleased(MouseEvent e) {

        if (e.getButton() == MouseEvent.BUTTON3) {
            popupMenu.show(e.getComponent(), e.getX(), e.getY());
        }
    }
});

Chúng ta dùng phương thức show() để hiển thị popup menu tại vị trí mà chúng ta muốn, ở đây mình tạo một listener cho cửa sổ chính và xử lý sự kiện thả chuột (sau khi nhấn chuột rồi thả ra thì phương thức mouseReleased() sẽ được gọi, chúng ta có thể lấy tọa độ của chuột cũng như các thông tin khác trong tham số MouseEvent).

Untitled

Tạo toolbar

Nếu như menu gộp nhóm các nút chức năng lại với nhau thì toolbar hiển thị các nút chức năng một cách độc lập để người dùng có thể thao tác một cách nhanh nhất. Java Swing cung cấp lớp JToolbar hỗ trợ tạo toolbar.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JToolBar;


public class ToolbarEx extends JFrame {

    public ToolbarEx() {
        
        initUI();
    }

    private void initUI() {
        
        JMenuBar menubar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        menubar.add(fileMenu);
        setJMenuBar(menubar);
 
        JToolBar toolbar = new JToolBar();
 
        ImageIcon icon = new ImageIcon("F:/exit.png");
 
        JButton exitButton = new JButton(icon);
        toolbar.add(exitButton);
 
        exitButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                System.exit(0);
            }
        });
 
        add(toolbar, BorderLayout.NORTH);
 
        setTitle("Menu Example");
        setSize(300, 300);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);        
    }

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            
            @Override
            public void run() {
                ToolbarEx ex = new ToolbarEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta sẽ tạo một toolbar có một button.

JToolBar toolbar = new JToolBar();

Đầu tiên là tạo một đối tượng JToolbar.

JButton exitButton = new JButton(icon);
toolbar.add(exitButton);

Tiếp theo chúng ta tạo một đối tượng JButton rồi thêm vào toolbar.

add(toolbar, BorderLayout.NORTH);

Chúng ta hiển thị toolbar lên cửa sổ với phương thức add(), tham số BorderLayout.NORTH hiển thị toolbar ở phía trên cửa sổ.

Untitled

Java Swing – Ví dụ mở đầu

Trong bài này chúng ta sẽ viết một số chương trình nhỏ để làm quen với Swing.

Ví dụ 1

Trong ví dụ dưới đây chúng ta sẽ hiển thị một cửa sổ lên màn hình.

import java.awt.EventQueue;
import javax.swing.JFrame;

public class SimpleEx extends JFrame {

    public SimpleEx() {

        initUI();
    }

    private void initUI() {
        
        setTitle("Simple example");
        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
        
            @Override
            public void run() {
                SimpleEx ex = new SimpleEx();
                ex.setVisible(true);
            }
        });
    }
}

Bạn có thể thay đổi kích thước, phóng to, thu nhỏ cửa sổ bằng chuột… mặc định thì những thứ như thế phải code rất nhiều nếu bạn dùng thư viện của hệ thống (như Windows API viết bằng C), tuy nhiên ở đây Swing tự động làm các công việc đó cho bạn rồi nên đoạn code ở trên khá đơn giản.

public class SimpleEx extends JFrame {

Lớp SimpleEx được kế thừa từ component JFrame, đây là một component đặc biệt dùng để chứa các component khác.

Lưu ý: các lớp tạo nên toàn bộ mọi thứ trong swing có tên chung là Component, tiếng việt có nghĩa là thành phần, nhưng trong series này mình sẽ không dùng tiếng việt mà dùng từ component luôn.

public SimpleEx() {

    initUI();
}

Theo kinh nghiệm lập trình của mình thì tốt nhất là chúng ta không nên để các đoạn code trong phương thức khởi tạo mà nên di chuyển chúng ta một phương thức khác dành riêng cho từng loại công việc.

setTitle("Simple example");

Phương thức setTitle() sẽ thay đổi tiêu đề của cửa sổ.

setSize(300, 200);

Phương thức setSize() thay đổi kích thước cửa sổ.

setLocationRelativeTo(null);

Dòng code trên có tác dụng hiển thị cửa sổ lên vị trí giữa màn hình.

setDefaultCloseOperation(EXIT_ON_CLOSE);

Dòng code trên thiết lập việc tắt chương trình khi click nào nút X trên thanh tiêu đề. Nếu bạn không thêm dòng đó vào thì khi bạn bấm nút X, cửa sổ chương trình vẫn sẽ biến mất nhưng bản thân chương trình thì vẫn chạy ngầm bên dưới chứ không tắt hẳn.

EventQueue.invokeLater(new Runnable() {
    @Override
    public void run() {
        SimpleExample ex = new SimpleExample();
        ex.setVisible(true);
    }
});

Phương thức invokeLater() sẽ chạy ứng dụng của chúng ta trong một luồng do EventQueue quản lý. Thực ra bạn cũng không cần đến EventQueue làm gì, chỉ cần tạo một đối tượng BasicEx rồi setVisible(true) là có thể chạy được nhưng trên tài liệu của Oracle thì lại khuyên chúng ta đặt bên trong EventQueue vì lý do là làm như vậy sẽ đảm bảo an toàn cho ứng dụng (mình cũng không hiểu tại sao) nên thôi thì mình cứ làm vậy 🙂

Capture

Ví dụ 2

Trong ví dụ này, chúng ta sẽ làm việc với Button.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;

public class QuitButtonEx extends JFrame {

    public QuitButtonEx() {

        initUI();
    }

    private void initUI() {

        JButton quitButton = new JButton("Quit");

        quitButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                System.exit(0);
            }
        });

        createLayout(quitButton);

        setTitle("Quit button");
        setSize(300, 200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void createLayout(JComponent... arg) {

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
        );
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
        
            @Override
            public void run() {
                QuitButtonEx ex = new QuitButtonEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta đặt một JButton lên cửa sổ và gắn một ActionListener vào button này.

JButton quitButton = new JButton("Quit");

Dòng code trên tạo một đối tượng JButton với tham số là đoạn text hiển thị trên button đó.

quitButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent event) {
        System.exit(0);
    }
});

Chúng ta gắn một ActionListener vào button, phương thức actionPerformed() sẽ được gọi mỗi lần chúng ta click vào button, bên trong phương thức chúng ta gọi System.exit() để thoát chương trình.

createLayout(quitButton);

Khi các component con được tạo xong thì chúng ta tiến hành đưa chúng lên cửa sổ chính, tại đây mình chuyển đoạn code làm công việc đó qua phương thức createLayout().

Container pane = getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);

Khu vực bên trong khung cửa sổ JFrame được gọi là pane hoặc panel, đây là vùng để chúng ta đặt các component vào đó. Vùng này được quản lý bởi các đối tượng layout (chúng ta sẽ tìm hiểu về quản lý Layout trong các bài viết sau), mặc định thì lớp layout chính quản lý cửa sổ khi mới tạo ra là BorderLayout, các chức năng của lớp này rất hạn chế nên ở đây mình dùng đến lớp GroupLayout.

gl.setAutoCreateContainerGaps(true);

Phương thức setAutoCreateContainerGaps() sẽ tự động tạo các khoảng trống phù hợp giữa các component và giữa các component với khung cửa sổ.

private void createLayout(JComponent... arg)
{
...
}

Lưu ý ở đây mình dùng cú pháp varargs để truyền tham số vào phương thức, cú pháp này cho phép chúng ta truyền vào số lượng tham số bất kỳ. Trong cú pháp này, tên của kiểu dữ liệu sẽ có 3 dấu chấm đằng sau nó, biến truyền vào sẽ trở thành một mảng. Bạn có thể tìm hiểu thêm về cú pháp varargs tại đây.

gl.setHorizontalGroup(gl.createSequentialGroup()
        .addComponent(quitButton)
);

gl.setVerticalGroup(gl.createSequentialGroup()
        .addComponent(quitButton)
);

GroupLayout sắp xếp các component theo chiều ngang và chiều dọc của cửa sổ. Nhưng ở đây chúng ta chỉ có một component là JButton nên cũng chưa thấy nhiều tiện ích cho lắm.

Capture

Ví dụ 3

Trong ví dụ này chúng ta sẽ cho hiển thị tooltip.

import java.awt.EventQueue;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;


public class TooltipEx extends JFrame {

    public TooltipEx() {
        
        initUI();
    }

    private void initUI() {

        JButton btn = new JButton("Button");
        btn.setToolTipText("A button component");

        createLayout(btn);
        
        setTitle("Tooltip");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
    private void createLayout(JComponent... arg) {
        
        JPanel pane = (JPanel) getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);
        
        pane.setToolTipText("Content pane");        
        
        gl.setAutoCreateContainerGaps(true);
        
        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(200)
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(120)
        );
        
        pack();        
    }

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
        
            @Override
            public void run() {
                TooltipEx ex = new TooltipEx();
                ex.setVisible(true);
            }
        });
    }
}

Tooltip là các đoạn text mô tả được hiện lên bên cạnh các component. Trong ví dụ này, mỗi khi di chuột vào button hoặc lên vùng trống của sổ, một tooltip sẽ hiện ra.

btn.setToolTipText("A button component");

Để cài đặt tooltip cho bất cứ component nào thì chúng ta dùng phương thức setTooltipText().

JPanel pane = (JPanel) getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);

Như đã nói ở trên, vùng trống trên cửa sổ được gọi là panel, để lấy đối tượng panel thì chúng ta dùng phương thức getContentPane(), tuy nhiên phương thức này trả về lớp Container, lớp này không có sẵn phương thức nào để làm việc với tooltip nên chúng ta chuyển đổi kiểu dữ liệu sang lớp JPanel.

Capture

pane.setToolTipText("Content pane");

Để tạo tooltip thì chỉ đơn giản là gọi phương thức setToolTipText() là được.

gl.setHorizontalGroup(gl.createSequentialGroup()
        .addComponent(arg[0])
        .addGap(200)
);

gl.setVerticalGroup(gl.createSequentialGroup()
        .addComponent(arg[0])
        .addGap(120)
);

Phương thức addGap() tạo khoảng trống xung quanh một đối tượng theo cả chiều ngang và chiều dọc.

pack();

Phương thức pack() sẽ tự động thay đổi kích thước của JFrame dựa trên kích thước của các component mà nó chứa kể cả các vùng được định nghĩa thêm như khoảng trống từ phương thức addGap().

Untitled

Ví dụ 4

Trong ví dụ này chúng ta sẽ tạo phím tắt cho các đối tượng, trong Java có hai cách thiết lập phím tắt cho đối tượng là dùng Mnemonics hoặc dùng KeyBinding, ở đây chúng ta sẽ dùng mnemonics.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;


public class MnemonicEx extends JFrame {
    
    public MnemonicEx() {
        
        initUI();
    }
    
    private void initUI() {
        
        JButton btn = new JButton("Button");
        btn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Button pressed");
            }
            
        });
        
        btn.setMnemonic(KeyEvent.VK_B);
        
        createLayout(btn);
        
        setTitle("Mnemonics");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);        
    }
    
    private void createLayout(JComponent... arg) {
        
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);        

        gl.setAutoCreateContainerGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(arg[0])
                .addGap(200)
        );

        gl.setVerticalGroup(gl.createParallelGroup()
                .addComponent(arg[0])
                .addGap(200)
        );       

        pack();
    }        

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            
            @Override
            public void run() {
                MnemonicEx ex = new MnemonicEx();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một button và một listener gắn vào button đó, sau đó chúng ta thiết lập phím tắt cho button này là tổ hợp Alt+B.

btn.setMnemonic(KeyEvent.VK_B);

Phương thức setMnemonic() thiết lập phím tắt cho button, phương thức này mặc nhiên thiết lập phím tắt là phím Alt với phím do chúng ta chọn trong lớp KeyEvent.

Java Swing – Giới thiệu

Thư viện Swing là một bộ toolkit được phát hành bởi Sun Microsystems với mục đích hỗ trợ việc tạo giao diện đồ họa người dùng với Java.

Một số đặc điểm chính của Swing:

  • Độc lập với thiết bị
  • Có thể tùy chỉnh
  • Có thể mở rộng
  • Có thể cấu hình
  • Khá nhẹ

Toàn bộ thư viện Swing có tổng cộng 18 package sau:

  • javax.accessibility
  • javax.swing
  • javax.swing.border
  • javax.swing.colorchooser
  • javax.swing.event
  • javax.swing.filechooser
  • javax.swing.plaf
  • javax.swing.plaf.basic
  • javax.swing.plaf.metal
  • javax.swing.plaf.multi
  • javax.swing.plaf.synth
  • javax.swing.table
  • javax.swing.text
  • javax.swing.text.html
  • javax.swing.text.html.parser
  • javax.swing.text.rtf
  • javax.swing.tree
  • javax.swing.undo

Swing là một bộ cung cụ cao cấp, cung cấp rất nhiều widget từ cơ bản như Button, Label cho đến nâng cao như Tree, Table…

Swing là một phần của Java Foundation Classes (JFC), đây là một tập các gói thư viện hỗ trợ đầy đủ cho veiecj tạo các ứng dụng desktop. Các thư viện khác ngoài Swing trong JFC là AWT, Accessibility, Java 2D, và Drag and Drop.  Swing được phát hành vào năm 1997 đi kèm với JDK 1.2, do đó đây là một toolkit khá lớn tuổi.

Thư viện SWT

Java còn có một bộ thư viện khác hỗ trợ lập trình GUI là Standard Widget Toolkit (SWT). Thư viện SWT ban đầu được phát triển bởi tập đoàn IBM nhưng bây giờ thì nó đã trở thành một dự án mã nguồn mở được duy trì bởi cộng đồng Eclipse.