使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。
day073 事件处理(鼠标事件)
如果只希望用户能够点击按钮或菜单,那么就不需要显式地处理鼠标事件。鼠标操作将由用户界面中的各种组件内部处理。然而,如果希望用户使用鼠标画图,就需要捕获鼠标移动点击和拖动事件。
今天的学习,将展示一个简单的图形编辑器应用程序,它允许用户在画布上放置、移动和擦除方块。
当用户点击鼠标按钮时,将会调用三个监听器方法:鼠标第一次被按下时调用mousePressed;鼠标被释放时调用mouseReleased;最后调用mouseClicked。如果只对最终的点击事件感兴趣,就可以忽略前两个方法。用MouseEvent类对象作为参数,调用getX和getY方法可以获得鼠标被按下时鼠标指针所在的x和y坐标。要想区分单击、双击和三击(!),需要使用getClickCount方法。
有些用户界面设计者喜欢让用户采用鼠标点击与键盘修饰符组合(例如,CONTROL+SHIFT+CLICK)的方式进行操作。我们感觉这并不是一种值得赞许的方式。如果对此持有不同的观点,可以看一看同时检测鼠标按键和键盘修饰符所带来的混乱。
可以采用位掩码来测试已经设置了哪个修饰符。在最初的API中,有两个按钮的掩码与两个键盘修饰符的掩码一样,即
BUHON2_MASK == ALT_MASK
BUTTON3_MASK == META_MASK
这样做是为了能够让用户使用仅有一个按钮的鼠标通过按下修饰符键来模拟按下其他鼠标键的操作。然而,在JavaSE1.4中,建议使用一种不同的方式。有下列掩码:
BUTTON1_DOWN_MASK
BUTTON2_DOWN_MASK
BUTTON3_DOWN_MASK
SHIFT_DOWN_MASK
CTRL_DOWN_MASK
ALT_DOWN_MASK
ALT_CRAPH_DOWN_MASK
META_DOWN_MASK
getModifiersEx方法能够准确地报告鼠标事件的鼠标按钮和键盘修饰符。需要注意,在Windows环境下,使用BUTT0N3_D0WN_MASK检测鼠标右键(非主要的)的状态。例如,可以使用下列代码检测鼠标右键是否被按下:
if ((event.getModifiersEx() & InputEvent.BUTT0N3_D0WN_MASK) != 0)
. . .//code for right click
在列举的简单示例中,提供了mousePressed和mouseClicked方法。当鼠标点击在所有小方块的像素之外时,就会绘制一个新的小方块。这个操作是在mousePressed方法中实现的,这样可以让用户的操作立即得到响应,而不必等到释放鼠标按键。如果用户在某个小方块中双击鼠标,就会将它擦除。由于需要知道点击次数,所以这个操作将在mouseClicked方法中实现。
public void mousePressed( MouseEvent event )
{
//add a new square if the cursor isn't inside a square
current = find(event.getPoint());
if ( current == null)
add(event.getPoint());
}
public void mousedieked( MouseEvent event )
{
//remove the current squareif double clicked
current = find(event.getPoint());
if (current != null && event.getClickCount() >= 2)
remove(current);
}
当鼠标在窗口上移动时,窗口将会收到一连串的鼠标移动事件。请注意:有两个独立的接口MouseListener和MouseMotionListener。这样做有利于提高效率。当用户移动鼠标时,只关心鼠标点击(clicks)的监听器就不会被多余的鼠标移动(moves)所困扰。
这里给出的测试程序将捕获鼠标动作事件,以便在光标位于一个小方块之上时变成另外一种形状(十字)。实现这项操作需要使用Cursor类中的getPredefinedCursor方法。下表列出了在Windows环境下鼠标的形状和方法对应的常量。
下面是示例程序中 MouseMotionListener类的 mouseMoved方法:
public void mouseMoved( MouseEvent event )
{
//set the mouse cursor to cross hairsifitisinside
//a rectangle
if (find(event.getPoint()) == null)
setCursor(Cursor.getDefaultCursor());
else
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
如果用户在移动鼠标的同时按下鼠标,就会调用mouseMoved而不是调用mouseDmgged。在测试应用程序中,用户可以用光标拖动小方块。在程序中,仅仅用拖动的矩形更新当前光标位置。然后,重新绘制画布,以显示新的鼠标位置。
public void mouseDragged( MouseEvent event )
{
if (current != null)
{
int x = event.getX();
int y = event.getY();
//drag the current rectangle to centeritat (x,y)
current.setFrame( x - SIDELENGTH/2, y-SIDELENGTH /2, SIDELENGTH, SIDELENGTH);
repaint();
}
}
还有两个鼠标事件方法:mouseEntered 和 mouseExited。这两个方法是在鼠标进入或移出组件时被调用。
最后,解释一下如何监听鼠标事件。鼠标点击由mouseClicked过程报告,它是MouseListener接口的一部分。由于大部分应用程序只对鼠标点击感兴趣,而对鼠标移动并不感兴趣,但鼠标移动事件发生的频率又很高,因此将鼠标移动事件与拖动事件定义在一个称为MouseMotionListener的独立接口中。
在示例程序中,对两种鼠标事件类型都感兴趣。这里定义了两个内部类:MouseHandler和MouseMotionHandler。MouseHandler类扩展于MouseAdapter类,这是因为它只定义了5个MouseListener方法中的两个方法。MouseMotionHandler实现了MouseMotionListener接口,并定义了这个接口中的两个方法。
下面的程序展示了上面所说的:
/**
*@author zzehao
*/
import javax.swing.*;
public class MouseFrame extends JFrame
{
public MouseFrame()
{
add(new MouseComponent());
pack();
}
public static void main(String[] args)
{
MouseFrame frame = new MouseFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
/**
*@author zzehao
*/
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;
public class MouseComponent extends JComponent
{
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
private static final int SIDELENGTH = 10;
private ArrayList<Rectangle2D> squares;
private Rectangle2D current; // the square containing the mouse cursor
public MouseComponent()
{
squares = new ArrayList<>();
current= null;
addMouseListener(new MouseHandler());
addMouseMotionListener(new MouseMotionHandler());
}
public Dimension getPreferredSize()
{
return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public void paintComponent(Graphics g)
{
Graphics2D g2 =(Graphics2D) g;
//draw all squares
for (Rectangle2D r : squares)
g2.draw(r);
}
public Rectangle2D find(Point2D p)
{
for (Rectangle2D r : squares)
{
if (r.contains(p))
return r;
}
return null;
}
public void add(Point2D p)
{
double x = p.getX();
double y= p.getY();
current = new Rectangle2D.Double(x - SIDELENGTH /2, y - SIDELENGTH /2, SIDELENGTH,SIDELENGTH);
squares.add(current);
repaint();
}
public void remove(Rectangle2D s)
{
if (s == null)
return;
if (s == current)
current = null;
squares.remove(s);
repaint();
}
private class MouseHandler extends MouseAdapter
{
public void mousePressed( MouseEvent event )
{
//add a new square if the cursor isn't inside a square
current = find(event.getPoint());
if ( current == null)
add(event.getPoint());
}
public void mousedieked( MouseEvent event )
{
//remove the current squareif double clicked
current = find(event.getPoint());
if (current != null && event.getClickCount() >= 2)
remove(current);
}
}
private class MouseMotionHandler implements MouseMotionListener
{
public void mouseMoved( MouseEvent event )
{
//set the mouse cursor to cross hairsifitisinside
//a rectangle
if (find(event.getPoint()) == null)
setCursor(Cursor.getDefaultCursor());
else
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
public void mouseDragged( MouseEvent event )
{
if (current != null)
{
int x = event.getX();
int y = event.getY();
//drag the current rectangle to centeritat (x,y)
current.setFrame( x - SIDELENGTH/2, y-SIDELENGTH /2, SIDELENGTH, SIDELENGTH);
repaint();
}
}
}
}
运行的结果如下所示: