Bootstrap

用JAVA写一个画图小程序(JAVA 大作业)

第一次写博客 且是稍微大点的程序 看看就行
重新写的在这,更加清晰明了: 用Java写一个画图程序
  • 设计思路

首先我直接去了Windows自带画图程序去实践模拟,看看具体方法,进行了布局和按钮的思考。

容器顶层放工具栏,工具栏中存放图形按钮、工具按钮、颜色按钮。对于图形按钮,存放在垂直的Box中,分成行列,设置边框,设置标签,加入JToolbar;对于工具按钮设置Jpanel保存,线条粗细设置垂直Box存储,设置边框后,最后将两者加入水平Box中,与前面图形按钮设置间隔加入JToolbar中,设置标签;对于颜色按钮,画布背景颜色选择按钮单独设置大按钮,直接加入JToolbar,而颜色选择按钮我设置了JPanel,垂直Box进行组装,成行列,设置边框,加入JToolbar中。最后的颜色选择编辑按钮,也是单独大按钮,加入JToolbar中。

最终效果如下:

这个程序,难在哪里?难在工具栏中按钮监听。对于工具栏中各个按钮,我要实时监听。但是对于工具栏中每一个按钮我都进行行为监听,适配器模式也招架不住程序的紊乱。于是写一个类,继承MouseAdapter、ActionListener,重写部分监听方法。

对于行为监听、鼠标抓取,进行了一些操作,下面类的设计会详细讲解。在这个过程中我对所有图形特征进行了分析,顺着这个我也创建了一个Shape类,类属性包含了所有图形的特征。可是工具栏不单单有图形构画,还有橡皮擦、颜色、粗细,为此我对Shape里面的属性做了调整,可以存储当前线条颜色、粗细。

对于菜单的设计较为简单,因为没有太强逻辑可言,你想让它如何展现,展现什么内容,就怎样重写监听方法。我设计了文件保存、打开以及画图的属性、帮助、界面风格。写了一个MyMenu类,进行相关设计组装,下面也会具体讲解。

图画刷新展示需要进行repaint,我写了MyFrame类继承JFrame,重写paint方法。在MyJFrame类中有参构造的同时,我也进行了画图的图标处理,这样更加更美观。

最终就是拼装完成,设置界面,进行显示。

总结:利用java中的现有类,创建主窗口、创建主窗口中内容、设置事件响应机制、显示主窗口、进入消息循环接收处理。

 

输入java Painter conf/Painter-1.ini 展示圆、矩形

输入java Painter conf/Painter-2.ini 展示圆、矩形、三角形、菱形、多边形

其中功能按钮、曲线、直线会一直展现。

  • 类的设计

(一)

  1. MyMouseListener

继承MouseAdapter适配器和MouseListner接口,进行一些方法重写。通过重写actionPerformed,先行获取按钮内容,设置属性。

(1)曲线、橡皮擦、喷枪、毛笔

在mouseDragged方法中进行行为监听。这个拖拽的过程是需要实时更新的,坐标也需要实时更新。

<1>曲线:是由无数直线拼接而来,不断创建shape,draw。这里我最开始也是用的实心圆,但是太曲折了,在网上资源也都是直线。

直线:

实心圆:

这里对比明显直线更胜一筹。

<2>橡皮擦:我的思路是在鼠标拖拽相应位置同样创建shape,而这些shape对象的颜色则为画布初始颜色,draw,实现橡皮擦功能。

<3>喷枪、毛笔:都是范围内进行喷画,可以用循环画无数条线的方式解决,并且这些直线是不连续的、随机的。可以使用Random循环一次次创建shape,draw。毛笔与喷枪不同的一点就是墨水会减少,需要记录刷新,画笔变细,最后为空,当再次pressed、released刷新“墨水”。

  1. 刷新、上一步

在actionPerformed中获取按钮内容之后,根据if条件,对vector中的数据进行更新。这里刷新之后,上一次vector数据要存起来,防止下次点击上一步不展示任何内容,利用两个vector互相转换,repaint。

  1. 直线、矩形、圆、三角形、菱形

重写mousePressed和mouseReleased方法,获取两顶点坐标,算法得出左上角坐标与右下角坐标。

<1>直线、矩形、圆、椭圆

在库中有相应的draw方法,对于直线、矩形,在已知坐标的情况,创建shape,draw;对于圆,我设置圆心为pressed点,计算拖拽距离为r,创建shape,draw;对于椭圆,我则是借鉴Windows画图中的画法,通过拖拽坐标得宽高,创建shape,draw。

<2>三角形、菱形

三角形:直角三角形、等腰三角形,根据拖拽坐标,找寻第三个点,创建三个type为直线的shape,draw三条线。

菱形:根据拖拽点计算四个点坐标,创建四个type为直线的type,draw四条直线。

  1. 多边形

对于多边形,获取坐标,相邻的点进行联接,创建多个type为直线的shape。并且存着首个坐标点,双击鼠标,可将图形封闭为多边形。

  1. 画笔粗细、颜色编辑

画笔粗细:根据按钮对象,创建stroke对象,在下次画图时传入shape中。

画布颜色:调用JColorChooser.showDialog进行选择,改变背景颜色,同时改变按钮边框颜色。

画笔颜色:根据点击的按钮,设置画笔相应的颜色,存入color对象,传入shape中。另外对点击的图形按钮设置相应的边框颜色。

按钮颜色编辑:自选颜色后可填入按钮中,并且设置循环,可反复覆盖。

  1. Shape

这个类实现了每一个图形的数据存储,包含两个坐标点,画笔颜色,图形类型,画笔粗细。

对于直线、椭圆、圆、矩形,两个坐标即可画出图形。多边形、三角形、喷枪、毛笔、橡皮擦,则是由若干条线段构成的,只需创建多个类型为直线的shape对象。color,stroke对象记录当前画笔颜色,画笔粗细。

  1. MyFrame

继承JFrame类,设置两个vector,v和v1的目的 v和v2都有存储图形的功能 用于遍历repaint。另外我可以当刷新时,将v的所有数据送给v1。回退时,我可以遍历v1,避免空画。

有参(title)构造MyFrame,并且在其中进行图标的设置。对paint方法进行重写,首先调用父类中的方法。其次将Graphics强制转换为Graphics2D,为了实现画笔粗细。在遍历过程中对shape中stroke,color进行获取,根据type画图。

其中直线、曲线、三角形、多边形的type都是直线,drawLine即可;圆、椭圆、矩形,则根据shape中两个坐标获取左上角坐标,计算大小,调用draw或fill方法。

最后若存在image,则drawImage,打开文件照片之后画布可以显示图片信息。

  1. MyMenu

构建菜单,设置菜单条,创建MyMouseListener对象。在类中构造方法,创建菜单条。

文件菜单下创建打开、保存、属性、帮助等menuItem,并且过程中直接设置行为监听。

其中文件打开,JFileChooser默认为当前目录,选取文件进行获取,repaint图片到画布;文件保存,可在一定的区间范围内,画布保存为图片,默认保存路径为当前目录,获取选择文件路径,图片格式为jpg,如果保存了,则设置flag为真,关闭程序不会弹出窗口。

属性和帮助则是弹出窗口显示相关说明性信息。

格式菜单下设置了几种风格,可以进行选择刷新组件外观,更换界面主题,并且repaint,将图画信息转移到当前画布。

最后所有菜单装入菜单条,组装至容器中。

  1. MyJToolbar

继承JToolbar类。创建一个初始化方法,对于按钮则可以设计for循环,进行创建加入图片,监听,装入工具栏;对于线条粗细的按钮,设置了一个垂直的Box拼装,监听,装入工具栏;对于颜色按钮,则仅仅设置颜色,监听,装入工具栏。

最后将工具栏装入JFrame的顶部。

  1. Main

创建上述几个类的对象,调用各类初始化方法,组装,展示。

(二)

类之间的联系

     我并没有采用复杂的继承关系和设置抽象的类与接 口,只是继承了java中的一些原有的类和接口,便于 更好地实现自己写的一些类以及继承相应的父类不可 少的方法。其次对于自己写的几个类,我的耦合度可 能高一点,不太好可能,可能也不是,因为各类中都 有创建其他类的对象解决一些问题,各类中有创建相 应变量进行关联,改变窗口界面。

简单地继承:

类之间的一些联系:

 代码实现

1.Painter 主函数

package com.zzu;

import javax.swing.*;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;

//框架:最上层Menu --->文件保存 导入 界面刷新(清除功能) 风格更换
//     Menu下面放工具栏 绘制各种图形 颜色 线条粗细 橡皮擦 字体模式大小
//     关闭界面可以弹出警告窗口 设置监听器是否保存
//     画布 默认CENTER

//Content:
//          一个shape类存储Point color type
//          矩形: press、release为两侧顶点坐标 通过计算可得左上角右下角坐标及长宽 drawRect(Math.min(x1,x2), Math.min(y1,y2), Math.abs(x2-x1),Math.abs(y2-y1));
//          直线: Mouse press release   drawLine
//          圆: Mouse press release   drawOval
//          菱形: Mouse press release   drawLine
//          喷枪: random 的实心圆 或 坐标点  fillOval
//          曲线: 这就需要MouseDrag监听不断更新起点和终点 Point1->Point2 Point2->新Point 无限小直线 drawLine
//          多边形:release clicked drawLine
//          橡皮擦:区域内添加新图形 颜色为画布颜色 橡皮擦大小也跟画笔粗细相关联
//          回退:返回上一步
//          刷新:画布删除所有图形
//          粗细:  Graphics2D Stroke
//          画布背景颜色:  JColorChooser

public class Painter {

    //创建窗口对象
    MyFrame jf = new MyFrame("画图");

    //创建画图内容监听对象
    MyMouseListener myMouseListener = new MyMouseListener();

    //弹出文件窗口放在JPanel 防止保存图片时出现问题
    JPanel jPanel = new JPanel();

    //创建菜单对象
    MyMenu myMenu = new MyMenu(jf,jPanel);

    //创建工具栏对象
    MyJToolbar myJToolbar =  new MyJToolbar(jf,myMouseListener);

    //空参构造
    public Painter(){
    }

    /**
     * @param file
     * @return ArrayList 文件内容以若干个字符串传出
     * @throws IOException
     */
    public static ArrayList<String> getFileContent(String file) throws IOException {

        BufferedReader reader = new BufferedReader(new FileReader(file));
        //这里ArrayList是为了存储不确定个数的String
        ArrayList<String> content = new ArrayList<>();
        //获取文件中字符串
        String line = "";
        try{
            while((line=reader.readLine())!=null){
                //首个属性值
                if(line.contains("[")) {
                    continue;
                }
                String str[] = line.split("=");//分割出内容
                if(str.length>1)  line = str[1];
                else line = str[0];

                line = line.trim();//去除首尾空格
                String str1[] = line.split(",");//逗号分隔开若干个
                //遍历加入list
                for (String s : str1) {
                    content.add(s);
                }
            }
            return content;
        }finally{//始终保持文件最终关闭
            reader.close();
        }

    }


    /**
     * 展示界面
     * @throws Exception
     */
    public void showUI() throws Exception {

        //读取图形数据
        Scanner sc = new Scanner(System.in);
        String file = sc.next();
        ArrayList<String> list = getFileContent(file);
        myJToolbar.setList(list);

        //JFrame设置监听 可以任意画曲线
        //添加鼠标监听器
        jf.addMouseListener(myMouseListener);
        jf.addMouseMotionListener(myMouseListener);
        //将JFrame传过去 实现repaint
        myMouseListener.setMyFrame(jf);

        //初始化菜单加入到JFrame中
        myMenu.init();

        //初始化工具栏加入JFrame中
        myJToolbar.init();

        //设置界面大小
        jf.setSize(1100,800);
        //居中显示
        jf.setLocationRelativeTo(null);
        //设置可见
        jf.setVisible(true);
        //关闭进程
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) throws Exception {
        new Painter().showUI();
    }
}


2.MyMenu 菜单类

package com.zzu;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;

public class MyMenu {
    private MyFrame jf;
    private JPanel jPanel;

    //菜单项组件
    //菜单条
    public JMenuBar jMenuBar = new JMenuBar();

    //两个菜单
    public JMenu fileMenu = new JMenu("文件");
    public JMenu styleMenu = new JMenu("界面风格");

    //确定文件是否被保存
    public boolean flag = false;

    //获取画布图片信息或者获取文件中图片信息
    public BufferedImage image;

    public MyMenu() {
    }

    public MyMenu(MyFrame jf, JPanel jPanel) {
        this.jf = jf;
        this.jPanel = jPanel;
    }

    /**
     * 组装菜单
     * @throws Exception
     */
    public void init() throws Exception {
        //fileMenu下的条目菜单 对应设置行为监听
        //图片打开
        JMenuItem open = new JMenuItem(new AbstractAction("打开",new ImageIcon("打开.png")) {
            @Override
            public void actionPerformed(ActionEvent e) {
                //显示一个文件选择器 当前目录
                JFileChooser fileChooser = new JFileChooser(".");
                fileChooser.showOpenDialog(jPanel);
                //获取选择的文件
                File file = fileChooser.getSelectedFile();
                //如果选择了文件 就进行相关操作
                if(file != null){
                    //进行显示
                    try {
                        image = ImageIO.read(file);
                        //将信息传进MyFrame中
                        jf.setImage(image);
                        jf.setIg(true);
                        jf.repaint();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        });
        //图片保存
        JMenuItem save = new JMenuItem(new AbstractAction("保存",new ImageIcon("保存.png")) {
            @Override
            public void actionPerformed(ActionEvent e) {
                //显示一个文件选择器 当前目录
                JFileChooser fileChooser = new JFileChooser(".");
                fileChooser.showSaveDialog(jPanel);
                //获取用户选择的保存文件路径
                File file = fileChooser.getSelectedFile();
                //如果只是打开目录没有保存 则不进行下面操作
                if(file!=null){
                //保存
                try {
                    try {
                        //画布保存为图片 设置区域范围大小
                        image = new Robot().createScreenCapture(new Rectangle(jf.getX(),jf.getY()+250, jf.getWidth(),jf.getHeight()-250));
                    } catch (AWTException ex) {
                        throw new RuntimeException(ex);
                    }
                    ImageIO.write(image,"jpg",file);//存入文件
                    flag = true;//保存成功
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
            }
        });
        //关于画图
        JMenuItem attribute = new JMenuItem(new AbstractAction("关于画图",new ImageIcon("关于画图.png")) {
            //展示特性
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(jf,null,"关于画图",JOptionPane.DEFAULT_OPTION,new ImageIcon("属性.png"));
            }
        });
        //帮助
        JMenuItem help = new JMenuItem(new AbstractAction("帮助",new ImageIcon("帮助.png")) {
            //展示内容
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(null, "" + "******************\n" + "#画图软件使用说明书#\r\n"
                        + "******************\n"
                        + "1.本软件主要分为四个模块:菜单、工具栏、调色板、和画布\n"
                        + "(1)菜单栏的文件子菜单包括打开、保存图片以及帮助,关于我们\r\n"
                        + "(2)工具主要包括清空画板、撤回操作、图形绘制\n"
                        + "(3)调色板位于界面的顶侧工具栏,用于改变画布背景颜色或设置画笔的颜色\n"
                        + "(4)画布用于图形绘制,使用鼠标选中要绘制的图形即可进行绘制\n"
                        +"2.具体操作:\n"
                        +"(1)可在画布绘制任意曲线\n"
                        +"(2)矩形、三角形、菱形、箭头:鼠标拖拽 获得两顶点系统会算法确定位置大小\r\n"
                        +"(3)圆:鼠标拖拽 press点和release点分别为圆心和圆上一点\n"
                        +"(4)多边形:相邻的两点连接,双击则可形成封闭图形\n"
                        +"(5)橡皮擦、喷枪 鼠标点击拖拽即可\n"
                        +"(6)画笔粗细选择\n"
                        +"(7)背景颜色、画笔颜色可自动选择 选择之后点击按钮 按钮边框也会变色\n"
                        +"(8)最后的编辑颜色可以配色进行选择\n", "使用说明", JOptionPane.PLAIN_MESSAGE);
            }
        });

        //styleMenu风格单选
        ButtonGroup buttonGroup = new ButtonGroup();
        JRadioButtonMenuItem metalItem = new JRadioButtonMenuItem("Metal 风格");
        JRadioButtonMenuItem windowsItem = new JRadioButtonMenuItem("Windows 风格",true);//默认
        JRadioButtonMenuItem windowsClassicItem = new JRadioButtonMenuItem("Windows 经典风格");
        JRadioButtonMenuItem motifItem = new JRadioButtonMenuItem("Motif 风格");

        //风格选择监听 避免多次重写
        ActionListener listener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    //获得内容 传送设置风格
                    changeFlavor(e.getActionCommand());
                    //风格改变后需要重画
                    jf.repaint();
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        };
        //退出若未保存则弹出提示窗口
        jf.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                if (!flag) {
                    // 弹出对话框 提示未保存
                     JOptionPane.showOptionDialog(jf,"你还没保存,确定要退出吗?","BBQ",JOptionPane.DEFAULT_OPTION
                            ,JOptionPane.WARNING_MESSAGE,null,new String[]{"确定","非常确定"},"确定");
                }
            }
        });

        //组装风格菜单
        buttonGroup.add(metalItem);
        buttonGroup.add(windowsItem);
        buttonGroup.add(windowsClassicItem);
        buttonGroup.add(motifItem);
        //添加监听器
        metalItem.addActionListener(listener);
        windowsItem.addActionListener(listener);
        windowsClassicItem.addActionListener(listener);
        motifItem.addActionListener(listener);
        //加入styleMenu
        styleMenu.add(metalItem);
        styleMenu.add(windowsItem);
        styleMenu.add(windowsClassicItem);
        styleMenu.add(motifItem);

        //文件以及帮助菜单
        fileMenu.add(open);
        fileMenu.add(save);
        fileMenu.add(attribute);
        fileMenu.add(help);

        //菜单栏
        jMenuBar.add(fileMenu);
        jMenuBar.add(styleMenu);

        //将组装好的菜单栏装入JFrame中
        jf.setJMenuBar(jMenuBar);
    }

    /**
     *
     * @param command
     * @throws Exception
     * 此方法用于改变界面风格
     */
    public void changeFlavor(String command) throws Exception {
        switch (command) {
            case "Metal 风格":
                UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
                break;
            case "Windows 风格":
                UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
                break;
            case "Windows 经典风格":
                UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
                break;
            case "Motif 风格":
                UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
                break;
        }

        //刷新组件的外观
        SwingUtilities.updateComponentTreeUI(jf.getContentPane());
        SwingUtilities.updateComponentTreeUI(jMenuBar);

    }

    public MyFrame getJf() {
        return jf;
    }

    public void setJf(MyFrame jf) {
        this.jf = jf;
    }

    public JPanel getjPanel() {
        return jPanel;
    }

    public void setjPanel(JPanel jPanel) {
        this.jPanel = jPanel;
    }

}

3.MyJToolbar 工具栏类

package com.zzu;

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

public class MyJToolbar extends JToolBar{
    static int num = 12;//当前第几个颜色选择框
    private MyFrame jf;
    private MyMouseListener myMouseListener;
    private ArrayList<String> list;

    public MyJToolbar() {
    }


    public MyJToolbar(MyFrame jf, MyMouseListener myMouseListener) {
        //声明工具条相关内容
        super("工具栏",SwingConstants.HORIZONTAL);
        this.setBackground(new Color(0xCFFCE6));
        this.jf = jf;
        this.myMouseListener = myMouseListener;
    }

    //TODO 工具条拖动画布内容消失问题
    @Override
    public void setFloatable(boolean b) {
        super.setFloatable(b);
        if(b) jf.repaint();
    }

    /**
     * 组装工具栏
     */
    public void init(){
        //组装工具栏
        //工具栏内容从最左侧开始填充
        this.setLayout(new FlowLayout(FlowLayout.LEFT));
        for (int i = 0; i < 3; i++) {
            //设置间隔 美观清晰
            this.addSeparator();
        }

        //第一行的这些图形功能是刚需的 不需要判断
        //第二行的需要根据文件读取图形数据判断是否加入
        String[] image1 = {"直线.png", "曲线.png","空心矩形.png", "实心矩形.png", "空心圆.png","实心圆.png","空心椭圆.png","实心椭圆.png",
                "直角三角形.png","等腰三角形.png","菱形.png", "多边形.png"};
        Box vBox = Box.createVerticalBox();
        JLabel jLabel1 = new JLabel("图形选择");
        JPanel jPanel1 = new JPanel();
        JPanel jPanel2 = new JPanel();
        //遍历组装图形功能按钮
        for (int i = 0; i < image1.length; i++) {
            boolean contain = false;
            if(i<2)   contain = true;//前几个图形不需要判定是否存在在文件中
            else {//文件中是否存在后面图形
                for(String s:list){
                    if(image1[i].contains(s))   {
                        contain = true;
                        break;
                    }
                }
            }

            if(contain){
                //JButton图片内容
                JButton jbt = new JButton();
                ImageIcon icon = new ImageIcon(image1[i]);//实例化ImageIcon对象
                icon.setImage(icon.getImage().getScaledInstance(30,30,Image.SCALE_DEFAULT));//设置图片大小与JButton大小匹配
                jbt.setIcon(icon);//加入JButton中
                //按钮大小
                jbt.setPreferredSize(new Dimension(35,35));
                //设置监听器
                jbt.addActionListener(myMouseListener);
                //默认边框蓝色
                jbt.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
                if(i<6) jPanel1.add(jbt);
                else    jPanel2.add(jbt);
            }
        }
        vBox.add(jPanel1);
        vBox.add(jPanel2);
        vBox.add(jLabel1);
        //vBox.setSize(210,80);
        vBox.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED, Color.RED, Color.GREEN));
        this.add(vBox);
        for (int i = 0; i < 3; i++) {
            //设置间隔 美观清晰
            this.addSeparator();
        }

        //工具按钮
        //水平Box存取工具 线条
        Box hBox = Box.createHorizontalBox();
        //设置边框
        hBox.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED, Color.RED, Color.GREEN));
        //存取按钮
        JPanel jPanel3 = new JPanel();
        JLabel jLabel2 = new JLabel("工具");
        String[] image2 = {"喷枪.png","毛笔.png", "橡皮擦.png","上一步.png","刷新.png"};
        //遍历组装图形功能按钮
        for (int i = 0; i < image2.length; i++) {
            //JButton图片内容
            JButton jbt = new JButton();
            ImageIcon icon = new ImageIcon(image2[i]);//实例化ImageIcon对象
            icon.setImage(icon.getImage().getScaledInstance(35,35,Image.SCALE_DEFAULT));//设置图片大小与JButton大小匹配
            jbt.setIcon(icon);//加入JButton中
            //按钮大小
            jbt.setPreferredSize(new Dimension(40,40));
            //设置监听器
            jbt.addActionListener(myMouseListener);
            //默认边框蓝色
            jbt.setBorder(BorderFactory.createLineBorder(Color.CYAN, 2));
            jPanel3.add(jbt);
        }

        //Vbox用来处置组装线条粗细按钮
        Box vBox2 = Box.createVerticalBox();
        //线条粗细
        String[] stroke = {"stroke1.png","stroke2.png","stroke3.png","stroke4.png"};
        //遍历设置监听和属性
        for(int i = 0;i<stroke.length;i++){
            //JButton粗细内容
            JButton jbt = new JButton();
            //实例化对象
            ImageIcon icon = new ImageIcon(stroke[i]);
            //设置图片大小与JButton大小匹配 加入按钮
            icon.setImage(icon.getImage().getScaledInstance(50,20,Image.SCALE_DEFAULT));
            jbt.setIcon(icon);
            //按钮大小
            jbt.setPreferredSize(new Dimension(50,20));
            //监听
            jbt.addActionListener(myMouseListener);
            vBox2.add(jbt);
        }
        vBox2.add(jLabel2);
        //hBox.setSize(210,80);
        hBox.add(jPanel3);
        hBox.add(vBox2);
        //加入工具条
        this.add(hBox);
        for (int i = 0; i < 3; i++) {
            //设置间隔 美观清晰
            this.addSeparator();
        }

        //设置背景颜色选择按钮
        JButton colorBack = new JButton(new ImageIcon("颜色.png"));
        colorBack.setBorder(BorderFactory.createLineBorder(Color.CYAN, 3));
        //设置监听
        colorBack.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //弹出颜色选择器
                Color color =  JColorChooser.showDialog(jf,"背景颜色选择器",Color.PINK);
                colorBack.setBorder(BorderFactory.createLineBorder(color, 3));
                //修改背景颜色 同时需要重画
                jf.getContentPane().setBackground(color);
                jf.repaint();
            }
        });
        this.add(colorBack);

        //三行颜色选择按钮
        Box vBox3 = Box.createVerticalBox();
        //vBox3.setSize(new Dimension(150,75));
        //三个JPanel分别装一行颜色按钮
        JPanel jPanel6 = new JPanel();
        JPanel jPanel7 = new JPanel();
        JPanel jPanel8 = new JPanel();
        //创建画笔颜色按钮
        Color[] color = {Color.WHITE,Color.BLACK,Color.CYAN,Color.BLUE,Color.PINK,Color.RED,
                Color.ORANGE,Color.MAGENTA,Color.GREEN,Color.GRAY,Color.DARK_GRAY,Color.LIGHT_GRAY,
                Color.WHITE,Color.WHITE,Color.WHITE,Color.WHITE,Color.WHITE,Color.WHITE};
        //设置n个按钮 这样实例化为了后面改变颜色选择按钮颜色
        JButton jbt[] = new JButton[color.length];
        //遍历组装按钮
        for (int i = 0; i < color.length; i++) {
            jbt[i] = new JButton(new ImageIcon(""));
            //按钮大小
            jbt[i].setPreferredSize(new Dimension(25,25));
            //按钮添加相应背景色
            jbt[i].setBackground(color[i]);
            //监听点击的背景色
            jbt[i].addActionListener(myMouseListener);
            if(i<6)  jPanel6.add(jbt[i]);
            else if(i<12) jPanel7.add(jbt[i]);
            else jPanel8.add(jbt[i]);
        }
        //将三个JPanel加入vBOX中
        vBox3.add(jPanel6);
        vBox3.add(jPanel7);
        vBox3.add(jPanel8);
        //设置边框以及一些边框属性
        vBox3.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED, Color.RED, Color.GREEN, Color.BLUE, Color.GRAY));
        //加入工具条
        this.add(vBox3);

        //设置背景颜色选择按钮
        JButton colorEdit = new JButton(new ImageIcon("编辑颜色.png"));
        colorEdit.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED, Color.RED, Color.GREEN));
        //设置监听
        colorEdit.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //弹出颜色选择器
                Color color =  JColorChooser.showDialog(jf,"按钮颜色编辑器",Color.PINK);
                //修改最后一行按钮颜色
                if(num==18){//都已经修改过 从头开始
                    num = 12;
                    jbt[num++].setBackground(color);
                }
                else  jbt[num++].setBackground(color);
            }
        });
        //加入工具条
        this.add(colorEdit);

        //TODO 界面为什么会为空 在哪repaint解决
        //让工具条可以拖动
        //this.setFloatable(true);

        //让工具条不可以拖动
        this.setFloatable(false);
        //工具栏居上
        jf.add(this,BorderLayout.NORTH);
    }



    public MyFrame getJf() {
        return jf;
    }

    public void setJf(MyFrame jf) {
        this.jf = jf;
    }

    public MyMouseListener getMyMouseListener() {
        return myMouseListener;
    }

    public void setMyMouseListener(MyMouseListener myMouseListener) {
        this.myMouseListener = myMouseListener;
    }

    public ArrayList<String> getList() {
        return list;
    }

    public void setList(ArrayList<String> list) {
        this.list = list;
    }
}

4.MyMouseListener 监听器类

package com.zzu;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

class MyMouseListener extends MouseAdapter implements ActionListener {
    //记录当前所选颜色 默认为蓝色
    public Color color = Color.BLACK;

    //Point1 Point2
    public int x1=0,y1=0,x2=0,y2=0;

    //ployX,ployY是多边形的第一个点
    public int ployX,ployY, ployX1, ployY1;

    //按钮名称 默认为null 可直接画曲线
    public String ActionCommand="";

    //判断多边形第一条边是否画出
    public boolean first;


    //毛笔模拟计时器 控制“墨汁”
    public int time,t;

//    //鼠标是否是静态
//    public int status;

    //毛笔是否已经完成上一次画线 默认为真
    boolean last = true;

    //数据存储
    public Vector<Shape> v = new Vector<>();

    //最多有四个shape(菱形四条边)
    public Shape s=null;
    public Shape s1=null;
    public Shape s2=null;
    public Shape s3=null;

    //字体粗细
    public Stroke stroke = new BasicStroke(1);//默认粗细为1

    //获得容器
    private MyFrame myFrame;

    //实时画图
    private Graphics2D g;

    public MyMouseListener() {
    }

    public MyMouseListener(MyFrame myFrame, Graphics2D g) {
        this.myFrame = myFrame;
        this.g = g;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        g = (Graphics2D) myFrame.getGraphics();
        g.setXORMode(myFrame.getBackground());
        //获取图片名字
        JButton jButton = (JButton)e.getSource();
        String command = jButton.getIcon().toString();
        //此处防止上次的点干扰此次作图
        x1=x2;
        y1=y2;
        if(command.equals("")){
            //提取颜色
            color = jButton.getBackground();
        }
        else if(command.equals("刷新.png")){
            //这里写if可以保证多次刷新数据也不会丢失
            if(v.size()>0){
            //保留刷新前的点 避免回退什么也不打印
            Vector<Shape> v1 = new Vector<>(v);
            myFrame.setV1(v1);
            }
            //删除所有点
            v.removeAllElements();
            //这里将back重置为false 不然会出现bug
            myFrame.setBack(false);
            myFrame.setV(v);
            myFrame.repaint();
        }
        else if (command.equals("上一步.png")) {
            int size = v.size();
            //有图形则删 无则不管
            if(size>0){
                v.remove(v.size()-1);
                myFrame.setV(v);
                myFrame.repaint();
            }
            else if(myFrame.getV1().size()>0){
                myFrame.setBack(true);
                myFrame.repaint();
            }
        }//字体粗细 并设置当前画笔粗细
        else if(command.equals("stroke1.png")){
            stroke = new BasicStroke(1);
            g.setStroke(stroke);
        }
        else if(command.equals("stroke2.png")){
            stroke = new BasicStroke(2);
            g.setStroke(stroke);
        }
        else if(command.equals("stroke3.png")){
            stroke = new BasicStroke(4);
            g.setStroke(stroke);
        }
        else if(command.equals("stroke4.png")){
            stroke = new BasicStroke(7);
            g.setStroke(stroke);
        }
        else{//橡皮擦就不要设置边框了
            if(!command.equals("橡皮擦.png")){
                JButton jbt = (JButton) e.getSource();
                //设置边框颜色 提醒画笔颜色
                //将所选按钮框改变为当前已选颜色
                jbt.setBorder(BorderFactory.createLineBorder(color, 2));
                jbt.setBackground(color);
            }
            //画笔颜色
            g.setColor(color);
            //获取图形
            ActionCommand=command;
            if(ActionCommand.equals("多边形.png")){
                //第一条边未画
                first = false;
            }
            else if(ActionCommand.equals("毛笔.png")) {
                //计时器0
                time = 0;
            }
        }
    }

    //得到按压起点坐标
    @Override
    public void mousePressed(MouseEvent e) {
        x1 = e.getX();
        y1 = e.getY();
    }
    //得到释放终点坐标
    @Override
    public void mouseReleased(MouseEvent e) {
        ployX1 = x2;
        ployY1 = y2;
        x2 = e.getX();
        y2 = e.getY();

        //如果released 那么毛笔墨汁刷新
        //计时器都刷新
        t=time=0;
        last = true;

        if(ActionCommand.equals("直角三角形.png")){
            Shape s = new Shape(x1,y1,x2,y2,color,"直线",stroke);
            v.addElement(s);
            g.drawLine(x1,y1,x2,y2);
            Shape s1 = new Shape(Math.min(x1,x2),Math.max(y1,y2),x1,y1,color,"直线",stroke);
            v.addElement(s1);
            g.drawLine(Math.min(x1,x2),Math.max(y1,y2),x1,y1);
            Shape s2 = new Shape(Math.min(x1,x2),Math.max(y1,y2),x2,y2,color,"直线",stroke);
            v.addElement(s2);
            g.drawLine(Math.min(x1,x2),Math.max(y1,y2),x2,y2);

        }
        else if(ActionCommand.equals("等腰三角形.png")){
            Shape s = new Shape(x1,y1,x2,y2,color,"直线",stroke);
            v.addElement(s);
            g.drawLine(x1,y1,x2,y2);
            Shape s1 = new Shape(2*x1-x2,y2,x1,y1,color,"直线",stroke);
            v.addElement(s1);
            g.drawLine(2*x1-x2,y2,x1,y1);
            Shape s2 = new Shape(2*x1-x2,y2,x2,y2,color,"直线",stroke);
            v.addElement(s2);
            g.drawLine(2*x1-x2,y2,x2,y2);
        }
        else if(ActionCommand.equals("菱形.png")){
            int x3 = (x1+x2)/2;
            int y3 = (y1+y2)/2;
            Shape s = new Shape(x1,y3,x3,y1,color,"直线",stroke);
            v.addElement(s);
            g.drawLine(x1,y3,x3,y1);
            Shape s1 = new Shape(x1,y3,x3,y2,color,"直线",stroke);
            v.addElement(s1);
            g.drawLine(x1,y3,x3,y2);
            Shape s2 = new Shape(x2,y3,x3,y1,color,"直线",stroke);
            v.addElement(s2);
            g.drawLine(x2,y3,x3,y1);
            Shape s3 = new Shape(x2,y3,x3,y2,color,"直线",stroke);
            v.addElement(s3);
            g.drawLine(x2,y3,x3,y2);
        }
        else if(ActionCommand.equals("空心矩形.png")){
            Shape s=new Shape(x1, y1, x2, y2,color,"空心矩形",stroke);
            v.addElement(s);
            g.drawRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
        }
        else if(ActionCommand.equals("实心矩形.png")){
            Shape s=new Shape(x1, y1, x2, y2,color,"实心矩形",stroke);
            v.addElement(s);
            g.fillRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
        }
        else if(ActionCommand.equals("空心圆.png")){
            int r = (int)Math.sqrt(Math.pow(x1-x2,2)+Math.pow(y1-y2,2));//半径
            Shape s=new Shape(x1, y1, r, r,color,"空心圆",stroke);
            v.addElement(s);
            g.drawOval(x1,y1,r,r);
        }
        else if(ActionCommand.equals("实心圆.png")){
            int r = (int)Math.sqrt(Math.pow(x1-x2,2)+Math.pow(y1-y2,2));//半径
            Shape s=new Shape(x1, y1, r, r,color,"实心圆",stroke);
            v.addElement(s);
            g.fillOval(x1,y1,r,r);
        }
        else if(ActionCommand.equals("空心椭圆.png")){
            Shape s=new Shape(x1, y1, x2, y2,color,"空心椭圆",stroke);
            v.addElement(s);
            g.drawOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
        }
        else if(ActionCommand.equals("实心椭圆.png")){
            Shape s=new Shape(x1, y1, x2, y2,color,"实心椭圆",stroke);
            v.addElement(s);
            g.fillOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
        }
        else if(ActionCommand.equals("直线.png")) {
            Shape s=new Shape(x1, y1, x2, y2,color,"直线",stroke);
            v.addElement(s);
            g.drawLine(x1,y1,x2,y2);
        }
        else if(ActionCommand.equals("多边形.png")&&!first){
            //存起始点
            ployX=x1;
            ployY=y1;
            Shape s=new Shape(x1, y1, x2, y2,color,"直线",stroke);
            v.addElement(s);
            //画多边形的第一条边
            g.drawLine(x1,y1,x2,y2);
            first =true;
        }
        else if(ActionCommand.equals("多边形.png")){
            //双击形成封闭图形
            if(e.getClickCount()==2){
                Shape s=new Shape(x2, y2,ployX,ployY,color,"直线",stroke);
                v.addElement(s);
                //首尾点相连
                g.drawLine(x2, y2,ployX,ployY);
                first =false;
            }//否则继续循序连接
            else {
                Shape s=new Shape(x1, y1, ployX1, ployY1,color,"直线",stroke);
                v.addElement(s);
                g.drawLine(x1, y1, ployX1, ployY1);
            }
        }

        myFrame.setV(v);
    }


    //监视曲线 画布 橡皮擦 喷枪
    //这里都是Point1->Point2 Point2更新 若干个Shape
    @Override
    public void mouseDragged(MouseEvent e) {
        g = (Graphics2D) myFrame.getGraphics();
        g.setColor(color);//画笔颜色
        g.setStroke(stroke);//画笔粗细
        ployX1 = x2;
        ployY1 = y2;
        x2 = e.getX();
        y2 = e.getY();
        if( ActionCommand.equals("曲线.png")||ActionCommand.equals("")){
            //添加若干条直线
            s=new Shape(x1, y1,x2, y2,color,"直线",stroke);
            v.addElement(s);
            g.drawLine(x1,y1,x2,y2);
            x1=x2;
            y1=y2;
        }
        else if( ActionCommand.equals("橡皮擦.png")){
            //区域添加背景色的图形
            s=new Shape(x1, y1,x2, y2,UIManager.getColor("control"),"实心圆",stroke);
            g.setColor(UIManager.getColor("control"));
            v.addElement(s);
            g.drawLine(x1,y1,x2,y2);
            x1=x2;
            y1=y2;
        }
        else if( ActionCommand.equals("喷枪.png")){
            //随机在区域内画线
            Random r=new Random();
            //遍历随机画线
            for(int i=0;i<30;i++){
                int a=r.nextInt(20);
                int b=r.nextInt(20);
                s=new Shape(x1+a, y1+b,x2+a, y2+b,color,"直线",stroke);
                v.addElement(s);
                g.drawLine(x1+a,y1+b,x2+a,y2+b);
                x1=x2;
                y1=y2;
            }
        }
        else if( ActionCommand.equals("毛笔.png")){
            //随机在区域内画线
            Random r=new Random();
            for(int i=0;i<60;i++){
                if(t<=15&&last){
                    int a=r.nextInt(20-t);//ab就当成墨汁了
                    int b=r.nextInt(20-t);
                    s=new Shape(x1+a, y1+b,x2+a, y2+b,color,"直线",stroke);
                    v.addElement(s);
                    g.drawLine(x1+a,y1+b,x2+a,y2+b);
                    x1=x2;
                    y1=y2;
                    time++;//计时器++
                }else; //啥也不画 没墨汁了 再次press重画获得墨汁
            }
            //刷新t 墨汁变少
            t = time/1000;
        }

        myFrame.setV(v);
    }

    public MyFrame getMyFrame() {
        return myFrame;
    }

    public void setMyFrame(MyFrame myFrame) {
        this.myFrame = myFrame;
    }

    public Graphics2D getG() {
        return g;
    }

    public void setG(Graphics2D g) {
        this.g = g;
    }

}

5.MyFrame 类

package com.zzu;

import javax.swing.*;
import java.awt.*;
import java.util.*;

public class MyFrame extends JFrame {
    //所有数据存放至此 进行repaint
    private Vector<Shape> v = new Vector<>(); //设置v和v1的目的 v和v1都有存储图形的功能 用于遍历repaint
    private Vector<Shape> v1 = new Vector<>(); //另外我可以当刷新时 将v的所有数据送给v1 回退时 我可以遍历v1 不然会什么也不画
    private boolean back = false; //是否满足回退且v1不为空的条件
    private Image image;//文件中图片
    private boolean ig;//保证图片也能被删除

    public MyFrame(){
    }

    public MyFrame(String title){
        //窗体名称
        super(title);
        this.getContentPane().setBackground(new Color(204,255,204));
        //TODO 设置窗体图标
        try {
            ImageIcon icon = new ImageIcon("画图.png");
            Image image = icon.getImage();
            this.setIconImage(image);
        } catch (Exception e) {//跳出对话框提示
            JOptionPane.showMessageDialog(null, "图标异常");
        }
    }

    /**
     *
     * @param g the specified Graphics window
     *          重写repaint方法
     */
    @Override
    public void paint(Graphics g) {
        //父类中的方法
        super.paint(g);
        //如果此时v1中有Shape 则说明已经刷新了
        //判断是否回退 回退则将这俩换一下 并且判定是否有图片回退打印
        if(v1.size()>0) {
            ig = false;
            if(back){
                v.addAll(v1);
                v1.removeAllElements();
                //ig = true;
            }
        }
        Graphics2D g1 = (Graphics2D) g;
        //遍历画出内容
        for (Shape type: v) {
            g1.setStroke(type.getStroke());//当前图形粗细
            g1.setColor(type.getColor());//当前颜色
            int x1 = type.getX1();
            int y1 = type.getY1();
            int x2 = type.getX2();
            int y2 = type.getY2();
            //根据image选择draw
            if(type.getImage().equals("空心圆")){//圆
                g1.drawOval(x1,y1,x2,y2);//这里x2=y2=r
            }
            else if(type.getImage().equals("实心圆")){//实心圆
                g1.fillOval(x1,y1,x2,y2);//这里x2=y2=r
            }
            else if(type.getImage().equals("空心椭圆")){//椭圆
                g1.drawOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
            }
            else if(type.getImage().equals("实心椭圆")){//实心椭圆
                g1.fillOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
            }
            else if (type.getImage().equals("空心矩形")) {//矩形
                //左上角坐标 长宽
                g1.drawRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
            }
            else if (type.getImage().equals("实心矩形")){//实心矩形
                //左上角坐标 长宽
                g1.fillRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x1-x2),Math.abs(y1-y2));
            }
            else{//线(其他多边形 曲线都是由若干条线组成的)
                g1.drawLine(x1,y1,x2,y2); //连接两端点
            }
        }
        if(ig) g1.drawImage(image,this.getX()+200,this.getY()+200,this);
    }

    public Vector<Shape> getV() {
        return v;
    }

    public void setV(Vector<Shape> v) {
        this.v = v;
    }

    public Vector<Shape> getV1() {
        return v1;
    }
    public void setV1(Vector<Shape> v1) {
        this.v1 = v1;
    }

    public boolean isBack() {
        return back;
    }

    public void setBack(boolean back) {
        this.back = back;
    }

    public Image getImage() {
        return image;
    }

    public void setImage(Image image) {
        this.image = image;
    }

    public boolean isIg() {
        return ig;
    }

    public void setIg(boolean ig) {
        this.ig = ig;
    }
}

最终效果

程序所需图片如下:

 这里要实现拉伸功能,大家可以去学一下GUI中的异或操作。

  • 面向对象 

设计

  1. 在问题领域中根据需求识别对象
  2. 将识别出的对象抽象为类
  3. 找出对象与对象之间的联系
  4. 进行类的设计,属性、方法、继承等,建立类层次结构
  5. 创建各类对象,传递参数,调用类中方法,根据对象间的联系,在主函数中展示组装。

优秀的面向对象编程,合理的进行类的设计,各种抽象的类、接口,那是必不可少的。

优势

在此次程序,通过面向对象编程,对于窗口的处理,我可以抽象出类来解决。我可以独立的分成几个类,分解若干个对象,描述事物在整个解决问题的步骤中的行为。我根据程序的目的,抽象出了菜单、工具栏、监听器、图形、顶级容器类,通过创建他们的对象,关联组装,就可以完成这个程序设计,并且层次较为清晰。

我目前没有用面向过程的编程进行过此类图形化界面程序的实现,但是也有写过一次千行的大作业,其中全是逻辑算法问题,单纯各种函数的书写,解决某一步的问题,与此程序书写风格大相径庭。

因此通过此程序,也可以感受到面向对象的一些优势。也多少能体会到两种编程的一些差别。用面向过程的语言书写代码,更偏向于顺序完成,调用函数。面向对象就与此有大的差别,思路不同、解决方法的实现可能现在对我而言是差不多,但是解决方法的路径与过程差距很大。

面向过程中的解决问题的方法主要是函数,而面向对象主要是类(这是面向过程编程所不具备的),抽象成类,构造类属性、方法,创建对象,解决问题,且带有针对性。

另外面向过程是一种以过程为中心的编程思想,缺点就是重用率低,扩展能力差。而面向对象编程则更容易设计出清晰、耦合度低的系统,并且便于程序修改更新。对我这个程序而言最关键的是易复用,可继承,能更好的联系,这同时也是面向对象编程的一些优势。

;