目录
一. 事件处理基础
1. 实例:处理按钮点击事件
2. 简洁的指定监听器
3. 实例:改变观感
4. 适配器类二. 动作
三. 鼠标事件
四. AWT事件继承层次
一. 事件处理基础
java中事件的所有信息都封装在一个事件对象中(event object), 所有的事件对象都继承自java.util.EventObject
类, 每个事件类型还有子类, 比如ActionEvent
, WindowEvent
;
创建一个自己的监听器要继承自ActionListener
接口, 必须要实现ActionPerformed
方法, 并且传入一个ActionEvent
参数;
可以把一些个监听器放到某一个事件源中(比如一个按钮), 然后每当事件源发生事件时都会自动调用所有监听器的ActionPerformed
方法;
1.实例:处理按钮点击事件
先上测试代码:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
public class Test {
public static void main(String[] args) {
EventQueue.invokeLater(()->{
JFrame myFrame = new ButtonFrame();
myFrame.setTitle("Recluse");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
});
}
}
class ButtonFrame extends JFrame{
private JPanel myWindow;
private static final int DEFAULT_WIDTH = 500;
private static final int DEFAULT_HEIGHT = 500;
public ButtonFrame(){
this.setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
JButton greenButton = new JButton("Green");
JButton yellowButton = new JButton("Yellow");
JButton redButton = new JButton("Red");
myWindow = new JPanel();
myWindow.add(greenButton);
myWindow.add(yellowButton);
myWindow.add(redButton);
this.add(myWindow);
Colorbutton greenbut = new Colorbutton(Color.green);
Colorbutton yellowbut = new Colorbutton(Color.yellow);
Colorbutton redbut = new Colorbutton(Color.red);
greenButton.addActionListener(greenbut);
yellowButton.addActionListener(yellowbut);
redButton.addActionListener(redbut);
}
private class Colorbutton implements ActionListener{
private Color backgroundColor;
public Colorbutton(Color c){
backgroundColor = c;
}
@Override
public void actionPerformed(ActionEvent e) {
myWindow.setBackground(backgroundColor);
}
}
}
然后慢慢分析一下上面这个例子, 首先测试部分已经很常见了, 就是在任务队列里创建一个JFrame里面实例化一个ButtonFrame, 然后设置一些关于窗口的标题之类的, 记得设置setVisible否则不可见, 后面写ButtonFrame类;
public static void main(String[] args) {
EventQueue.invokeLater(()->{
JFrame myFrame = new ButtonFrame();
myFrame.setTitle("Recluse");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
});
然后写ButtonFrame类, 继承自JFrame类, 最重要的一个是private JPanel myWindow;
这是定义了一个面板, 可以看做是一个容器, 可以向这个容器里加入一些组件, 比如按钮, 标签之类的, 然后定义默认的宽和高并且在构造函数里面设置一下, 都很简单;
然后在构造函数里面, 新建三个按钮JButton, 传入一个String表示的是button上显示的文本信息, 然后把myWindow实例化一下(初始化)myWindow = new JPanel();
, 然后把刚才新建的三个按钮加入到myWindow中, 这里一定要记得把myWindow加入到buttonFrame中, 因为:
面板与顶层容器的不同点: 面板不能独立存在, 必须被添加到其他容器内部(面板可以嵌套)
所以一定要把JPanel加入到JFrame中;
这是前面的一部分:
this.setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
JButton greenButton = new JButton("Green");
JButton yellowButton = new JButton("Yellow");
JButton redButton = new JButton("Red");
myWindow = new JPanel();
myWindow.add(greenButton);
myWindow.add(yellowButton);
myWindow.add(redButton);
this.add(myWindow);
然后去定义一个内部类, 支持ActionListener接口, 写一个自己的监听器, 用于在按钮被按下时, 改变JPanel的背景颜色, 因为这里使用的是内部类, 所以可以直接访问到ButtonFrame中的变量JPanel, 我们覆写ActionPerformed方法, 在其中直接调用myWindow.setBackGround方法, 内部类如下:
private class Colorbutton implements ActionListener{
private Color backgroundColor;
public Colorbutton(Color c){
backgroundColor = c;
}
@Override
public void actionPerformed(ActionEvent e) {
myWindow.setBackground(backgroundColor);
}
}
然后我们在ButtonFrame的构造器中实例化三个ColorButton即可, 然后使用addActionListener方法把这三个监听器分别加入到之前写过的三个按钮中去即可:
Colorbutton greenbut = new Colorbutton(Color.green);
Colorbutton yellowbut = new Colorbutton(Color.yellow);
Colorbutton redbut = new Colorbutton(Color.red);
greenButton.addActionListener(greenbut);
yellowButton.addActionListener(yellowbut);
redButton.addActionListener(redbut);
最终效果如下图:
至此这个实例就结束了, 然后在我自己随便瞎搞的过程中发现一个问题, 如果我希望按按钮时改变的不是JPanel的颜色, 而是直接改变JFrame的颜色, 我发现我直接在内部类的ActionPerformed中改写成:
ButtonFrame.this.setBackground(backgroundColor);
这样是无效的, 无论怎么设置, 甚至是在Test类中进行设置, 也无法看到JFrame类的颜色(在窗口加载的过程中可以看到窗体是先变成我我设置的颜色再变成系统背景色的), 究其原因, 原来是因为JFrame类的颜色被其中的容器覆盖掉了, 我们对JFrame类使用getContentPane()可以看到, 其实直接覆盖了JFrame的是一个ContentPane的容器, 要想看到我们对JFrame设置的颜色, 可以在Test中把MyFrame.myFrame.setVisible()
设置成false这样就能看到JFrame的本来面貌了, 但是这样显然非我所愿, 所以要改变窗体的背景色, 可以把前面的代码改成:
ButtonFrame.this.getContentPane().setBackground(backgroundColor);
这样就能实现效果啦~ 代码稍微修改了一下, 代码贴上:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
public class Test {
public static void main(String[] args) {
EventQueue.invokeLater(()->{
JFrame myFrame = new ButtonFrame();
myFrame.setTitle("Recluse");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
});
}
}
class ButtonFrame extends JFrame{
private JPanel myWindow;
private static final int DEFAULT_WIDTH = 500;
private static final int DEFAULT_HEIGHT = 500;
public ButtonFrame(){
this.setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
JButton greenButton = new JButton("Green");
JButton yellowButton = new JButton("Yellow");
JButton redButton = new JButton("Red");
myWindow = new JPanel();
myWindow.add(greenButton);
myWindow.add(yellowButton);
myWindow.add(redButton);
myWindow.setPreferredSize(new Dimension(500,100));
this.add(myWindow,BorderLayout.SOUTH);
Colorbutton greenbut = new Colorbutton(Color.green);
Colorbutton yellowbut = new Colorbutton(Color.yellow);
Colorbutton redbut = new Colorbutton(Color.red);
greenButton.addActionListener(greenbut);
yellowButton.addActionListener(yellowbut);
redButton.addActionListener(redbut);
}
private class Colorbutton implements ActionListener{
private Color backgroundColor;
public Colorbutton(Color c){
backgroundColor = c;
}
@Override
public void actionPerformed(ActionEvent e) {
ButtonFrame.this.getContentPane().setBackground(backgroundColor);
}
}
}
我还设置了一下JPanel的尺寸Dimension和布局Layout
最终效果如下:
2. 简洁的指定监听器
但是这个方法需要创建一个内部类, 其实有更简单的方法, 比如使用lambda表达式, 而不需要创建内部类, 我们可以把创建按钮和响应的部分放到一个方法里面, 如下:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
public class Test {
public static void main(String[] args) {
EventQueue.invokeLater(()->{
JFrame myFrame = new ButtonFrame();
myFrame.setTitle("Recluse");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
});
}
}
class ButtonFrame extends JFrame{
private JPanel myWindow;
private static final int DEFAULT_WIDTH = 500;
private static final int DEFAULT_HEIGHT = 500;
public ButtonFrame(){
myWindow = new JPanel();
this.setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
myWindow.setPreferredSize(new Dimension(500,100));
this.add(myWindow,BorderLayout.SOUTH);
makeButton("Green", Color.GREEN);
makeButton("Yellow",Color.YELLOW);
makeButton("Blue",Color.BLUE);
}
public void makeButton(String name, Color backgroundcolor){
JButton aButton = new JButton(name);
myWindow.add(aButton);
aButton.addActionListener(event->{myWindow.setBackground(backgroundcolor);});
}
}
当然使用lambda表达式肯定不是非用不可的, 以前的方法是使用匿名对象, 也是可以的:
aButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myWindow.setBackground(backgroundcolor);
}
});
当然,其实可以直接用JFrame本身做事件源容器, 使ButtonFrame支持ActionListener接口, 需要把按钮定义在属性中, 然后在构造函数中添加的监听器中直接使用this 像这样:greenButton.addActionListener(this);
然后在自己类中实现ActionPerformed方法, 使用Object source = e.getSource();
获取到事件的更新源, 然后用判断语句来判断是哪个按钮的事件依次执行事件操作;
class ButtonFrame extends JFrame implements ActionListener{
private JPanel myWindow;
private static final int DEFAULT_WIDTH = 500;
private static final int DEFAULT_HEIGHT = 500;
JButton greenButton = new JButton("Green");
JButton redButton = new JButton("Red");
JButton blueButton = new JButton("Blue");
public ButtonFrame(){
myWindow = new JPanel();
this.setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
myWindow.setPreferredSize(new Dimension(500,100));
greenButton.addActionListener(this);
redButton.addActionListener(this);
blueButton.addActionListener(this);
myWindow.add(greenButton);
myWindow.add(redButton);
myWindow.add(blueButton);
this.add(myWindow,BorderLayout.SOUTH);
}
@Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if(source == greenButton) myWindow.setBackground(Color.GREEN);
else if(source == redButton) myWindow.setBackground(Color.RED);
else if(source == blueButton) myWindow.setBackground(Color.BLUE);
}
}
最后一种方法是也可以使用EventHandler来使用这样的方法
EventHandler.create(ActionListener.class, frame, "loadData")
创造一个ActionListener, 这种方法用lambda表达式就等同于event->frame.localData();
但是这个方法我还不会用呜呜呜
3.实例:改变观感
可以改变界面的UI风格, 比如按钮的风格, 标签之类的, 这样做:
try{
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
SwingUtilities.updateComponentTreeUI(this);
}
catch (Exception e){
System.out.println(e);
}
UIManager.setLookAndFeel
可以传入一个String参数, 即希望使用的风格的名字, 使用的风格必须要是Java安装过的, 这就有可能出现Exception所以要做try catch
;
可以这样来查看系统中安装了哪些UI风格:
UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
for(UIManager.LookAndFeelInfo i : infos){
System.out.println(i);
System.out.println(i.getClassName());
System.out.println(i.getName());
}
//比如我的电脑里安装了下面这些:
//javax.swing.UIManager$LookAndFeelInfo[Metal javax.swing.plaf.metal.MetalLookAndFeel]
//javax.swing.plaf.metal.MetalLookAndFeel
//Metal
//javax.swing.UIManager$LookAndFeelInfo[Nimbus javax.swing.plaf.nimbus.NimbusLookAndFeel]
//javax.swing.plaf.nimbus.NimbusLookAndFeel
//Nimbus
//javax.swing.UIManager$LookAndFeelInfo[CDE/Motif com.sun.java.swing.plaf.motif.MotifLookAndFeel]
//com.sun.java.swing.plaf.motif.MotifLookAndFeel
//CDE/Motif
//javax.swing.UIManager$LookAndFeelInfo[Mac OS X com.apple.laf.AquaLookAndFeel]
//com.apple.laf.AquaLookAndFeel
//Mac OS X
如果使用了没有安装的UI风格会报出异常java.lang.ClassNotFoundException
如果希望给一个按钮添加监听器来实现改变观感, 可以在ActionListener中传入一个lambda表达式来实现一个类似于ActionPerformed的操作, 类似于这么做:
redButton.addActionListener(event->{
try{
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
SwingUtilities.updateComponentTreeUI(this);
}
catch (Exception e){
System.out.println(e);
}
});
也可以使用匿名内部类来实现:
blueButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.updateComponentTreeUI(ButtonFrame.this);
}
catch (Exception ee){
System.out.println(ee);
}
}
});
要注意的是在使用匿名内部类的时候, 给SwingUtilities.updateComponentTreeUI
传入参数一定要传入ButtonFrame.this
, 而不能直接传入this
, 因为直接用this返回的是内部类的this而不是ButtonFrame的this, 所以这么看还是用lambda表达式比较好~嘤嘤嘤 ?? ?
最后大概是这样的效果:
按Red就切换到第一张图的UI, 按Blue就切换到第二张图的UI -.- 懒得换按钮文本了嘤嘤嘤 ?
![](https://ws3.sinaimg.cn/large/006tNbRwgy1fwyvg8zie5j311u0fm7dg.jpg)
4. 适配器类
有的时候我们不仅需要点击按钮的事件操作, 可能希望实现对窗体的监控, 比如希望窗体失去焦点的时候变色之类的, 或者关闭前弹出确认关闭的窗口, 可以这样实现:
public class Test {
public static void main(String[] args) {
EventQueue.invokeLater(()->{
JFrame myFrame = new ButtonFrame();
myFrame.setTitle("Recluse");
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
myFrame.addWindowListener(new WindowAdapter() {
@Override
public void windowIconified(WindowEvent e) {
System.out.println(new Date());
super.windowIconified(e);
}
@Override
public void windowLostFocus(WindowEvent e) {
System.out.println("LostFocus");
super.windowLostFocus(e);
}
});
});
}
}
那么到底适配器类是干嘛的呢, 我们写窗口事件的时候当然可以去直接去支持一个WindowListener接口,但是这样要实现很多可能不需要使用到的方法, 所以你就可以使用适配器类, 它为所有的接口的方法写了默认方法, 你就可以只写自己需要的方法就可以了, 算是对历史遗留问题的一个解决方案把(因为写WindowListener接口的时候还不能为接口方法提供默认方法);
实现的是WindowAdapter抽象类(实现了WindowListener接口), 同时实现了很多没有任何动作的方法(这里是10个, WindowListener里一共是有7个), 看到这个方法的源代码:
public abstract class WindowAdapter
implements WindowListener, WindowStateListener, WindowFocusListener
{
public void windowOpened(WindowEvent e) {}
public void windowClosing(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowStateChanged(WindowEvent e) {}
public void windowGainedFocus(WindowEvent e) {}
public void windowLostFocus(WindowEvent e) {}
}
这个WindowAdapter抽象类实现的是WindowListener接口:
public interface WindowListener extends EventListener {
public void windowOpened(WindowEvent e);
public void windowClosing(WindowEvent e);
public void windowClosed(WindowEvent e);
public void windowIconified(WindowEvent e);
public void windowDeiconified(WindowEvent e);
public void windowActivated(WindowEvent e);
public void windowDeactivated(WindowEvent e);
}
从名字就能很显然的看出来它的作用-0 0-
然后还有两个好用的东西:
二. 动作
激活一个命令不止可以用按钮像前面一样使用ActionListener, 也可以使用Action接口, Action接口扩展于ActionListener接口, Action接口里的方法有:
如果要写一个Action没必要直接implements Action
, 这样要实现里面的所有方法, 可以直接extends AbstractAction
然后记得覆写ActionPerformed
方法即可;
里面的这个putValue和getValue方法很重要, 在Action里面, 存了很多键值对, 可以这样调用:
redAction.putValue(Action.NAME, "Red");
有这些预定义的动作名称:
当然也可以自己建立自己的键值对, 自己能用就好,嘻嘻:D
到这里为止我们就能写出来一个动作了, 比如下面这样:
public class ColorAction extends AbstractAction{
public ColorAction(String name, Icon icon, Color c){
this.putValue(Action.NAME, name);
this.putValue(Action.SMALL_ICON, icon);
this.putValue("Color", c);
this.putValue(Action.SHORT_DESCRIPTION, "Set the color to"+name.toString());
}
@Override
public void actionPerformed(ActionEvent e) {
myWindow.setBackground((Color)this.getValue("Color"));
}
}
写了一个ColorAction继承自AbstractAction类, 然后放进去了一些键值对, 在ActionPerformed方法里面写了要做的动作;
然后这里有了动作之后我们就可以创建一个和以前一样的按键了, 这样:
JPanel myWindow = new JPanel();
Action redAction = new ColorAction("Red", new ImageIcon("red-ball.gif"), Color.RED);
myWindow.add(new JButton(redAction));
this.add(myWindow);
按键上的显示就是传入的Action的Action.NAME
然后希望做一个, 能读取到用户键盘的读入, 然后做出一些动作;
可以使用KeyStroke, 像下面这样做:
InputMap imap = myWindow.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
imap.put(KeyStroke.getKeyStroke("ctrl R"), "Panel Red");
ActionMap aMap = myWindow.getActionMap();
aMap.put("Panel Red", redAction);
在这里有个东西, 这个输入映射里面有下面几个条件:
InputMap
不能直接地将Keystroke
对象映射到Action
对象, 而是先映射到任意对象上, 然后由ActionMap
类实现将对象映射到动作上的第2个映射, 这样很容易实现来自不同输入映射的按键共享一个动作的目的;
习惯上用none表示空动作, 用于取消一个动作;
这样:
imap.put(KeyStroke.getKeyStroke("ctrl C"), "none");
总结一下写动作的步骤:
上一下我的测试代码:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class ActionFrame extends JFrame {
JPanel myWindow;
private static final int DEFAULT_WIDTH = 500;
private static final int DEFAULT_HEIGHT = 500;
public ActionFrame(){
this.setTitle("Recluse");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
this.setSize(new Dimension(DEFAULT_WIDTH,DEFAULT_HEIGHT));
myWindow = new JPanel();
Action redAction = new ColorAction("Red", new ImageIcon("red-ball.gif"), Color.RED);
Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"), Color.BLUE);
myWindow.add(new JButton(redAction));
myWindow.add(new JButton(blueAction));
this.add(myWindow);
InputMap imap = myWindow.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
imap.put(KeyStroke.getKeyStroke("ctrl R"), "Panel Red");
imap.put(KeyStroke.getKeyStroke("ctrl B"), "Panel Blue");
ActionMap aMap = myWindow.getActionMap();
aMap.put("Panel Red", redAction);
aMap.put("Panel Blue", blueAction);
}
public class ColorAction extends AbstractAction{
public ColorAction(String name, Icon icon, Color c){
this.putValue(Action.NAME, name);
this.putValue(Action.SMALL_ICON, icon);
this.putValue("Color", c);
this.putValue(Action.SHORT_DESCRIPTION, "Set the color to"+name.toString());
}
@Override
public void actionPerformed(ActionEvent e) {
myWindow.setBackground((Color)this.getValue("Color"));
}
}
public static void main(String[] args) {
new ActionFrame();
}
}
效果如下:
点击图标或者使用ctrl+R/B可以实现切换背景颜色;
但是我还发现一个可以用掩码来设置 按住每个键不放再按某个键实现功能的方法:
KeyStroke.getKeyStroke('C', InputEvent.CTRL_MASK)
然后惯例上常用方法:
三. 鼠标事件
现在我想要去监听鼠标事件, 可以想到, 鼠标可以有滑过, 点击(单击双击等, 按下去, 松开组成依次点击), 拖拽这些过程;
先上一段测试代码, 说明我想做什么, 现在要做一个画板, 在画板上按下鼠标会在鼠标附近画一个正方形, 然后拖动鼠标的时候这个正方形会跟着鼠标一起动, 然后双击这个正方形可以删除这个图形, 鼠标经过正方形的时候会变成十字形状:
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
public class MouseFrame extends JFrame {
public MouseFrame(){
this.add(new MouseComponent());
this.pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(()->{
JFrame a = new MouseFrame();
a.setTitle("PaintingBoard");
a.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
a.setVisible(true);
});
}
}
class MouseComponent extends JComponent{
private ArrayList<Rectangle2D> recs;
private Rectangle2D currentrec;
private static final double REC_WIDTH=20;
public MouseComponent(){
recs = new ArrayList<>();
currentrec = null;
this.addMouseListener(new MouseClickHandler());
this.addMouseMotionListener(new MouseMotionHandler());
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D gg = (Graphics2D)g;
for(Rectangle2D i : recs){
gg.draw(i);
}
}
public void add(Point2D p){
double x=p.getX(),y=p.getY();
currentrec = new Rectangle2D.Double(x-REC_WIDTH/2,y-REC_WIDTH/2,REC_WIDTH,REC_WIDTH);
recs.add(currentrec);
this.repaint();
}
public Rectangle2D find(Point2D p){
for(Rectangle2D i : recs){
if(i.contains(p)) return i;
}
return null;
}
public void remove(Rectangle2D r){
if(r==null) return;
if(r==currentrec) currentrec=null;
recs.remove(r);
this.repaint();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(600,600);
}
public class MouseClickHandler extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
currentrec = find(e.getPoint());
if(currentrec==null) add(e.getPoint());
}
@Override
public void mouseClicked(MouseEvent e) {
currentrec =find(e.getPoint());
if(currentrec!=null && e.getClickCount()>=2) remove(currentrec);
}
}
public class MouseMotionHandler implements MouseMotionListener{
@Override
public void mouseMoved(MouseEvent e) {
if(find(e.getPoint())!=null) setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
else setCursor(Cursor.getDefaultCursor());
}
@Override
public void mouseDragged(MouseEvent e) {
if(currentrec==null) return;
int x=e.getX(),y=e.getY();
currentrec.setFrame(x-REC_WIDTH/2,y-REC_WIDTH/2,REC_WIDTH,REC_WIDTH);
repaint();
}
}
}
然后上一下结果图是这样的:
然后分析一下这个程序代码:
大致思路是这样的, 在JFrame的框架中创建了一个ArrayList数组列表用于存创建的正方形, 然后每次创建正方形就加入到这个列表中, 然后每次发生事件的时候都要repaint一次, 比如新点击了一下鼠标, 比如拖拽了正方形, 比如双击删除了一个正方形, 都要repaint一次, 然后要实现一些简单功能, 比如正方形的增加, 删除功能, 然后最重点的是要实现两个内部类来处理鼠标事件, 因为鼠标的拖动监测太频繁了, 所以专门给鼠标移动做了一个接口mouseMotionListener
, 对于鼠标点击可以继承一个mouseAdapter
类;
先看代码吧, 首先第一段:
public MouseComponent(){
recs = new ArrayList<>();
currentrec = null;
this.addMouseListener(new MouseClickHandler());
this.addMouseMotionListener(new MouseMotionHandler());
}
在构造器里初始化了数组列表, 给框架加了鼠标事件和鼠标移动事件的监听;
先看一下MouseClickHandler
的代码:
public class MouseClickHandler extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
currentrec = find(e.getPoint());
if(currentrec==null) add(e.getPoint());
}
@Override
public void mouseClicked(MouseEvent e) {
currentrec =find(e.getPoint());
if(currentrec!=null && e.getClickCount()>=2) remove(currentrec);
}
}
这里面实现了两个方法, 一个是mousePressed
方法, 一个是mouseClicked
方法, 每次按下鼠标的时候, 就监测当前位置有没有正方形, 如果没有就创建一个, 每次双击鼠标, 如果当前位置有正方形, 就把这个正方形remove掉;在这里用```e.getPoint()``来获取鼠标位置;
然后是MouseMotionHandler
类:
public class MouseMotionHandler implements MouseMotionListener{
@Override
public void mouseMoved(MouseEvent e) {
if(find(e.getPoint())!=null) setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
else setCursor(Cursor.getDefaultCursor());
}
@Override
public void mouseDragged(MouseEvent e) {
if(currentrec==null) return;
int x=e.getX(),y=e.getY();
currentrec.setFrame(x-REC_WIDTH/2,y-REC_WIDTH/2,REC_WIDTH,REC_WIDTH);
repaint();
}
}
这里面也是实现了两个方法mouseMoved
和mouseDragged
, 在鼠标移动的时候改变指针为Cursor.CROSSHAIR_CURSOR
, 然后在鼠标拖拽的时候去设置currentrec.setFrame
用于直接改变这个正方形的位置, 然后记得一定要repaint一下;
这里要稍微注意一下mouseDragged的逻辑, 因为每次执行mouseDragged的时候一定会先click, 所以当前的currentrec的位置其实是已经确定了的, 在mousePressed
里面就更新过currentrec的位置了, 所以这里其实是不需要实时更新它的位置的, 否则会出现拖动鼠标的时候不跟手的问题~
有下面这些光标可供选择:
到这里主干代码就完成了, 然后实现一些基本的方法add, find, remove
方法, 用于操作数组列表里的正方形:
@Override
protected void paintComponent(Graphics g) {
Graphics2D gg = (Graphics2D)g;
for(Rectangle2D i : recs){
gg.draw(i);
}
}
public void add(Point2D p){
double x=p.getX(),y=p.getY();
currentrec = new Rectangle2D.Double(x-REC_WIDTH/2,y-REC_WIDTH/2,REC_WIDTH,REC_WIDTH);
recs.add(currentrec);
this.repaint();
}
public Rectangle2D find(Point2D p){
for(Rectangle2D i : recs){
if(i.contains(p)) return i;
}
return null;
}
public void remove(Rectangle2D r){
if(r==null) return;
if(r==currentrec) currentrec=null;
recs.remove(r);
this.repaint();
}
其余的关于简单窗口的细节我就省略不谈了0 0
然后上一些常用方法:
四. AWT事件继承层次
每次到这种都是字的章节我都不知道该说些啥, 看看吧, 嘤嘤嘤;
结束~嘻嘻 TAT