我第一次的画图板

                             总结
                                     ----------关于画图板
花了将近一个学期的时间,我的画图板也终于是在小部分上已经完工了,至少,他看上去已经是一个画图板了,虽然还有很多功能需要我去完善。
在制作这个画图板的时候,我当然是毫无疑问的遇到了许许多多的问题,而这个总结也是从另一方面对这些问题进行一些阐述吧!也可以算是一个大纲吧!!
首先,想要制作一个画图板,最基本的窗体是少不了的了,这个就是javax.swing.JFrame的顶级容器。
代码如下:
javax.swing.JFrame    jf =  new  javax.swing.JFrame;
然后就是一系列对窗体功能的添加和设定:
他的标题
this.setTitle("画图板");
他的大小
this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
    关闭时执行的操作
this.setDefaultCloseOperation(3);
    位置
this.setLocationRelativeTo(null);
等等这些能够添加的很多,但是现在只用这些就能让他形成最初的界面,之后的美化可以再继续的进行,甚至可以为他的边框进行自己的设置等等。
当然,在这之中,曾经最让我头疼的就是他的布局了。
但是最重要的是必须要将这个窗口设置为可见,什么都能少就是这个不能少。
代码如下:
This.setVisible(true);
然后,我觉得排在之后的就要是给他加上一个菜单栏了,而实现菜单栏的话,就需要用到一个数组。
在对数组的理解中,数组可以分为多维数组,这里可以是一维的,代码如下;
Object [][]Array=new Object[i][j];
这个定义方法之中需要给数组添加上一定的大小,而i,j就是他们的长度和宽度。
当然还有别的方法,而像我们在菜单栏中所需要用到的数组就是用另外一个方法来获得的,
代码如下:
String [][]Array1={“”};在这里,我们可以添加任意数量的字符串,有多少就加多少,但是一定要用双引号将他们括起来;
String [][]Array2={{“”}{“”}};在这里,很容易发现我使用了三个大括号,没错,这就代表的是这个数组是一个二维数组的定义,当然,大括号的数量并不是说是局限于三个,只是内部的大括号代表多维数组内部的字符串。
在这里我可以用表格来说明一下二维数组的图形来想象一下多维数组的概念。在二维数组中,或者是在数组中,有下标这样一个概念,顾名思义,下标的意思就是标记每一个数组每一个数字,或者字符串,又或者是其他的位置。但是,需要的注意的是,下标是从0开始的。下面我将用i和j来说明一下这个下标。



0 1 2 3 j
1
2
3
4
5
i
如表格中所见,一个数组下标的表示方法Array[i][j];
这里是使用数组的方法。
这样,使用一个数组就可以很轻松的将所需要的命令加入到菜单栏中去了。
在我们使用菜单栏的时候,我们需要用到一下三个类:
1.javax.swing.JMenuBar;
2.javax.swing.JMenuItem;
3.javax.swing.JMenu;
其中,第一个类是创建一个菜单栏,也就是载体;第二个类是给他们加入命令,第三个类是加入载体中的类似于容器的东西。


一个画图板之所以要叫做一个画图板就是因为他能够画图,这也就是下面的代码的核心了。
首先,为了能够好好的画图,我们需要的是一个类似于我们电脑上画图板左边那一栏的图标还有下面那一栏的颜色选项,这两者都是必不可少的。而这两者就都在一个叫做面板的东西上了。在JAVA中,面板需要使用到的类就是java.swing.JPanel。这是一个作为容器的东西,也就是我们放那些菜单栏的选项的地方,这个类所形成的面板也是放在窗体上的,但是他更利于我们对窗体上的分类摆放。
代码如下:
java.swing.JPanel jp=new java.swing.JPanel();
这样,我们就随时能够在jp的对象,也就是面板上添加我们所需要的东西了。





现在,我们拥有了我们放东西的地点,接下来就是放东西了。
首先,是我们左边的选择画什么图形的工具栏。
而为了方便我们的区分和选择,我们将他重新以一个类的形式进行书写,也就是ToolPanel的一个类(这个类是我们自己定义的,和JAVA程序自带的类没有任何的关系)。
在工具栏中,我们会发现有一个一个小的按钮,这些虽然都是按钮,但是和菜单栏的按钮却是不一样的,他们是JAVA中java.swing.JRadioButton中的,也就是按钮类。我们使用这个类可以给面板(JPanel)上添加上一个一个的按钮,而且可以对他们的大小和位置进行设定,这个不进行详细说明,我们需要的说明的是我们在对这些按钮添加图片的方法。因为,我们刚开始创建一个按钮对象之后,这是一个空的,也就是什么都没有在上面的,我们不知道他表示的是什么意思,事实上他们也没有任何的意义,他们只是一个按钮,而他们的意义需要我们给他们添加上,可以是文字也可以使一个图片。
代码如下:
JRadioButton jb = new JRadioButton();
this.add(jb);
但是,我们很容易发现,如果只是一个一个的按钮的话,我们很难去管理,但是恰恰一个画图板的按钮还并不少,所以,这个时候我们就需要一个java.swing.ButtonGroup的类来替我们对这些按钮进行管理。
代码如下:
ButtonGroup group=new ButtonGroup();
这样的话,我们就需要先把所有的按钮放到一起管理再进行添加了
代码如下:
Group.add(jb)

但是,就算我们这样做了, 我们会发现,我们仍然是很难快速的给我们按钮添加我们想要添加的图片或者是文字,因为一个一个的添加值是一件很麻烦的过程,这样就引入了我们需要进行循环的思想。运用一个循环,让计算机去一直按照这个循环去给按钮赋值,这样就大大缩短了我们写代码的时间,也提高的机器的效率。
代码如下:
for (int i = 0; i < 16; i++) {
// 创建按钮
JRadioButton jb = new JRadioButton();
// 设置按钮的大小
jb.setPreferredSize(new Dimension(30, 30));
// 设置图像的地址
jb.setIcon(this.change("picture/draw" + i + ".jpg"));
// 分别设置鼠标在图像上,按下时,松开时的图像
jb.setRolloverIcon(this.change("picture/draw" + i + "-1.jpg"));
jb.setPressedIcon(this.change("picture/draw" + i + "-2.jpg"));
jb.setSelectedIcon(this.change("picture/draw" + i + "-3.jpg"));
// 设按钮的动作命令
jb.setActionCommand("draw" + i);
其中,jb后中均为JButton中本身写有的方法,三种方法依次为设置鼠标在图像上,按下停留,松开时,我们想要给他加上的图像。
小技巧:为了让我们的for循环结构能够成功的运行,我们需要做的就是对我们的图片进行一些有规律性的命名,这样往往能够大大的节省我们的时间,减少我们的工作量。
在这之中,我们有点需要注意的是,在我们的电脑上,我们的对图片的命名是一样的,所以我们的电脑上存在很多相同的名称,但是由于互联网的存在,我们在登录互联网交换信息的时候,不得不想办法去避免那些相同名字的概念,所以,我们的文件的名字就被重新在互联网上进行了定义,也是URL,文件在互联网上唯一的名称。
代码如下:
public ImageIcon change(String path) {
// 将本地路径转化为网络路径
URL u = Toolpanel.class.getResource(path);
// 实例化一个ImageIcon对象,并创建一个图标对象
ImageIcon im = new ImageIcon(u);
// 返回im的值
return im;

}




完成了ToolPanel之后,就是我们的颜色那一栏了,为了方便,我们也将他写成一个单独的类,也就是ColorPanel这个类,这个类也是我们自己定义的一个类,和JAVA本身所带有的类没有任何的关系。

由于颜色按钮本身和文字和图片名有着一定的差别,虽然我们使用的都是按钮,但是,我们在对按钮添加我们想要赋予的意义的时候,就只是对他们的背景色进行的改变了,而我们想要获得的命令也就是他们的背景色。
本处代码与上面的类似,所以略过。




在我们成功的添加了工具栏和颜色栏之后,我们会发现,不管我们多么努力的去点击那些图案,我们也无法像真正的画图板一样去画一些东西。这就是我们并没有对鼠标的动作进行定义的结果。
所以,我们要做的是对鼠标的每一次动作和得到的命令进行定义。我们称之为监听器。
在画图中,我们需要用到的则是:
java.awt.event.ActionListener;
java.awt.event.MouseListener;
java.awt.event.MouseMotionListener;
这三个接口都是我们所需要用到的。
值得一提的是他们是接口,而并不是类,他们最本质的区别是接口的方法必须被全部的实现,而类中的方法,我们是可以有选择性的去调用的。

首先是:
java.awt.event.ActionListener;
我们使用他来监听我们鼠标动作得到的命令。
代码如下:
public void actionPerformed(ActionEvent e) {
// 得到事件源对象
Object ob = e.getSource();
if (ob instanceof JButton) {

JButton but = (JButton) ob;
// 得到被选中按钮的背景颜色,作为要绘制的颜色
color = but.getBackground();
}
}

然后是:
java.awt.event.MouseListener;
我们在这里所需要用到的主要是我们对鼠标的点击和释放的监听。
代码如下:
public void mousePressed(MouseEvent e) {
// 获取传过来的动作命令
type = group.getSelection().getActionCommand();
// 得到第一次按下时的坐标
x1 = e.getX();
y1 = e.getY();
}
public void mouseReleased(MouseEvent e) {
// 取得鼠标松开时的坐标值
x2 = e.getX();
y2 = e.getY();
g.setColor(color);
// 判断选择的图形
if (type.equals("draw10")) {
// 画直线
g.drawLine(x1, y1, x2, y2);
} else if (type.equals("draw12")) {
// 画矩形
g.drawRect(Math.min(x1, x2), Math.min(y1,y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
} else if (type.equals("draw14")) {
// 画圆
g.drawOval(Math.min(x1,x2),Math.min(y1,y2) , Math.abs(x1 - x2), Math.abs(y1 - y2));
}
}
可以看到的是,在这之中,出现了类似于g.drawRect()的字样。
这些都是我们画图所需要的类java.awt.Graphics之中的。
最后是
java.awt.event.MouseMotionListener;
对他,我们主要用到的是对我们鼠标拖动时的监听。
代码如下:
public void mouseDragged(MouseEvent e) {
// 获取点的坐标值
x2 = e.getX();
y2 = e.getY();
g.setColor(color);
if (type.equals("draw6")) {
// 画曲线
g.drawLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;

} else if (type.equals("draw2")) {// 判断是否是橡皮
g.setColor(Color.WHITE);// 设置颜色为白色
((Graphics2D) g).setStroke(new BasicStroke(10));// 设置线条的大小
g.drawLine(x1, y1, x2, y2);
((Graphics2D) g).setStroke(new BasicStroke(1));// 重新设置线条的大小(还原为1)
x1 = x2;
y1 = y2;
} else if (type.equals("draw7")) {// 判断是否是刷子
((Graphics2D) g).setStroke(new BasicStroke(10));// 设置线条的大小
g.drawLine(x1, y1, x2, y2);
((Graphics2D) g).setStroke(new BasicStroke(1));// 重新设置线条的大小(还原为1)
x1 = x2;
y1 = y2;
} else if (type.equals("draw8")) {// 判断是否是喷枪
// 创建一个随机数对象
Random rand = new Random();
// 循环画10个点
for (int i = 0; i < 10; i++) {
// 随机X
int xValue = rand.nextInt(8);
// 随机Y
int yValue = rand.nextInt(8);
g.drawLine(x2 + xValue, y2 + yValue, x2 + xValue, y2 + yValue);
}
}

}



然后,在我们完成了这些监听器之后,我们将这些监听器加在他们需要放的位置上之后,我们的画图板就可以正常的画画了。
代码如下:
Toolpanel tp = new Toolpanel();
this.add(tp, BorderLayout.WEST);
ColorTool cp = new ColorTool();
this.add(cp, BorderLayout.SOUTH);

// 给画图板添加另一个底色
JPanel center = new JPanel();
center.setLayout(new FlowLayout(0));
center.setBackground(Color.GRAY);
this.add(center, BorderLayout.CENTER);
// 用来绘图的画板
panel = new MyPanel();
panel.setBackground(Color.WHITE);
panel.setPreferredSize(new Dimension(DRAW_WIDTH, DRAW_HEIGHT));
center.add(panel);
this.setVisible(true);

Listener l = new Listener(g, group);
this.addMouseListener(l);
// 在tp中添加鼠标监听器
panel.addMouseListener(l);
panel.addMouseMotionListener(l);
// 给所有颜色按钮添加监听器
cp.addListener(l);



但是,当我们很开心的完成了这些之后,我们会突然的发现,当我们最小化,甚至只是拖动窗体之后,窗体上的画的图形就没有了。很简单,这是因为我们画的东西并没有保存到内存中去,所以,我们还需要对我们的画图板进行进一步的完善,也就是对它们进行重绘。在重绘中,我们需要用到的就是JAVA中的paint()方法。
代码如下:
Public void paint(Graphics g){
super.paint(g);
}
这是最基本的重绘的方法。
但是,对于我们的画图板,我们就有两种重绘的方法。
我进行简单的说明。
第一种:
我们可以获得我们画的每一个线的命令和坐标来进行重绘,这时候,我们需要用到的是一个保存了命令和坐标的数组,简单的代码如下(没有保存的命令,而只是画直线的数组)。
代码如下:
public void paint(Graphics g){
       super.paint(g);
       int []array=st.getArray();//获取Array中的值
       //重新画矩形
       for(int i=0;4*i<array.length;i++){
       g.drawRect(array[4*i],array[4*i+1],array[4*i+2],array[4*i+3]);     
       }
}
其中,array数组中保存的就是我们之前存储进去的画直线所需要的坐标。



第二种:
这种方法相对来第一种要显得简单很多,也更加的巧妙。
他是只将一个面板的所有图像记录下来,再进行重绘。
代码如下:
public void drawShape(Graphics g) {
// 得到Panel的背景色
Color backcolor = panel.getBackground();
int back = backcolor.getRGB();

// 遍历二位数组,取出每个像素点的颜色
for (int i = 0; i < Listener.data.length; i++) {
for (int j = 0; j < Listener.data[i].length; j++) {
int num = Listener.data[i][j];
if (back != num) {
// 创建颜色对象
Color color = new Color(num);
g.setColor(color);
g.drawLine(j, i, j, i);
}
}
}

}

class MyPanel extends JPanel {
public MyPanel(){
this.setBackground(Color.white);
}

/**
* 重写父类绘制窗体的方法
*/
public void paint(Graphics g) {
// 调用父类的方法来正确的绘制窗体
super.paint(g);

if (Listener.isPaint) {
// 调用绘制形状的方法
drawShape(g);
}
}

}



最后,在我们能够重绘之后,我们更需要做的事情就是将文件保存在硬盘或者是从硬盘上读出了。这样我们就需要用到的是JAVA中
java.io.DataInputStream;
java.io.DataOutputStream;
java.io.FileInputStream;
java.io.FileOutputStream;
这四个类,其中,这几个类分别是用来从内存中输入输出流,还有将他们打包成能够读写不同类型数据的流。

值得一提的是,在计算机中,我们会定义很多中不同的类型,像int整型,String字符型等等之类的,而普通的流就只能够读写byte字节型,而且最终存储的也一定是byte字节型,但是我们在读取和存入的时候却又不得不考虑我们的所画东西并不是一个Byte字节型,这样,就需要了以Data为开头的对普通流进行打包,使他们能够读取其他类型的类了。


具体代码如下:
public static void saveFile(String path) {
try {
// 创建文件输出流对象
FileOutputStream fos = new FileOutputStream(path);
// 包装成可写基本类型的流
DataOutputStream dos = new DataOutputStream(fos);
// 写图片的高度和宽度
dos.writeInt(Listener.data.length);
dos.writeInt(Listener.data[0].length);

// 遍历二维数组,写数据
for (int i = 0; i < Listener.data.length; i++) {
for (int j = 0; j < Listener.data[i].length; j++) {
int num = Listener.data[i][j];
// 写数据
dos.writeInt(num);
}
}

dos.flush();
fos.close();

} catch (Exception ef) {
ef.printStackTrace();
}

}

/**
* 读取文件中的数据
*
* @param path
*            要读取的文件
* @return 将读取到的数据作为二位数组返回
*/
public static int[][] readFile(String path) {

// 创建文件输入流
try {
FileInputStream fis = new FileInputStream(path);
DataInputStream dis = new DataInputStream(fis);
// 读取高度和宽度
int height = dis.readInt();
int width = dis.readInt();

// 定义二位数组
int[][] readData = new int[height][width];
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
// 将数据读入数组
readData[i][j] = dis.readInt();
}
}
fis.close();
return readData;
} catch (Exception ef) {
ef.printStackTrace();
}

return null;
}









但是,在最后我需要强调的一点就是,虽然我们能够将这些内容保存在硬盘之中,但是不同的文件都有不同的解码器,换句话说,就是如果我们用别的解码器去打开他,这是不能被读取的,产生的就是一堆令人头疼的乱码了,而当我们再用我们的画图板去读取的时候,他却能够正常的读取了。





以上,就是我此次对画图板制作的一些总结。



可能有很多的地方都很不到位,而且有很多的功能都没有得到完善,所以希望大家多多留下自己的指教,轻喷,因为我是一个初学者,有意见我一定是会去改正的。

猜你喜欢

转载自specialzheng-163-com.iteye.com/blog/1431014