Java基础 自学讲义 8.事件处理

目录
一. 事件处理基础

  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 -.- 懒得换按钮文本了嘤嘤嘤 ?

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();
        }
    }

这里面也是实现了两个方法mouseMovedmouseDragged, 在鼠标移动的时候改变指针为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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目 录 第一章 JAVA入门 10 计算机语言发展史 10 机器语言 10 汇编语言 10 高级语言 10 其他高级语言 11 JAVA发展简史 12 JAVA为什么能够流行? 13 JAVA各版本的含义 13 JAVA技术体系架构 14 JAVA的特性和优势 14 JAVA应用程序的运行机制 15 JVM(JAVA VIRTUAL MACHINE) 16 Java运行时环境JRE(Java Runtime Environment) 17 JAVA语言应用范围 18 第一个JAVA程序 18 JAVA开发环境搭建 18 一个典型的JAVA程序的编写和运行过程 19 第一个程序常见错误 20 第一个JAVA程序的总结和提升 20 常用Java开发工具 20 常用dos命令 21 本章笔试作业 21 本章上机操作 21 第二章(1) 编程的基本概念 22 注释 22 标识符 22 关键字/保留字 23 变量(variable) 24 常量(Constant) 25 命名规则(规范) 25 基本数据类型(primitive data type) 26 整型变量 26 浮点型 27 字符型(2个字节): 28 boolean类型 29 运算符(operator) 29 二元运算符 29 一元运算符 30 布尔逻辑表达符 30 位运算符 30 扩展运算符 31 字符串连接符 31 三目条件运算符 31 运算符优先级的问题 31 自动类型转换 32 基本类型转化时常见错误和问题 33 方法 33 简单的键盘输入和输出 33 本章思考作业 34 上机操作 34 第二章(2) 控制语句 35 顺序结构 35 选择结构 35 if单选择结构 35 if-else双选择结构 35 If-elseif-else多选择结构 36 switch多选择结构 37 循环结构 39 While和dowhile的区别 41 For循环 42 break语句和continue语句 47 语句块 48 递归结构 49 本章作业 50 本章上机操作 51 第三章 JAVA面向对象程序开发 52 编程语言发展史 52 类和对象是如何产生发展的?如何进化的? 52 面向对象思想初步(OOP初步Object Oriented Programming) 53 面向对象编程的语言的三大特征简介 56 对象和类的概念 56 类和对象初步 57 测试类的定义方式 57 简单的学生类编写示例 58 内存分析 59 属性(field,或者叫成员变量) 59 引用类型 60 类的方法 60 对象的创建和使用 60 构造器(或者叫做构造方法,constructor) 60 垃圾回收机制(Garbage Collection) 63 方法的重载(overload),构造方法的重载 63 this关键字 65 static 关键字 66 静态初始化块(经常用来初始化类,加载类信息时执行!) 67 package 68 JDK中的主要包 68 import 68 eclipse的使用 69 继承(extend, inheritance) 70 为什么需要继承?继承的作用? 70 继承介绍 70 如何实现继承? 70 继承使用要点 71 Object类 72 toString方法 72 equals方法 73 super关键字 74 方法的重写(override) 74 隐藏/封装(encapsulation) 75 为什么需要封装?封装的作用和含义? 75 使用访问控制符,实现封装 76 封装的使用细节 76 多态(polymorphism) 76 为什么需要多态? 76 如何实现多态? 77 方法绑定(method binding) 77 静态绑定 77 动态绑定 77 多态的使用要点 78 对象的转型(casting) 79 final 81 抽象类 82 抽象类的使用要点 83 接口 83 为什么需要接口? 84 如何定义接口? 84 接口的本质探讨 84 接口使用要点 85 接口的多继承 86 面向接口编程 87 OOP更多应用 87 组合 87 内部类(innerclasses) 88 字符串(java.lang.String类)的使用 90 字符串相等的判断 92 思考作业 93 上机作业 94 第四章 异常机制 95 导引问题 95 异常(Exception)的概念 96 异常分类 96 Error 97 Error和Exception的区别 97 Exception 97 异常的处理办法之一,捕获异常 99 try块 99 catch 99 finally 100 try, catch,finally ,return 执行顺序 100 异常的处理办法之二,声明异常: throws子句 101 方法重写中声明异常原则 102 异常的处理办法之三,手动抛出异常,throw子句 103 自定义异常 103 使用异常机制建议 104 总结 105 思考作业 105 上机作业 105 第五章 数组 106 数组概述和特点 106 创建数组和初始化 106 数组常见操作 108 数组的拷贝 108 数组排序 109 多维数组 110 附录(面试前复习一下!!) 111 冒泡排序 111 二分法查找 112 命令行参数的问题 113 增强for循环 114 思考作业 114 上机作业 115 第六章 常用类的使用 117 基本数据类型的包装类 117 包装类基本知识 117 包装类的用途 118 自动装箱和拆箱?autoboxing,unboxing 119 字符串相关类(StringStringBuffer 、 StringBuilder) 120 String类的常用方法(已讲过,不再讲!) 120 StringBuffer和StringBuilder 121 StringStringBuffer和StringBuilder使用要点 123 时间处理相关类 124 Date时间类(java.util.Date) 124 DateFormat类和SimpleDateFormat类 125 Calendar日历类 126 可视化日历的编写 128 Math类 131 File类 132 File类的基本用法 132 树状结构展现文件结构 133 枚举 133 上机作业 135 第七章 容器(Collection) 136 容器的作用和概览 136 容器中的接口层次结构 136 Collection接口 137 LIST接口 137 SET接口 138 Map接口 138 Iterator接口 139 遍历集合 140 Collections工具类 141 Comparable接口 141 equals和hashcode方法 143  泛型 144 思考作业 145 上机作业 145 第八章 IO技术 146 为什么需要学习IO技术 146 基本概念 146 数据源 146 流的概念 146 第一个简单的IO流程序及深入(将文件中的数据读入) 146 Java中流的概念细分 148 Java中IO流类的体系 149 四个IO基本抽象类 150 InputStream 150 OutputStream 150 常用InputStream和OutputStream子类用法 150 FileInputStream和FileOutputStream 150 ByteArrayInutStream和ByteArrayOutputStream 154 BufferedInputStream和BufferedOutputStream 156 DataInputStream和DataOutputStream 157 ObjectInputStream和ObjectOutputStream 158 PrintStream 158 Reader 158 Writer 159 FileReader和FileWriter 159 BufferReader和BufferWriter 159 InputStreamReader和OutputStreamWriter 161 JAVA对象的序列化和反序列化 161 为什么需要序列化和反序列化 161 对象的序列化主要有两种用途 161 序列化涉及的类和接口 162 序列化/反序列化的步骤和实例 162 综合的序列化和反序列化练习 163 JAVA.IO包相关流对象用法总结(尚学堂1002班王鑫) 165 IO中其他常用类 165 File类 165 RandomAccessFile 166 思考作业 166 上机作业 166 提高课外作业 166 第九章 多线程技术 167 基本概念 167 程序 167 进程 167 线程 167 线程和进程的区别 167 进程与程序的区别 168 JAVA中如何实现多线程(重点!!) 168 通过继承Thread类实现多线程 168 通过Runnable接口实现多线程 169 线程状态和sleep/yield/join/stop/destroy方法 170 新生状态 170 就绪状态 170 运行状态 170 死亡状态 170 终止线程的典型方法(重要!!!) 171 阻塞状态(sleep/yield/join方法) 171 线程基本信息和优先级别 173 线程同步和死锁问题 175 死锁及解决方案 179 生产者/消费者模式 181 线程回顾总结 184 任务调度(补充内容,了解即可!) 184 思考作业 185 上机作业 185 第十章 网络编程 186 基本概念 186 什么是计算机网络 186 计算机网络的主要功能 186 什么是网络通信协议 186 网络通信接口 186 为什么要分层 186 通信协议的分层规定 186 数据封装 188 数据拆封 188 IP 188 端口 188 URL 189 TCP协议和UDP协议 189 区别 189 TCP协议 189 UDP协议 190 JAVA网络编程 190 InetAddress 190 InetSocketAddress 191 URL类 191 基于TCP协议的SOCKET编程和通信 193 UDP通讯的实现 201 思考作业 203 上机作业(分组完成,3人一组,周末完成) 204 第十一章 JAVA多媒体编程 205 字体 205 字体示例和效果 205 颜色 206 颜色编程示例 206 图形绘制 206 绘制各种图形示例 207 图像处理 208 加载图片示例 208 图片任意缩放并输出新图片 209 使用JMF处理音频和视频 211 JMF的下载 211 JMF的安装和配置 211 使用JMF播放音频文件 212 上机作业 213 第十二章 GUI编程之AWT 214 为什么需要GUIGUI是什么? 214 AWT是什么? 214 AWT的优势 214 AWT的缺点 214 为什么还需要学习AWT? 215 AWT各组件的整体关系 215 组件 215 容器 216 布局管理器 218 为什么需要布局管理器? 218 FlowLayout布局管理器 218 BorderLayout布局管理器 219 CardLayout布局管理器 220 GridLayout布局管理器 220 GridBagLayout布局管理器 221 综合应用组件和容器和布局管理器 221 AWT事件处理模型 223 问题 223 事件处理模型是什么? 223 最简单的事件处理程序 224 AWT中事件处理代码编写 225 编写过程 225 处理过程 225 简化理解上面过程 225 事件和事件源对象 225 事件适配器 232 为什么需要事件适配器 232 事件监听器常见的定义形式 233 AWT其他组件 233 菜单的实现 233 特点 233 代码示例和效果 233
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值