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 MySQLJDBCDriver và bấm AddLibrary.
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..
Để 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.
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
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.
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.
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().
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).
JColorChooser
Java Swingcung 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.
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, JTextArea và JTextPane.
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.
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.
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>
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.
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.
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().
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.
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.
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.
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().
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 ngheevent 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.
Để 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 ActionListener. Tứ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.
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.
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.
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.
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() và getY().
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);
}
});
}
}
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, JTree và JTextArea. 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.
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.
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.
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, Strut và RigidArea. 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.
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.
Đầ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).
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.
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.
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.
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().
Tạo menu con
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
public class SubmenuEx extends JFrame {
public SubmenuEx() {
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 saveAsIcon = new ImageIcon("F:/saveas.png");
ImageIcon exitIcon = new ImageIcon("F:/exit.png");
JMenu saveMenu = new JMenu("Save");
JMenuItem saveItem = new JMenuItem("Save", saveIcon);
JMenuItem saveAsItem = new JMenuItem("Save as...", saveAsIcon);
saveMenu.add(saveItem);
saveMenu.add(saveAsItem);
JMenu menuFile = new JMenu("File");
JMenuItem newItem = new JMenuItem("New", newIcon);
JMenuItem openItem = new JMenuItem("Open", openIcon);
JMenuItem exitItem = new JMenuItem("Exit", exitIcon);
exitItem.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
});
menuFile.add(newItem);
menuFile.add(openItem);
menuFile.add(saveMenu);
menuFile.addSeparator();
menuFile.add(exitItem);
menubar.add(menuFile);
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() {
SubmenuEx ex = new SubmenuEx();
ex.setVisible(true);
}
});
}
}
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.
Tạo phím tắt cho menu item
Trong Java có 2 kiểu phím tắt là Mnemonic và Accelerator, 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.
Để thiết lập phím tắt accelerator thì chúng ta dùng phương thức setAccelerator().
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.
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.
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.
Trong ví dụ này chúng ta tạo popup menu có 2 item là maximize và quit. 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, ICONIFIED, MAXIMIZED_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).
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ổ.
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 🙂
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ổ.
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.
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.
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().
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.
pane.setToolTipText("Content pane");
Để tạo tooltip thì chỉ đơn giản là gọi phương thức setToolTipText() là được.
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().
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.
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.
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, Java2D, 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.
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.
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.
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.
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.
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.
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
isStarted và isPaused: 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
curX và curY: 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).
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.