Category Archives: Java

Java – Kết nối cơ sở dữ liệu MySQL – Phần 1

Trong bài này chúng ta sẽ học cách kết nối cơ sở dữ liệu MySQL.

JDBC

JDBC là một tập các hàm API cho phép kết nối đến cơ sở dữ liệu, cũng như thực hiện các câu truy vấn, cập nhật trên cơ sở dữ liệu. JDBC được phát triển dành riêng cho cơ sở dữ liệu quan hệ. JDBC nằm trong gói java.sql.

Cơ sở dữ liệu MySQL

Bạn tất nhiên là phải có MySQL cài đặt trong máy rồi. Và ngoài ra bạn cũng cần phải có driver tương ứng với từng môi trường nữa, chẳng hạn như ODBC là driver cho môi trường Windows, Linux, và MacOSX, ADO.NET là driver cho môi trường .NET… còn đối với Java thì driver là JDBC. Nếu bạn dùng IDE là NetBeans thì JDBC đã có sẵn trong máy bạn rồi. Còn các IDE khác thì bạn phải lên trang chủ của MySQL download về cài vào. Ở đây mình dùng NetBeans.

Để sử dụng JDBC thì bạn click chuột phải vào Libraries trong project của bạn rồi chọn MySQL JDBC Driver và bấm Add Library.

Capture

Bây giờ bạn cần có một database để test. Bạn chạy MySQL lên rồi tạo một database mới và đặt tên tùy ý mình..

CREATE DATABASE test;
Query OK, 1 row affected (0.02 sec)

Ở đây mình tạo database với tên là test. Sau khi đã có CSDL rồi thì chúng ta bắt đầu kết nối và thao tác với CSDL từ Java.

Kết nối và xem phiên bản MySQL

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Version {

    public static void main(String[] args) {

        Connection con = null;
        Statement st = null;
        ResultSet rs = null;

        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "";

        try {
            con = DriverManager.getConnection(url, user, password);
            st = con.createStatement();
            rs = st.executeQuery("SELECT VERSION()");

            if (rs.next()) {
                System.out.println(rs.getString(1));
            }

        } catch (SQLException ex) {
            ex.printStackTrace();

        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (st != null) {
                    st.close();
                }
                if (con != null) {
                    con.close();
                }

            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }
}

Chúng ta truy vấn phiên bản MySQL trên máy của mình.

String url = "jdbc:mysql://localhost:3306/testdb";

Để kết nối đến CSDL thì chúng ta cần có một đường dẫn url theo quy định. Cú pháp của đường dẫn này bao gồm jdbc:mysql:// tiếp theo là địa chỉ của máy tính, số hiệu cổng 3306, và tên của CSDL mà mình muốn kết nối.

con = DriverManager.getConnection(url, user, password);

Để thực hiện kết nối thì chúng ta gọi DriverManager.getConnection() và đưa vào tham số url, user và password của MySQL khi bạn cài đặt.

st = con.createStatement();

Phương thức createStatement() tạo ra một đối tượng lớp Statement, lớp này có nhiệm vụ gửi các câu truy vấn lên cơ sở dữ liệu.

rs = st.executeQuery("SELECT VERSION()");

Phương thức executeQuery() của lớp Statement sẽ thực thi các câu truy vấn SQL và trả về một đối tượng ResultSet, đây chính là bảng dữ liệu mà CSDL trả về.

if (result.next()) {
    System.out.println(result.getString(1));
}

Bên trong đối tượng ResultSet có một con trỏ chỉ đến các hàng trong bảng dữ liệu, mặc định thì con trỏ này trỏ đến trước hàng đầu tiên một hàng. Phương thức next() sẽ di chuyển con trỏ lên 1 hàng nếu có, nếu không còn hàng nào thì phương thức này trả về false. Phương thức getString() lấy giá trị của hàng hiện tại với tham số cột trong bảng. Các cột được đánh số từ 1.

try {
    if (rs != null) {
        rs.close();
    }
    if (st != null) {
        st.close();
    }
    if (con != null) {
        con.close();
    }
...

Sau khi đã kết thúc truy vấn thì chúng ta phải đóng kết nối tới CSDL.

10.1.9-MariaDB

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.

Java 2D – Trò chơi xếp hình – Tetris

Trong phần này chúng ta sẽ viết một game rất nổi tiếng đó là game Xếp hình 😀 (Tetris)

Tetris

Game được thiết kế và phát triển lần đầu tiên vào năm 1985 bởi một lập trình viên người Nga tên là Alexey Pajitnov. Kể từ đó, game này được phát triển thành nhiều phiên bản trên nhiều hệ máy khác nhau.

Trong game có 7 khối gạch có dạng hình chữ S, hình chữ Z, hình chữ T, hình chữ L, hình đường thẳng, hình chữ L ngược và hình vuông. Mỗi khối gạch được tạo nên bởi 4 hình vuông. Các khối gạch sẽ rơi từ trên xuống. Người chơi phải di chuyển và xoay các khối gạch sao cho chúng vừa khít với nhau, nếu các khối gạch lấp đầy một đường ngang thì người chơi ghi điểm. Trò chơi kết thúc khi các khối gạch chồng lên nhau quá cao.

tetrominoes

 

Mô tả game

Kích thước của các đối tượng trong game này được tính theo đơn vị 1 hình vuông chứ không theo pixel nữa. Kích thước của cửa sổ giờ đây là 10×22 hình vuông. Kích thước thật của các hình vuông được tính động theo kích thước cửa sổ. Tức là nếu kích thước cửa sổ là 200×400 pixel thì kích thước thật của môi hình vuông là 200×400 / 10×22 là khoảng 20×18 pixel (nhưng chúng ta cũng không quan tâm đến con số này).

Mỗi lần chạy chương trình là game sẽ tự động chạy ngay, tuy nhiên chúng ta có thể cho tạm dừng game bằng cách bấm phím P. Nếu muốn khối gạch rơi nhanh xuống đáy ngay lập tức thì bấm phím Space. Bấm phím D sẽ làm cho khối gạch rơi xuống thêm 1 hàng, có thể dùng phím này để tăng tốc độ rơi thêm một chút. Tốc độ game không thay đổi. Điểm sẽ là số hàng đã được loại bỏ.

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;


public class Tetris extends JFrame {

    private JLabel statusbar;

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

        statusbar = new JLabel(" 0");
        add(statusbar, BorderLayout.SOUTH);
        Board board = new Board(this);
        add(board);
        board.start();

        setSize(200, 400);
        setTitle("Tetris");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);       
   }

   public JLabel getStatusBar() {
       
       return statusbar;
   }

    public static void main(String[] args) {

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

Toàn bộ game này chúng ta viết trong 3 file, đầu tiên là file Tetris.java, file này là file main, làm các công việc khởi tạo cửa sổ, khởi tạo kích thước, chạy luồng main…

board.start();

Chúng ta gọi phương thức start() để khi chương trình chạy thì game cũng chạy luôn.

import java.util.Random;

public class Shape {

    protected enum Tetrominoes { NoShape, ZShape, SShape, LineShape, 
               TShape, SquareShape, LShape, MirroredLShape };

    private Tetrominoes pieceShape;
    private int coords[][];
    private int[][][] coordsTable;


    public Shape() {

        coords = new int[4][2];
        setShape(Tetrominoes.NoShape);
    }

    public void setShape(Tetrominoes shape) {

         coordsTable = new int[][][] {
            { { 0, 0 },   { 0, 0 },   { 0, 0 },   { 0, 0 } },
            { { 0,-1 },   { 0, 0 },   {-1, 0 },   {-1, 1 } },
            { { 0,-1 },   { 0, 0 },   { 1, 0 },   { 1, 1 } },
            { { 0,-1 },   { 0, 0 },   { 0, 1 },   { 0, 2 } },
            { {-1, 0 },   { 0, 0 },   { 1, 0 },   { 0, 1 } },
            { { 0, 0 },   { 1, 0 },   { 0, 1 },   { 1, 1 } },
            { {-1,-1 },   { 0,-1 },   { 0, 0 },   { 0, 1 } },
            { { 1,-1 },   { 0,-1 },   { 0, 0 },   { 0, 1 } }
        };

        for (int i = 0; i < 4 ; i++) {
            
            for (int j = 0; j < 2; ++j) {
                
                coords[i][j] = coordsTable[shape.ordinal()][i][j];
            }
        }
        
        pieceShape = shape;
    }

    private void setX(int index, int x) { coords[index][0] = x; }
    private void setY(int index, int y) { coords[index][1] = y; }
    public int x(int index) { return coords[index][0]; }
    public int y(int index) { return coords[index][1]; }
    public Tetrominoes getShape()  { return pieceShape; }

    public void setRandomShape() {
        
        Random r = new Random();
        int x = Math.abs(r.nextInt()) % 7 + 1;
        Tetrominoes[] values = Tetrominoes.values(); 
        setShape(values[x]);
    }

    public int minX() {
        
      int m = coords[0][0];
      
      for (int i=0; i < 4; i++) {
          
          m = Math.min(m, coords[i][0]);
      }
      
      return m;
    }


    public int minY() {
        
      int m = coords[0][1];
      
      for (int i=0; i < 4; i++) {
          
          m = Math.min(m, coords[i][1]);
      }
      
      return m;
    }

    public Shape rotateLeft() {
        
        if (pieceShape == Tetrominoes.SquareShape)
            return this;

        Shape result = new Shape();
        result.pieceShape = pieceShape;

        for (int i = 0; i < 4; ++i) {
            
            result.setX(i, y(i));
            result.setY(i, -x(i));
        }
        
        return result;
    }

    public Shape rotateRight() {
        
        if (pieceShape == Tetrominoes.SquareShape)
            return this;

        Shape result = new Shape();
        result.pieceShape = pieceShape;

        for (int i = 0; i < 4; ++i) {

            result.setX(i, -y(i));
            result.setY(i, x(i));
        }
        
        return result;
    }
}

Tiếp theo là file Shape.java, file này lưu thông tin về các khối gạch.

protected enum Tetrominoes { NoShape, ZShape, SShape, LineShape, 
            TShape, SquareShape, LShape, MirroredLShape };

Chúng ta định nghĩa tên các khối gạch trong enum Tetrominoes. Ngoài 7 khối cơ bản chúng ta tạo thêm 1 mối đặc biệt nữa là NoShape, khối này không có hình thù.

public Shape() {

    coords = new int[4][2];
    setShape(Tetrominoes.NoShape);
}

Trong phương thức khởi tạo, chúng ta tạo mảng coords để lưu giữ tọa độ thật của khối gạch.

coordsTable = new int[][][] {
   { { 0, 0 },   { 0, 0 },   { 0, 0 },   { 0, 0 } },
   { { 0, -1 },  { 0, 0 },   { -1, 0 },  { -1, 1 } },
   { { 0, -1 },  { 0, 0 },   { 1, 0 },   { 1, 1 } },
   { { 0, -1 },  { 0, 0 },   { 0, 1 },   { 0, 2 } },
   { { -1, 0 },  { 0, 0 },   { 1, 0 },   { 0, 1 } },
   { { 0, 0 },   { 1, 0 },   { 0, 1 },   { 1, 1 } },
   { { -1, -1 }, { 0, -1 },  { 0, 0 },   { 0, 1 } },
   { { 1, -1 },  { 0, -1 },  { 0, 0 },   { 0, 1 } }
};

Mảng coordsTable lưu tọa độ của các hình vuông tạo nên từng loại khối gạch.

for (int i = 0; i < 4 ; i++) {
    
    for (int j = 0; j < 2; ++j) {
        
        coords[i][j] = coordsTable[shape.ordinal()][i][j];
    }
}

Phương thức setShape() sẽ quy định cho đối tượng khối gạch đó là loại nào, tham số của phương thức này là một giá trị của enum Tetrominoes, từ tham số đó chúng ta lấy số thứ tự của thuộc tính đó bằng phương thức ordinal(), rồi dùng số thứ tự đó để lấy tọa độ trong mảng coordsTable và chép vào mảng coords.

Lý do tại sao tọa độ ở đây chỉ là 0, 1 và -1 thì bạn xem hình dưới đây để hình dung. Ví dụ { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, -1 } , sẽ đại diện cho khối gạch hình chữ S.

Coordinates
public Shape rotateLeft() {
    
    if (pieceShape == Tetrominoes.SquareShape)
        return this;

    Shape result = new Shape();
    result.pieceShape = pieceShape;

    for (int i = 0; i < 4; ++i) {
        
        result.setX(i, y(i));
        result.setY(i, -x(i));
    }
    
    return result;
}

Phương thức rotateLeft() làm nhiệm vụ xoay khối gạch qua bên trái. Ở đây mình sẽ không giải thích vì sao chúng ta lại có đoạn code xoay như trên vì rất khó giải thích, bạn cứ nhìn hình ở trên rồi tự suy ra sẽ thấy, nếu thắc mắc chỗ nào thì hãy comment ở cuối bài này và mình sẽ giải thích. Phương thức rotateRight() cũng tương tự là xoay khối gạch sang bên phải.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

import Shape.Tetrominoes;

public class Board extends JPanel 
        implements ActionListener {

    private final int BoardWidth = 10;
    private final int BoardHeight = 22;

    private Timer timer;
    private boolean isFallingFinished = false;
    private boolean isStarted = false;
    private boolean isPaused = false;
    private int numLinesRemoved = 0;
    private int curX = 0;
    private int curY = 0;
    private JLabel statusbar;
    private Shape curPiece;
    private Tetrominoes[] board;

    public Board(Tetris parent) {

        initBoard(parent);
    }
    
    private void initBoard(Tetris parent) {
        
       setFocusable(true);
       curPiece = new Shape();
       timer = new Timer(400, this);
       timer.start(); 

       statusbar =  parent.getStatusBar();
       board = new Tetrominoes[BoardWidth * BoardHeight];
       addKeyListener(new TAdapter());
       clearBoard();          
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        
        if (isFallingFinished) {
            
            isFallingFinished = false;
            newPiece();
        } else {
            
            oneLineDown();
        }
    }

    private int squareWidth() { return (int) getSize().getWidth() / BoardWidth; }
    private int squareHeight() { return (int) getSize().getHeight() / BoardHeight; }
    private Tetrominoes shapeAt(int x, int y) { return board[(y * BoardWidth) + x]; }


    public void start()  {
        
        if (isPaused)
            return;

        isStarted = true;
        isFallingFinished = false;
        numLinesRemoved = 0;
        clearBoard();

        newPiece();
        timer.start();
    }

    private void pause()  {
        
        if (!isStarted)
            return;

        isPaused = !isPaused;
        
        if (isPaused) {
            
            timer.stop();
            statusbar.setText("paused");
        } else {
            
            timer.start();
            statusbar.setText(String.valueOf(numLinesRemoved));
        }
        
        repaint();
    }
    
    private void doDrawing(Graphics g) {
        
        Dimension size = getSize();
        int boardTop = (int) size.getHeight() - BoardHeight * squareHeight();

        for (int i = 0; i < BoardHeight; ++i) {
            
            for (int j = 0; j < BoardWidth; ++j) {
                
                Tetrominoes shape = shapeAt(j, BoardHeight - i - 1);
                
                if (shape != Tetrominoes.NoShape)
                    drawSquare(g, 0 + j * squareWidth(),
                               boardTop + i * squareHeight(), shape);
            }
        }

        if (curPiece.getShape() != Tetrominoes.NoShape) {
            
            for (int i = 0; i < 4; ++i) { 
                int x = curX + curPiece.x(i); 
                int y = curY - curPiece.y(i); 
                drawSquare(g, 0 + x * squareWidth(), boardTop + (BoardHeight - y - 1) * squareHeight(), curPiece.getShape()); 
            } 
        } 
    } 
    
    @Override 
    public void paintComponent(Graphics g) { 
        super.paintComponent(g); 
        doDrawing(g); 
    } 

    private void dropDown() { 
        int newY = curY; 
        while (newY > 0) {
            
            if (!tryMove(curPiece, curX, newY - 1))
                break;
            --newY;
        }
        
        pieceDropped();
    }

    private void oneLineDown()  {
        
        if (!tryMove(curPiece, curX, curY - 1))
            pieceDropped();
    }


    private void clearBoard() {
        
        for (int i = 0; i < BoardHeight * BoardWidth; ++i)
            board[i] = Tetrominoes.NoShape;
    }

    private void pieceDropped() {
        
        for (int i = 0; i < 4; ++i) {
            
            int x = curX + curPiece.x(i);
            int y = curY - curPiece.y(i);
            board[(y * BoardWidth) + x] = curPiece.getShape();
        }

        removeFullLines();

        if (!isFallingFinished)
            newPiece();
    }

    private void newPiece()  {
        
        curPiece.setRandomShape();
        curX = BoardWidth / 2 + 1;
        curY = BoardHeight - 1 + curPiece.minY();

        if (!tryMove(curPiece, curX, curY)) {
            
            curPiece.setShape(Tetrominoes.NoShape);
            timer.stop();
            isStarted = false;
            statusbar.setText("game over");
        }
    }

    private boolean tryMove(Shape newPiece, int newX, int newY) {
        
        for (int i = 0; i < 4; ++i) {
            
            int x = newX + newPiece.x(i);
            int y = newY - newPiece.y(i);
            
            if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
                return false;
            
            if (shapeAt(x, y) != Tetrominoes.NoShape)
                return false;
        }

        curPiece = newPiece;
        curX = newX;
        curY = newY;

        repaint();

        return true;
    }

    private void removeFullLines() {
        
        int numFullLines = 0;

        for (int i = BoardHeight - 1; i >= 0; --i) {
            boolean lineIsFull = true;

            for (int j = 0; j < BoardWidth; ++j) {
                if (shapeAt(j, i) == Tetrominoes.NoShape) {
                    lineIsFull = false;
                    break;
                }
            }

            if (lineIsFull) {
                ++numFullLines;
                for (int k = i; k < BoardHeight - 1; ++k) {
                    for (int j = 0; j < BoardWidth; ++j) 
                        board[(k * BoardWidth) + j] = shapeAt(j, k + 1); 
                } 
            } 
        } 
        if (numFullLines > 0) {
            
            numLinesRemoved += numFullLines;
            statusbar.setText(String.valueOf(numLinesRemoved));
            isFallingFinished = true;
            curPiece.setShape(Tetrominoes.NoShape);
            repaint();
        }
     }

    private void drawSquare(Graphics g, int x, int y, Tetrominoes shape)  {
        
        Color colors[] = { new Color(0, 0, 0), new Color(204, 102, 102), 
            new Color(102, 204, 102), new Color(102, 102, 204), 
            new Color(204, 204, 102), new Color(204, 102, 204), 
            new Color(102, 204, 204), new Color(218, 170, 0)
        };

        Color color = colors[shape.ordinal()];

        g.setColor(color);
        g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);

        g.setColor(color.brighter());
        g.drawLine(x, y + squareHeight() - 1, x, y);
        g.drawLine(x, y, x + squareWidth() - 1, y);

        g.setColor(color.darker());
        g.drawLine(x + 1, y + squareHeight() - 1,
                         x + squareWidth() - 1, y + squareHeight() - 1);
        g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1,
                         x + squareWidth() - 1, y + 1);

    }

    class TAdapter extends KeyAdapter {
        
         @Override
         public void keyPressed(KeyEvent e) {

             if (!isStarted || curPiece.getShape() == Tetrominoes.NoShape) {  
                 return;
             }

             int keycode = e.getKeyCode();

             if (keycode == 'p' || keycode == 'P') {
                 pause();
                 return;
             }

             if (isPaused)
                 return;

             switch (keycode) {
                 
             case KeyEvent.VK_LEFT:
                 tryMove(curPiece, curX - 1, curY);
                 break;
                 
             case KeyEvent.VK_RIGHT:
                 tryMove(curPiece, curX + 1, curY);
                 break;
                 
             case KeyEvent.VK_DOWN:
                 tryMove(curPiece.rotateRight(), curX, curY);
                 break;
                 
             case KeyEvent.VK_UP:
                 tryMove(curPiece.rotateLeft(), curX, curY);
                 break;
                 
             case KeyEvent.VK_SPACE:
                 dropDown();
                 break;
                 
             case 'd':
                 oneLineDown();
                 break;
                 
             case 'D':
                 oneLineDown();
                 break;
             }
         }
     }
}

Cuối cùng chúng ta có file Board.java. File này chịu trách nhiệm điều khiển các khối gạch cũng như xử lý các hoạt động diễn ra trong game.

...
private boolean isFallingFinished = false;
private boolean isStarted = false;
private boolean isPaused = false;
private int numLinesRemoved = 0;
private int curX = 0;
private int curY = 0;
...

Ý nghĩa của các thuộc tính trên như sau:

  • isFallingFinished: cho biết khối gạch hiện tại đã rơi xuống xong chưa, nếu rồi thì tạo một khối gạch mới
  • isStartedisPaused: dùng để kiểm tra trạng thái hiện tại của game là đang chơi hay tạm dừng hay đã kết thúc
  • numLinesRemoved: lưu số lượng các đường ngang đã bị xóa
  • curXcurY: lưu tọa độ hiện tại của khối gạch đang rơi
setFocusable(true);

Chúng ta phải gọi phương thức setFocusable() một cách tường minh để bàn phím có thể nhận sự kiện bấm phím.

timer = new Timer(400, this);
timer.start(); 

Đối tượng Timer sẽ giải phóng sự kiện sau mỗi 400 mili-giây và gọi đến phương thức actionPerformed(), có thể hiểu đây là vòng lặp của game.

@Override
public void actionPerformed(ActionEvent e) {
    
    if (isFallingFinished) {
        
        isFallingFinished = false;
        newPiece();
    } else {
        
        oneLineDown();
    }
}

Trong phương thức actionPerformed(), chúng ta kiểm tra xem khối gạch đang rơi đã rơi xong chưa, nếu chưa thì chúng ta gọi phương thức oneLineDown() để cho khối gạch này rơi thêm 1 hàng, nếu đã rơi xong rồi thì chúng ta gọi phương thức newPiece() để tạo một khối gạch mới.

Trong phương thức doDrawing() chúng ta vẽ các đối tượng lên màn hình.

for (int i = 0; i < BoardHeight; ++i) {
    
    for (int j = 0; j < BoardWidth; ++j) {
        
        Tetrominoes shape = shapeAt(j, BoardHeight - i - 1);
        
        if (shape != Tetrominoes.NoShape)
            drawSquare(g, 0 + j * squareWidth(),
                        boardTop + i * squareHeight(), shape);
    }
}

Đầu tiên chúng ta vẽ các khối gạch đã rơi xuống đáy màn hình. Các khối gạch đã rơi xong sẽ được lưu lại vị trí trong mảng board, chúng ta lấy lại thông tin đó thông qua phương thức shapeAt().

if (curPiece.getShape() != Tetrominoes.NoShape) {
    
    for (int i = 0; i < 4; ++i) {
        
        int x = curX + curPiece.x(i);
        int y = curY - curPiece.y(i);
        drawSquare(g, 0 + x * squareWidth(),
                    boardTop + (BoardHeight - y - 1) * squareHeight(),
                    curPiece.getShape());
    }
}

Tiếp theo chúng ta vẽ khối gạch đang rơi.

private void dropDown() {
    
    int newY = curY;
    
    while (newY > 0) {
        
        if (!tryMove(curPiece, curX, newY - 1))
            break;
        --newY;
    }
    
    pieceDropped();
}

Nếu bấm phím space, khối gạch sẽ được rơi xuống đáy ngay lập tức bằng cách cho chạy một vòng lặp riêng.

private void clearBoard() {
    
    for (int i = 0; i < BoardHeight * BoardWidth; ++i)
        board[i] = Tetrominoes.NoShape;
}

Phương thức clearBoard() có nhiệm vụ lấp đầy cửa sổ bằng khối gạch NoShape, chúng ta làm thế để phục vụ việc kiểm tra va chạm sau này.

private void pieceDropped() {
    
    for (int i = 0; i < 4; ++i) {
        
        int x = curX + curPiece.x(i);
        int y = curY - curPiece.y(i);
        board[(y * BoardWidth) + x] = curPiece.getShape();
    }

    removeFullLines();

    if (!isFallingFinished)
        newPiece();
}

Phương thức pieceDropped() được gọi mỗi lần có một khối gạch đã rơi xong (chạm đáy hoặc chạm một khối gạch khác đã nằm sẵn), phương thức này sẽ thêm tọa độ mới của các khối gạch trong mảng board sau đó tiến hành kiểm tra xem có hàng nào full hay không thông qua phương thức removeFullLines(), cuối cùng khởi tạo một khối gạch mới (chúng ta thêm câu lệnh if để kiểm tra xem khối gạch chắc chắn đã rơi xong chưa).

private void newPiece()  {
    
    curPiece.setRandomShape();
    curX = BoardWidth / 2 + 1;
    curY = BoardHeight - 1 + curPiece.minY();

    if (!tryMove(curPiece, curX, curY)) {
        
        curPiece.setShape(Tetrominoes.NoShape);
        timer.stop();
        isStarted = false;
        statusbar.setText("game over");
    }
}

Phương thức newPiece() khởi tạo một khối gạch mới và tính toán vị trí cho khối gạch. Nếu vị trí khởi tạo của khối gạch không đủ vì phần gạch bên dưới đã quá cao thì chúng ta tiến hành cho game kết thúc.

private boolean tryMove(Shape newPiece, int newX, int newY) {
    
    for (int i = 0; i < 4; ++i) {
        
        int x = newX + newPiece.x(i);
        int y = newY - newPiece.y(i);
        
        if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
            return false;
        
        if (shapeAt(x, y) != Tetrominoes.NoShape)
            return false;
    }

    curPiece = newPiece;
    curX = newX;
    curY = newY;

    repaint();

    return true;
}

Phương thức tryMove() sẽ di chuyển khối gạch, phương thức này trả về false nếu khối gạch không thể di chuyển được do đã chạm biên cửa sổ hoặc chạm các khối gạch khác.

int numFullLines = 0;

for (int i = BoardHeight - 1; i >= 0; --i) {
    boolean lineIsFull = true;

    for (int j = 0; j < BoardWidth; ++j) {
        if (shapeAt(j, i) == Tetrominoes.NoShape) {
            lineIsFull = false;
            break;
        }
    }

    if (lineIsFull) {
        ++numFullLines;
        for (int k = i; k < BoardHeight - 1; ++k) {
            for (int j = 0; j < BoardWidth; ++j)
                    board[(k * BoardWidth) + j] = shapeAt(j, k + 1);
        }
    }
}

Phương thức removeFullLines() kiểm tra xem có hàng nào đã full hay chưa, nếu có thì chúng ta tăng một biến đếm lên để dùng vào việc tính điểm, đồng thời chúng ta kéo các hàng phía trên xuống một hàng, để đảm bảo tất cả các hàng full đều bị xóa.

Các khối gạch của game có màu khác nhau. Các khối gạch được vẽ trong phương thức drawSquare().

g.setColor(color.brighter());
g.drawLine(x, y + squareHeight() - 1, x, y);
g.drawLine(x, y, x + squareWidth() - 1, y);

Chúng ta vẽ các hình vuông có cạnh bên trái và cạnh trên màu sáng hơn với 2 cạnh còn lại để tạo hiệu ứng khối 3D.

Sự kiện bàn phím sẽ được nhận thông qua phương thức keyPressed() của lớp KeyAdapter.

case KeyEvent.VK_LEFT:
    tryMove(curPiece, curX - 1, curY);
    break;

Chẳng hạn như nếu bấm phím mũi tên trái, chúng ta thử di chuyển khối gạch sang bên trái.

Untitled