Giao diện người dùng (Graphical User Interface – GUI)

Một giao diện người dùng (gọi tắt là GUI) là một cửa sổ (window) chứa các điều khiển (controls) như button, text box, combo box, v.v. Người dùng tương tác với giao diện bằng cách dùng chuột, con trỏ hay bàn phím.

Java cho phép chúng ta tạo giao diện người dùng bằng cách sử dụng một trong hai gói là AWT hay Swing. AWT (Asbtract Window Toolkit) là tập con của Swing nên Swing được dùng chủ yếu nhưng trong chương trình Java phải import cả hai gói SwingAWT:


import java.awt.*;

import javax.swing.*;

Các điều khiển trong giao diện đồ họa dùng gói Swing luôn bắt đầu bằng chữ “J” ví dụ JLabel, JButton,v.v. Muốn tạo giao diện người dùng, một lớp phải kế thừa lớp JFrame như sau:


import java.awt.*;

import javax.swing.*;

public class MainClass extends JFrame {

public static void main(String[] args)  {

// TODO Auto-generated method stub

}

}

Đối tượng JFrame là một cửa sổ (window) cho phép chúng ta đóng, thay đổi kích thước và có thể đặt các điều khiển. Một cửa sổ có thể được khởi tạo trong phương thức khởi tạo của một lớp kế thừa lớp JFrame (ví dụ MainClass) như sau:


public MainClass() {

// thiết lập kích thước cửa sổ

this.setSize(640, 480);

// thiết lập thao tác đóng ứng dụng khi cửa sổ đóng

this.setDefaultCloseOperation(EXIT_ON_CLOSE);

// cho phép cửa sổ hiển thị

this.setVisible(true);

}

Chúng ta dùng phương thức setSize để thiết lập kích thước cho cửa sổ. Phương thức setDefaultCloseOperation cho phép đóng ứng dụng khi cửa sổ đóng, nếu không dùng phương thức này, ứng dụng vẫn tiếp tục chạy khi cửa sổ đã đóng. Phương thức setVisible cho phép hiển thị cửa sổ.

Bây giờ chỉ việc tạo một đối tượng cửa sổ giao diện:


MainClass mainWindow = new MainClass();

Đoạn mã hoàn chỉnh của lớp MainClass:


import java.awt.*;

import javax.swing.*;

public class MainClass extends JFrame {

public static void main(String[] args)  {

// TODO Auto-generated method stub

MainClass mainWindow = new MainClass();

}

public MainClass() {

// thiết lập kích thước cửa sổ

this.setSize(640, 480);

// thiết lập thao tác đóng cửa sổ

this.setDefaultCloseOperation(EXIT_ON_CLOSE);

// cho phép cửa sổ hiển thị

this.setVisible(true);

}

}

Kết quả khi thực thi:

Các điều khiển không thể đặt trực tiếp đến cửa sổ mà phải được đặt trong đối tượng JPanel. Như vậy, muốn thêm các điều khiển như button, text box, combo box, v.v. đến cửa sổ, đầu tiên chúng ta phải tạo một điều khiển JPanel:


JPanel panel = new JPanel(new FlowLayout());

Đối số cho phương thức khởi tạo của lớp JPanel là một đối tượng FlowLayout. Đây là một đối tượng tạo bố cục (layout) trên đối tượng JPanel. Một vài dạng bố cục phổ biến có thể được mô tả như bảng sau:

Kiểu bố cục Mô tả
BorderLayout Panel được phân chia thành trên (top), dưới (bottom), trái (left), phải (right) và giữa (center)
BoxLayout Đặt các điều khiển trong một cột đơn hay một hàng đơn
CardLayout Cho phép chúng ta chuyển đổi giữa các tập điều khiển
FlowLayout Sắp xếp các điều khiển trên một hàng đơn
GridBagLayout Tạo một bố cục dạng lưới và các điều khiển có thể chiếm giữ nhiều ô
GridLayout Tạo một bố cục dạng lưới
GroupLayout Tạo các bố cục ngang (horizontal) và dọc (vertical) tách biệt
SpringLayout Tạo bố cục cho phép các điều khiển có mối quan hệ theo vị trí của chúng.

Sau khi tạo một đối tượng JPanel với một layout, bước kế tiếp chúng ta sẽ thêm các điều khiển đến đối tượng JPanel này. Đoạn mã sau sẽ thêm điều khiển JLabelJButton đến đối tượng panel vừa tạo:


panel.add(new JLabel("Test Button: "));

panel.add(new JButton("Click me!"));

Bước cuối cùng là thiết lập nội dung cho panel với phương thức setContentPanel:


this.setContentPane(panel);

Đoạn mã hoàn chỉnh của lớp MainClass:


import java.awt.*;

import javax.swing.*;

public class MainClass extends JFrame {

public static void main(String[] args)  {

// TODO Auto-generated method stub

MainClass mainWindow = new MainClass();

}

public MainClass() {

// tạo một đối tượng JPanel

JPanel panel = new JPanel(new FlowLayout());

// Thêm hai điều khiển đến panel

panel.add(new JLabel("Test Button: "));

panel.add(new JButton("Click me!"));

// Thiết lập nội dung hiện tại cho panel

this.setContentPane(panel);

// thiết lập kích thước cửa sổ

this.setSize(640, 480);

// thiết lập thao tác đóng cửa sổ

this.setDefaultCloseOperation(EXIT_ON_CLOSE);

// cho phép cửa sổ hiển thị

this.setVisible(true);

}

}

Kết quả:

Các điều khiển là thành phần cơ bản của giao diện đồ họa người dùng và mỗi điều khiển sẽ có những mục đích khác nhau. Java cung cấp nhiều lớp với những phương thức cho những điều khiển riêng biệt và đồng thời cũng cung cấp những phương thức dành cho mọi điều khiển. Ví dụ một button có thể được định dạng qua các phương thức như sau:


JButton btn = new JButton("Initial Text");

btn.setText("New text!");

String text = btn.getText();

btn.setVisible(false);

btn.setVisible(true);

btn.setMargin(new Insets(100, 100, 100, 100));

Dimension dim = btn.getSize();

btn.setBackground(Color.BLUE);

btn.setForeground(Color.WHITE);

btn.setEnabled(false);

btn.setEnabled(true);

btn.setSize(new Dimension(10, 10));

btn.setBounds(new Rectangle(20, 20, 200, 60));

panel.add(btn);

Kết quả:

Sự kiện (events) và xử lý sự kiện (events handling)

Giao diện ActionListener

Sự kiện là các hành động của người dùng như nhấn phím, nhấp chuột, di chuyển chuột, v.v. hay các sự xuất hiện như hệ thống phát sinh một thông báo. Hệ thống cần đáp ứng các sự kiện khi chúng xảy ra, ví dụ hiển thị một thông báo khi người dủng nhấn chuột vào một button.

Để xử lý sự kiện khi chúng xuất hiện, chúng ta cần lắng nghe các sự kiện đó để đưa ra các hành động, ví dụ thực thi đoạn mã hiển thị một thông điệp, phù hợp. Trong Java chúng ta làm điều này bằng cách thực thi các phương thức trong giao diện ActionListener. Ngoài ActionListener, Swing còn cung cấp một danh sách các giao diện lắng nghe các sự kiện có thể tham khảo tại tutorialspoint.com.

Như vậy, một lớp giao diện đồ họa người dùng trong Java có hai đặc điểm cơ bản:

  • Kế thừa lớp JFrame
  • Thực thi giao diện ActionListener

Một cách hình dung trực quan như hình sau đây:

Lớp MainClass của chúng ta sẽ thực thi giao diện ActionListener như sau:


import java.awt.*;

import javax.swing.*;

public class MainClass extends JFrame implements ActionListener{

public static void main(String[] args)  {

// TODO Auto-generated method stub

MainClass mainWindow = new MainClass();

}

public MainClass() {

//...

}

}

Khi chúng ta gõ từ khóa implements ActionListener sẽ có lỗi trong như sau:

Nhấn dòng liên kết đầu tiên để import các lớp phù hợp:


import java.awt.event.ActionListener;

Lúc này lớp MainClass sẽ bị lỗi:

Vẫn nhấn vào dòng liên kết đầu tiên sẽ xuất hiện dòng mã import:


import java.awt.event.ActionEvent;

Và xuất hiện một phương thức actionPerformed trong lớp MainClass:


@Override

public void actionPerformed(ActionEvent e) {

// TODO Auto-generated method stub

}

Đoạn mã hoàn chỉnh cho lớp MainClass:


import java.awt.*;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import javax.swing.*;

public class MainClass extends JFrame implements ActionListener{

public static void main(String[] args)  {

// TODO Auto-generated method stub

MainClass mainWindow = new MainClass();

}

public MainClass() {

//...

}

@Override

public void actionPerformed(ActionEvent e) {

// TODO Auto-generated method stub

}

}

Bây giờ chúng ta thêm vào cửa sổ giao diện một JLabel và một JButton bằng cách thêm các đoạn mã sau vào phương thức khởi tạo của lớp MainClass:


// Thêm một JLabel

JLabel lbName = new JLabel("Test button: ");

panel.add(lbName);

//Thêm một JButton

JButton btnClickMe = new JButton("Click here to display a message!");

panel.add(btnClickMe);

Khi người dùng nhấn chuột vào button, một hộp thông điệp sẽ xuất hiện. Đoạn mã xử lý yêu cầu này phải được đặt trong phương thức actionPerformed và sử dụng lớp JOptionPane (xem lại chương III):


@Override

public void actionPerformed(ActionEvent e) {

// TODO Auto-generated method stub

// Hiển thị hộp thông điệp

JOptionPane.showMessageDialog(null,"You clicked on the button!");

}

Chúng ta muốn button lắng nghe và thực thi đoạn mã hiển thị hộp thông điệp. Để thực hiện điều này, chúng ta dùng phương thức addActionListener của đối tượng JButton như sau:


//lắng nghe sự kiện từ button

btnClickMe.addActionListener(this);

Đoạn mã hoàn chỉnh của lớp MainClass lúc này trông như sau:


import java.awt.*;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import javax.swing.*;

public class MainClass extends JFrame implements ActionListener{

public static void main(String[] args)  {

// TODO Auto-generated method stub

MainClass mainWindow = new MainClass();

}

public MainClass() {

// tạo một đối tượng JPanel

JPanel panel = new JPanel(new FlowLayout());

// Thêm một JLabel

JLabel lbName = new JLabel("Test button: ");

panel.add(lbName);

//Thêm một JButton

JButton btnClickMe = new JButton("Click here to display a message!");

panel.add(btnClickMe);

// Thiết lập nội dung hiện tại cho panel

this.setContentPane(panel);

//lắng nghe sự kiện từ button

btnClickMe.addActionListener(this);

// thiết lập kích thước cửa sổ

this.setSize(640, 480);

// thiết lập thao tác đóng cửa sổ

this.setDefaultCloseOperation(EXIT_ON_CLOSE);

// cho phép cửa sổ hiển thị

this.setVisible(true);

}

@Override

public void actionPerformed(ActionEvent e) {

// TODO Auto-generated method stub

// Hiển thị hộp thông điệp

JOptionPane.showMessageDialog(null,

"You clicked on the button!");

}

}

Kết quả:

Xử lý sự kiện cho nhiều điều khiển

Cách thức thực thi phương thức addPerformed trong lớp MainClass như trên có nhược điểm là chỉ thực thi cùng một đoạn mã cho các sự kiện của nhiều điều khiển. Giả sử rằng chúng ta có hai button và khi click chuột mỗi button sẽ xuất hiện những thông điệp khác nhau, thì với cách thực thi phương thức addPerformed như trên sẽ không hiệu quả. Có hai giải pháp:

  • Sử dụng lớp nặc danh (anonymous class)
  • Tạo các lớp thực thi addPerformed cho các yêu cầu khác nhau
Sử dụng lớp nặc danh

Chúng ta có thể thực thi phương thức addPerformed của giao diện ActionListener bằng cách dùng lớp nặc danh. Lúc này, các lớp nặc danh sẽ đóng vai trò tham số cho phương thức addActionListener của các điều khiển. Khi sử dụng lớp nặc danh, chúng ta không cần khai báo implements ActionListener tại lớp MainClass.

Thêm hai JLabel và hai JButton đến giao diện như sau:


public class MainClass extends JFrame {

public static void main(String[] args)  {

// TODO Auto-generated method stub

MainClass mainWindow = new MainClass();

}

public MainClass() {

// tạo một đối tượng JPanel

JPanel panel = new JPanel(new FlowLayout());

// Thêm một JLabel

JLabel label1 = new JLabel("Button 1: ");

panel.add(label1);

//Thêm button 1

JButton btn1 = new JButton("Click me!");

panel.add(btn1);

// Thêm một JLabel

JLabel label2 = new JLabel("Button 2: ");

panel.add(label2);

//Thêm button 2

JButton btn2 = new JButton("Click me!");

panel.add(btn2);

// Thiết lập nội dung hiện tại cho panel

this.setContentPane(panel);

// thiết lập kích thước cửa sổ

this.setSize(640, 480);

// thiết lập thao tác đóng cửa sổ

this.setDefaultCloseOperation(EXIT_ON_CLOSE);

// cho phép cửa sổ hiển thị

this.setVisible(true);

}

}

Kết quả:

Lắng nghe và xử lý sự kiện Click cho Button 1:


// Thực thi ActionListener dùng lớp nặc danh

btn1.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

// Show a message box.

JOptionPane.showMessageDialog(null,"I am button 1");

}

});

Lắng nghe và xử lý sự kiện Click cho Button 2:


// Thực thi ActionListener dùng lớp nặc danh

btn2.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

// Show a message box.

JOptionPane.showMessageDialog(null,"I am button 2");

}

});

(Xem mã hoàn chỉnh tại GitHub)

Tạo các lớp thực thi

Vì Java cho phép các lớp lồng nhau nên chúng ta có thể định nghĩa các lớp thực thi phương thức actionPerformed cho từng yêu cầu sự kiện khác nhau của các điều khiển. Lưu ý rằng, với mỗi lớp thực thi phải khai báo thực thi giao diện ActionListener. Định nghĩa hai lớp Button1HandlerButton2Handler trong MainClass:


public class MainClass extends JFrame{

public static void main(String[] args)  {

// TODO Auto-generated method stub

MainClass mainWindow = new MainClass();

}

public MainClass() {

...

}

private class Button1Handler implements ActionListener{

@Override

public void actionPerformed(ActionEvent e) {

// TODO Auto-generated method stub

JOptionPane.showMessageDialog(null,"I am button 1");

}

}

private class Button2Handler implements ActionListener{

@Override

public void actionPerformed(ActionEvent e) {

// TODO Auto-generated method stub

JOptionPane.showMessageDialog(null,"I am button 2");

}

}

}

Và để tiện sử dụng, các biến như label1, label2, btn1,…được khai báo như là biến thành viên của lớp MainClass:


public class MainClass extends JFrame{

private JPanel panel;

private JLabel label1;

private JLabel label2;

private JButton btn1;

private JButton btn2;

public static void main(String[] args)  {

// TODO Auto-generated method stub

MainClass mainWindow = new MainClass();

}

...

}

Sử dụng các lớp thực thi cho từng button:


btn1.addActionListener(new Button1Handler());

btn2.addActionListener(new Button2Handler());

(Xem đoạn mã hoàn chỉnh tại GitHub)

Tương tác với các điều khiển

Các lớp điều khiển chứa nhiều phương thức giúp chúng ta có thể tương tác dễ dàng với các điều khiển như thay đổi định dạng kích cỡ, màu nền, kiểu chữ, v.v. của điều khiển như ví dụ chúng ta đã thực hiện với button trong phần mở đầu của chương này. Các phương thức này giúp chúng ta linh hoạt khi tương tác với các điều khiển. Ví dụ sau sẽ điều chỉnh mã thực thi khi phát sinh sự kiện click của hai button 1 và button 2 ở trên, thay vì hiển thị các hộp thông điệp, chúng ta sẽ thay đổi nội dung văn bản hiển thị trên hai button:


private class Button1Handler implements ActionListener{

@Override

public void actionPerformed(ActionEvent e) {

// TODO Auto-generated method stub

btn1.setText("I am button 1");

}

}

private class Button2Handler implements ActionListener{

@Override

public void actionPerformed(ActionEvent e) {

// TODO Auto-generated method stub

btn2.setText("I am button 2");

}

}

Khi thực thi chương trình:

Nhấn vào Button 1:

Nhấn vào Button 2:

Tham khảo ví dụ:

Ngôn ngữ Java >