Chương 6. LẬP TRÌNH GIAO DIỆN ĐỒ HỌA
6.5. Xử lý sự kiện
6.5.4. Các sự kiện chuột
Bạn không cần phải xử lý các sự kiện chuột một cách tường minh nếu bạn chỉ muốn người dùng nhấp vào nút hoặc menu. Các thao tác chuột này được xử lý nội bộ bởi các thành phần khác nhau trong giao diện người dùng. Tuy nhiên, nếu bạn muốn cho phép người dùng vẽ bằng chuột, bạn sẽ cần bẫy các thao tác chuột như các sựkiện di chuyến, nhấp và kéo chuột.
Trong phần này, chúng tôi sẽ chỉ ra cho bạn một ứng dụng soạn thảo đồ họa đơn giản cho phép người dùng đặt, di chuyển và xóa các ô vuông trên một khung vẽ (canvas) (xem hình 6.13).
Hình 6.13. Một chương trình xử lý sự kiện chuột.
Khi người dùng nhấp vào nút chuột, ba phương thức lắng nghe được gọi là:
mousePress khi nhấn chuột lần đầu tiên, mouseReleased khi chuột được thả ra và cuối cùng là mouseClicked. Neu bạn chỉ quan tâm đến những nhấp chuộthoàn chỉnh, bạn có thể bỏ qua hai phương thức đầu tiên. Bằng cách sử dụng các phương thức getx và getY trên đối số MouseEvent, bạn có thể lấy được tọa độ X và y của con trỏ chuộtkhi nhấp chuột. Đe phân biệt các thao tác nhấp chuột đơn, nhấp chuột đôi và nhấp chuột ba (!), hãy sử dụng phương thức getClickCount.
Một số nhà thiết kế giao diện người dùng tạo ra các tố họp kết hợp nhấp chuột và bàn phím, chang hạn như Ctrl + Shift + Nhấp_chuột, cho người dùng. Chúng tôi thấy rằng cách làm này khá bất tiện, nhưng nếu bạn không đồng ý, bạn sẽ thấy rằng việc kiểm tra các
Sử dụng mật nạ bit để kiểm tra những bộ điều chỉnh (modifiers) nào đã được đật.
Trong API nguyên thủy, hai trong số các mặt nạ nútbằng với hai mặt nạ bộ điều chỉnh bàn phím, cụ thể là:
BƯTT0N2-MASK == ALT-MASK BƯTT0N3-MASK == META-MASK
Điều này đã được thực hiện để những người dùng với chuột một - nút có thế mô phỏng các nút chuột khác bằng cách giữ các phím điều chỉnh thay thế. Tuy nhiên, kể từ Java SE 1.4, một cách tiếp cận khácđược khuyến nghị. Bây giờ chúng ta có các mật nạ:
BUTT0N1-D0WN-MASK BUTT0N2_D0WN_MASK BUTT0N3-D0WN-MASK SHIFT_DOWN_MASK CTRL_DOWN_MASK ALT-DOWN-MASK
ALT_GRAPH_DOWN_MASK META-DOWN-MASK
Phương thức getModifierEx thông cáo một cách chính xác các nút chuột và bộ điều chỉnh bàn phímcủa một sựkiện chuột.
Lưu ý rằng BUTT0N3-D0WN-MASK kiểm tra nút chuột phải (không phải là nút chính) trong Windows. Ví dụ: Bạn có thế sử dụngđoạn mã sau đế pháthiệnxemnút chuột phải có đang được giữ hay không:
if((event.getModifiersExQ & InputEvent.BUTT0N3_D0WN_MASK) != 0) ... // đoạn mã cho sự kiện nhấp chuột phải
Trong chương trình mẫu của chúng ta, chúng ta cung cấp cả hai phương thức mousePressed và mouseClỉcked. Khi bạn nhấp vào một điểmảnh (pixel) không nằm trong bất kỳ hình vuông nào đã được vẽ, một hình vuông mới sẽ được thêm vào. Chúng ta đã thực hiện điều này trong phương thức mousePressed đế cho người dùng nhận được phản hồi ngay lập tức và không phải đợi cho đến khi nút chuột được nhả ra. Khi người dùng nhấp đúp vào bên trong một hình vuông đãtồntại, hình vuông đó sẽbị xóa đi. Chúng ta đã thực hiện điều này trong phương thức mouseClicked vìchúngta cầnđếm số lần nhấp.
public void mousePressed(MouseEvent event) {
current = find(event.getPoint());
if(current == null) // not inside a square add(event.getPoint());
}
public void mouseClickedfMouseEvent event) {
current = find(event.getPointQ);
if (current !=null &&event.getClickCountQ >= 2) remove(current);
}__________________________________________________________________
Khi chuột di chuyển qua một cửa so, cửa so sẽ nhận được một chuỗi các sự kiện di chuyến chuột một cách đều đặn. Lưu ý rằng chúng ta có các giao diện MouseLỉstener và MouseMotỉonListener riêng biệt. Điều này đượcthực hiện đểđạt mục đíchhiệu quả, có rất nhiều sự kiện chuộtkhi người dùng di chuyểnchuộtxung quanh và một trình lắng nghe chỉ quan tâm đến việcnhấp chuột sẽ không bị làm phiền bời việcdi chuyến chuột không mong muốn.
Chương trình mẫu của chúng ta sẽ bẫy các sự kiện chuyển động của chuột để thay đổi con trô sang hình dạng khác (ví dụ: một hình chữ thập) khi con trò nằm trên một hình vuông. Điều này được thực hiện với phương thức getPredefinedCursor của lớp Cursor.
Hình 6.14 liệt kê các hằng số sẽ sử dụng với phương thức này cùng với hình dạng tương ứng của các con trỏtrongWindows:
Hình 6.14. Các hình dạng con trỏ mẫu.
Icon Constant Icon Constant
DEFAULT-CURSOR NE-RESIZE-CURSOR
I CROSSHAIR-CURSOR
4—► E-RESIZE-CURSOR
HAND-CURSOR r SE-RESIZE-CURSOR
▲ MOVE-CURSOR t S-RESIZE-CURSOR
▼
I TEXT-CURSOR SW-RESIZE-CURSOR
WAIT-CURSOR
4 ► W-RESIZE-CURSOR
t N RESIZE CURSOR NW RESIZE CURSOR
ị
Dưới đây là phương thức mouseMovedcủaMouseMotỉonListener trong chương trình ví dụcủa chúng ta:
public void mouseMoved(MouseEvent event) {
if (find(event.getPoint()) == null)
setCursor(Cursor.getDefaultCursor());
else
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CUR SOR));
}
LƯU Ý: Bạn cũng CO the định nghĩa các con trò của riêng bạn thông qua việc sử dụng các phương thức createCustomCursor trong lóp Toolkit'.
Toolkittk=Toolkit.getDefaultToolkit 0;
Image img = tk.getlmage ("dynamite.gif");
Cursor dynamiteCursor = tk.createCustomCursor (img, new Point (10, 10),"dynamic stick");
Tham số đầu tiên của createCustomCursor trỏ tới hình ảnh của con trỏ. Tham số thứ hai đưara độ lệchcủa “hot spot” của con trỏ. Tham số thứ ba là một chuồi mô tả của con trỏ. Chuỗi này có thể được sử dụng để hỗ trợ trợnăng. Ví dụ: Chương trình đọc màn hình có thể đọc mô tả hình dạng con trỏ cho người dùng bị khiếm thị hoặc đơn giản là người đó khôngđối diện với màn hình.
Neu người dùng nhấn nút chuột trong khi chuột đang chuyển động, các lời gọi phương thức mouseDragged sẽ được tạo ra thay vì các lời gọi phương thức mouseMoved.
Chương trình mẫu của chúng ta cho phép người dùng kéo hình vuông bên dưới con trỏ.
Chúng ta chỉ cầncập nhật hình chữ nhật đang được kéo sao cho nó được căn giữa bên dưới vị trí chuột. Sau đó, chúng ta vẽ lạikhung vẽ để hiển thị vị trí chuộtmới.
public voidmouseDragged(MouseEventevent) { if (current != null) {
intX= event.getXQ;
inty = event.getY();
current.setFrame(x - SIDELENGTH / 2, y - SIDELENGTH I 2, SIDELENGTH, SIDELENGTH);
repaintQ;
} }
LƯU Ý: Phương thức mouseMoved chỉ được gọi khi chuột vẫn ở bên trong thành phần. Tuy nhiên, phương thức mouseDragged tiếp tục được gọi ngay cả khi chuộtbị kéo rangoài thành phần.
Có hai phương thức sự kiện chuột khác: mouseEntered và mouseExited. Các phương thức này được gọikhi chuộtđi vào hoặc thoát khỏi một thành phần.
Cuối cùng, chúng tôi giải thích làm thế nào để lắng nghe các sự kiện chuột. Các nhấp chuột được thông cáo thông qua thủ tục mouseClỉcked, là một phần của giao diện MouseListener. Nhiều ứng dụng chỉ quan tâm đến việc nhấp chuột và mà không quan tâm đến di chuyển chuột; với các sự kiện di chuyển chuột xảy ra rất thường xuyên, các sự kiện di chuyển và kéo chuột được định nghĩa trong một giao diện riêng gọi là MouseMotỉonLỉstener.
Trong chương trình của chúng ta, chúng ta quan tâm đến cả hai kiểu sự kiện chuột.
Chúng ta định nghĩa hai lớp bên trong: MouseHandler và MouseMotionHandler. Lớp MouseHandler mờ rộng lóp MouseAdapter bởi vì nó chỉ định nghĩa hai trong số năm phương thức của giao diện MouseLỉstener. Lớp MouseMotionHandler thực thi giao diện MouseMotionLỉstener và định nghĩa cả hai phương thức của giao diện đó. Các chương trình 6.5 và 6.6 chỉ ra mã hoànchỉnh.
Chương trình 6.5 import javax.swing.*;
11 Định nghĩa khung chứa một bảng nội dung để kiểm tra cácthaotácchuột public class MouseFrame extends JFrame {
public MouseFrameQ {
addfnew MouseComponentQ);
packQ;
} }
Chương trình 6.6 importjava.awt.*; import java.awt.event.*; import java.awt.geom.*; importjava.util.*;
import javax.swing.*;
11 Định nghĩa thành phẫn với cácthaotácchuột để thêm vàxóacáchìnhvuông public class MouseComponent extends JComponent {
private static final intDEFAULT-WIDTH = 300;
private static final int DEFAULT-HEIGHT = 200;
private static finalint SIDELENGTH - 10;
private ArrayList<Rectangle2D> squares;
private Rectangle2D current; // Hình vuông chứa con trỏ chuột public MouseComponentQ
{
squares =new ArrayListoQ; current = null;
addMouseListenerfnew MouseHandlerQ);
addMouseMotionListenerfnew MouseMotionHandlerQ];
}
public Dimension getPreferredSizeQ {
return newDimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public void paintComponentfGraphics g) {
Graphics2D g2 = (Graphics2D) g;
I/Nẽ tất cả cả hình vuông for (Rectangle2D r: squares) g2.draw(r);
}
ỵ
**
*Tìm hình vuông đầu tiên chứa một điểm
* @param p: điểm
* ©return: trả về hình vuông đầu tiên chứa điểm
7
public Rectangle2D find(Point2D p) {
for (Rectangle2D r: squares) {
if (/.contains (p)) return r;
}
return null;
}
ỵ
**
* Thêm một hình vuông vào tập hợp
* @param p: điểm trungtâm của hìnhvuông
V
public voidadd(Point2D p) {
doubleX= p.getXQ;
doubley= p.getYQ;
current = new Rectangle2D.Double(x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH);
squares.add(current);
repaintf);
} ỵ
**
* Xóamộthình vuông khỏi tập hợp
* @param s: hình vuông cần xóa
7
publicvoid remove(Rectangle2D s) {
if (s == null) return;
if (s == current) current= null;
squares.remove(s);
repaint();
}
private class MouseHandler extends MouseAdapter {
publicvoid mousePressed(MouseEventevent) {
// Thêm một hình vuông nếu con trỏ không ở bên trong mộthình vuông nào đó
current= find(event.getPoint());
if (current ==null) add(event.getPoint());
}
publicvoid mouseClickedfMouseEvent event) {
// Xóa hình vuông hiện tại nếu nhấp kép chuột (double clicked)
current = find(event.getPointO);
if (current != null && event.getClickCount() >= 2) remove(current);
} }
private class MouseMotionHandler implements MouseMotionListener {
publicvoid mouseMoved(MouseEventevent) {
// Đặt con trỏ chuột sang dạng chữ thập (cross hairs) nếu nó đangở bên trong một hình vuông
if (find(event.getPoint()) == null)
setCursor(Cursor.getDefaultCursor());
else
setCursor(Cursor.getPredefinedCursor(Cursor.CRO SSHAIR-CURSOR));
}
publicvoid mouseDragged(MouseEventevent) {
if (current != null) {
int X = event.getXQ;
int y= event.getYf);
11 Kéo (drag) hình vuông hiện tại sang vị trí khác với trung tâm của nó ở tọa độ (x, y)
current.setFrame(x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH);
repaintf);
} }
} }