Bài 42: Event trong Java Swing – Lập trình Java cơ bản – ICP DAS Việt Nam

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:

  1. Event nguồn
  2. Đối tượng event
  3. Đối tượng lắng nghe event

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

1. Đố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

{

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() {

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.

1okButton

.addActionListener

(newClickAction());

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

private

class

ClickAction

extends

AbstractAction

{

public

void

actionPerformed

(ActionEvent e)

{ ... } }

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

Locale locale = Locale.getDefault();

Date

date =

new

Date

(e.getWhen());

String

s = DateFormat.getTimeInstance(DateFormat.SHORT, locale).format(date);

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

String 

source

= e.getSource().getClass().getName(); model.addElement(

"Source: "

+

source

);

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

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

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

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

Tạo lớp ẩn nội

import

java.awt.Container;

import

java.awt.EventQueue;

import

java.awt.event.ActionEvent;

import

java.awt.event.ActionListener;

import

javax.swing.GroupLayout;

import

javax.swing.JButton;

import

javax.swing.JFrame;

public

class

AnonymousInnerClassEx

extends

JFrame

{

public

AnonymousInnerClassEx

()

{ initUI(); }

private

void

initUI

()

{ Container pane = getContentPane(); GroupLayout gl =

new

GroupLayout(pane); pane.setLayout(gl); JButton closeButton =

new

JButton(

"Close"

); closeButton.setBounds(

40

,

50

,

80

,

25

); closeButton.addActionListener(

new

ActionListener() {

public

void

actionPerformed

(ActionEvent event)

{ System.exit(

0

); } }); gl.setAutoCreateContainerGaps(

true

); gl.setHorizontalGroup(gl.createSequentialGroup() .addComponent(closeButton) .addGap(

220

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

180

) ); pack(); setTitle(

"Event Example"

); setLocationRelativeTo(

null

); setDefaultCloseOperation(EXIT_ON_CLOSE); }

public

static

void

main

(String[] args)

{ EventQueue.invokeLater(

new

Runnable() {

public

void

run

()

{ AnonymousInnerClassEx ex =

new

AnonymousInnerClassEx(); ex.setVisible(

true

); } }); } }

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

JButton closeButton = 

new

JButton(

"Close"

);

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

closeButton.addActionListener(

new

ActionListener() {

public

void

actionPerformed

(ActionEvent event)

{ System.exit(

0

); } });

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

Tạo lớp nội

import

java.awt.Container;

import

java.awt.EventQueue;

import

java.awt.event.ActionEvent;

import

java.awt.event.ActionListener;

import

javax.swing.GroupLayout;

import

javax.swing.JButton;

import

javax.swing.JFrame;

public

class

InnerClassExample

extends

JFrame

{

public

InnerClassExample

()

{ initUI(); }

private

void

initUI

()

{ Container pane = getContentPane(); GroupLayout gl =

new

GroupLayout(pane); pane.setLayout(gl); JButton closeButton =

new

JButton(

"Close"

); ButtonCloseListener listener =

new

ButtonCloseListener(); closeButton.addActionListener(listener); gl.setAutoCreateContainerGaps(

true

); gl.setHorizontalGroup(gl.createSequentialGroup() .addComponent(closeButton) .addGap(

220

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

180

) ); pack(); setTitle(

"Event Example"

); setLocationRelativeTo(

null

); setDefaultCloseOperation(EXIT_ON_CLOSE); }

private

class

ButtonCloseListener

implements

ActionListener

{

public

void

actionPerformed

(ActionEvent e)

{ System.exit(

0

); } }

public

static

void

main

(String[] args)

{ EventQueue.invokeLater(

new

Runnable() {

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

{

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

); }

public

void

actionPerformed

(ActionEvent e)

{ System.exit(

0

); } }

public

static

void

main

(String[] args)

{ EventQueue.invokeLater(

new

Runnable() {

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

); }

public

void

actionPerformed

(ActionEvent e)

{ System.exit(

0

); } }

3. 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.

Ví dụ:

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

{

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() {

public

void

run

()

{ MultipleSources ms =

new

MultipleSources(); ms.setVisible(

true

); } }); } }

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

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

import

java.awt.BorderLayout;

import

java.awt.EventQueue;

import

java.awt.event.ActionEvent;

import

java.awt.event.ActionListener;

import

java.util.Calendar;

import

javax.swing.BorderFactory;

import

javax.swing.GroupLayout;

import

static

javax.swing.GroupLayout.Alignment.CENTER;

import

static

javax.swing.GroupLayout.DEFAULT_SIZE;

import

static

javax.swing.GroupLayout.PREFERRED_SIZE;

import

javax.swing.JButton;

import

javax.swing.JFrame;

import

javax.swing.JLabel;

import

javax.swing.JPanel;

import

javax.swing.JSpinner;

import

javax.swing.SpinnerModel;

import

javax.swing.SpinnerNumberModel;

public

class

MultipleListeners

extends

JFrame

{

private

JLabel statusbar;

private

JSpinner spinner;

private

int

count =

0

;

public

MultipleListeners

()

{ initUI(); }

private

void

initUI

()

{ JPanel panel =

new

JPanel(); GroupLayout gl =

new

GroupLayout(panel); panel.setLayout(gl); add(panel, BorderLayout.CENTER); statusbar =

new

JLabel(

"0"

); statusbar.setBorder(BorderFactory.createEtchedBorder()); add(statusbar, BorderLayout.SOUTH); JButton addButton =

new

JButton(

"+"

); addButton.addActionListener(

new

ButtonListener1()); addButton.addActionListener(

new

ButtonListener2()); Calendar calendar = Calendar.getInstance();

int

currentYear = calendar.get(Calendar.YEAR); SpinnerModel yearModel =

new

SpinnerNumberModel(currentYear, currentYear -

100

, currentYear +

100

,

1

); spinner =

new

JSpinner(yearModel); spinner.setEditor(

new

JSpinner.NumberEditor(spinner,

"#"

)); gl.setAutoCreateContainerGaps(

true

); gl.setHorizontalGroup(gl.createSequentialGroup() .addComponent(addButton) .addGap(

20

) .addComponent(spinner, DEFAULT_SIZE, DEFAULT_SIZE, PREFERRED_SIZE) ); gl.setVerticalGroup(gl.createSequentialGroup() .addGroup(gl.createParallelGroup(CENTER) .addComponent(addButton) .addComponent(spinner, DEFAULT_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)) ); pack(); setTitle(

"Event Example"

); setSize(

300

,

200

); setLocationRelativeTo(

null

); setDefaultCloseOperation(EXIT_ON_CLOSE); }

private

class

ButtonListener1

implements

ActionListener

{

public

void

actionPerformed

(ActionEvent e)

{ Integer val = (Integer) spinner.getValue(); spinner.setValue(++val); } }

private

class

ButtonListener2

implements

ActionListener

{

public

void

actionPerformed

(ActionEvent e)

{ statusbar.setText(Integer.toString(++count)); } }

public

static

void

main

(String[] args)

{ EventQueue.invokeLater(

new

Runnable() {

public

void

run

()

{ MultipleListeners ml =

new

MultipleListeners(); ml.setVisible(

true

); } }); } }

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

5. 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); }

public

void

componentResized

(ComponentEvent e)

{ }

public

void

componentMoved

(ComponentEvent e)

{

int

x = e.getComponent().getX();

int

y = e.getComponent().getY(); labelx.setText(

"x: "

+ x); labely.setText(

"y: "

+ y); }

public

void

componentShown

(ComponentEvent e)

{ }

public

void

componentHidden

(ComponentEvent e)

{ }

public

static

void

main

(String[] args)

{ EventQueue.invokeLater(

new

Runnable() {

public

void

run

()

{ MovingWindowEx ex =

new

MovingWindowEx(); ex.setVisible(

true

); } }); } }

6. 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ụ:

  1. 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.
  2. 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);