简易画板

  • UI界面设置
  • 监听器类
  • 图形保存

github源代码下载: 画图工具源代码.

UI界面设置

根据我们想实现的画板要求,可以分步骤进行解决。在创建画板之前,首先创建窗体框架,利用java提供的工具包中的JFrame窗口类就可以创建一个基本的框架。这个框架是之后添加画板和画笔的最顶层的容器。就好比吃饭的桌子,没有这个桌子,盘子和碗筷都无法放置。当我们把这个顶层容器设置好后,我们就可以进一步添加我们的相应功能组件了,也就是在这个桌子上放东西。创建窗体代码如下:

JFrame jf = new JFrame();
jf.setTitle("画图板");
jf.setLocationRelativeTo(null);//设置窗体为正中央
jf.setSize(700, 600);//设置窗体大小
jf.setLayout(new FlowLayout());	设置布局为流式布局
jf.setVisible(true);	//设置窗体可见

当我窗体创建好后,接下来就要添加东西了,首先就是画板。在此之前我想先简要介绍一下JPanel类,JPanel类也是java提供的一个工具类,根据它我们可以创建一个面板容器添加到我们的窗体当中,来帮助我们更好的组织我们接下来要放置的元素组件。可以简单理解为用一个个餐垫将桌子的放置区域划分的更细更紧凑。

关于组件的添加位置,还要先了解一下Jframe的层次结构,当我们向窗体添加组件时是添加到 containPane内容面板。直接添加到窗体中实质上就是添加到containPane上。

JFrame jf = new JFrame();
jf.add(component);//等价
jf.getContentPane().add(component);

链接: jframe层次结构.

同样的方法,可以利用工具包中的JButton创建功能按钮,并且添加到窗体中,创建方法同上,这里就不赘述了。

至此已经实现了画板,然后就可以创建画笔了,JPanel可以调用父类中getGraphics方法可以获得画笔,类型为Graphics。画笔的操作对象是图形坐标系的像素点。

(说明一点:所有组件的添加要放在jf.setVisible(true)之前,而getGraphics()一定要在jf.setVisible(true)之后再调用,否则编译时不会报错,但在运行时画笔会报空指针异常)

在图形坐标系中,其原点位于构件的左上角,坐标轴沿向下和右的方向增长,小圈代表坐标,正方形代表像素,坐标位于像素之间:

Java 图形化实现连线 java做图形化界面_sed


获取画笔方法如下:

//jf.setVisible(true)
JPanel jp=new JPanel(); 
Graphics gf = jp.getGraphics();

至此我们的基本画板界面就可以创建好了:

public class Paint extends JPanel{
	public static void main(String[] args) {
		Paint p = new Paint();
	}
	  JFrame jf = new JFrame();
	public Paint() {
		  //实现一个窗体
		  jf.setTitle("画图板");
		  jf.setSize(700, 600);
		  jf.setLocation(600,250);//窗口左上角起点
		  jf.setLayout(new FlowLayout());	//设置布局为流式布局
 
          //声明两个数组,存储按钮指示
      	String[] command = { "开始", "清除", "直线", "曲线", "多边形", "矩形","圆形" };
      	Color[] color = { Color.BLACK,Color.BLUE,Color.YELLOW, Color.RED, Color.GREEN };
          //将按钮添加到窗体中
   		for (int i = 0; i < command.length; i++) {
   			JButton jb = new JButton(command[i]);
   			jf.add(jb);
   		}
   		for (int i = color.length - 1; i >= 0; i--) {
   			JButton jb = new JButton();
   			jb.setBackground(color[i]);//设置背景颜色
   			Dimension dm = new Dimension(20, 20);//设置大小
   			jb.setPreferredSize(dm);
   			jf.add(jb);
   		}
          this.setPreferredSize(new Dimension(1600, 1400));
          this.setBackground(Color.LIGHT_GRAY);//画板背景颜色
           //将JPanel对象添加进入jf窗体对象中,让他生效
   		  jf.add(this);
   		  jf.setDefaultCloseOperation(3);
          jf.setVisible(true);	//设置窗体可见
          
   			Graphics gf = this.getGraphics();	//获取画笔,我们的this代表当前类的对象,正好是一个JPanel的对象
	}
	}

Java 图形化实现连线 java做图形化界面_swing_02

监听器类

接下来就可以开始实现画图功能了:
我们都用过windows自带的画图工具,假如要画一条直线,鼠标点击左键不松开,拖动一段距离,松开左键,这时一条直线就画好了。这里先引入一个概念:鼠标监听。
在JPanel中可以调用父类中的一个方法addMouseListener(lis),顾名思义,添加鼠标监听器,监听对象就是JPanel的实例对象(这里指的是画板),并且鼠标会根据在监听对象上的做的举动做出响应事件(例如:在画板上拖动鼠标则画出线条)。

说明一下重写方法public void mouseClicked(MouseEvent e) 调用的时间:鼠标按下并松开后(pressed和realeased方法均已发生),如果鼠标的始末的坐标不变,则clicked被调用,否则不被调用。这是容易搞错的地方。

除此之外,还有其他监听方法addMouseMotionListener(lis),addActionListener(lis)读者可自行了解。这里再说明一下,调用这些方法的时候,发现括号中形参列表需要传入的参数是接口类型,然而接口是无法直接创建对象的,所以要创建相应的接口子类对象lis来实现。之后在以上代码相应地方为监听对象添加这些方法就行了。

现在就根据不同的监听方法来设置我们的响应事件。
创建一个Listener接口子类来实现响应事件:

public class Listener implements ActionListener,MouseListener,MouseMotionListener {
	//一定要将接口的所有方法重写
	private Graphics gr;
	private String command="";
	private Paint p;
	int x0=0,y0=0,//曲线的起始坐标
	    x1=0,y1=0,x2=0,y2=0,//其他图形
	    x3=0,x4=0,y3=0,y4=0,
	    start_x=0,start_y=0;//多边形起点

	//初始化数据
	//基本数据类型向形参传递的是副本,引用数据类型(类,接口,数组)向形参传递的地址,如下传递jp
	
	public void set_gr(Graphics G) {
		this.gr = G;
	}

	public void set_p(Paint p)
	{
		this.p=p;
	}
	/*
	 * 这里必须先点击被命名的按钮(即要画什么图形),将"command"初始化,
	 * 否则后面的监听回应方法都不能执行,如果不点击颜色按钮则不会为画笔setcolor默认黑色
	 */
	public void actionPerformed(ActionEvent e) 
	{
		// TODO Auto-generated method stub
		//只不过每次使用之前把画笔的颜色修改
		x1 = 200; y1 = 200;
		if (e.getActionCommand()=="") {
			  //j暂存颜色按钮  
			  //getSource得到的组件的名称,而getActionCommand得到的是标签。
			JButton j = (JButton)e.getSource();
			//将画笔设置为按钮的背景颜色
			gr.setColor(j.getBackground());
		}
		else {
			command = e.getActionCommand();
			if("清除".equals(command))
			{
				//JFrame和JPanel的repaint方法是同一个,都是继承自    JComponent的,对该方法的调用执行的都是同样的代码的。
				p.repaint();
			}
		}
	}
	
	//鼠标按键在组件上按下(长按)并拖动时(在拖动的过程中)调用。                                (处理鼠标拖动事件)
			public void mouseDragged(MouseEvent e) {
				if("曲线".equals(command)) {
					x1 = x0; y1 = y0; //鼠标按下的时候就获得了起始坐标,调用了mousePressed
					x0 = e.getX(); y0 = e.getY();
					gr.drawLine(x1,y1,x0,y0);
					}
			}
	        
			// Override鼠标监听//点击鼠标点击时间,用来绘画多边形
			public void mouseClicked(MouseEvent e) //是点击和松开两步完成后再调用 在此之前,pressed 和released都已经被调用
			{
				// TODO Auto-generated method stub
				if("多边形".equals(command)) 
				{
					
					if(x4==0&&y4==0)
					    {
						
						x4 = e.getX(); y4 = e.getY();
						start_x = x4; start_y = y4;
						//记录多边形的起点位置
					      }
					else {
						x3 = x4; y3 = y4;//这里的x3,y3为上一条边的终点
						x4 = e.getX(); y4 = e.getY();
						gr.drawLine(x3, y3, x4, y4);//将这点与上一条边的终点连起来
					     }
					if(e.getClickCount()==2)
					{
					x4 =0; y4=0;//这里设置为零是为了能都再次执行if语句 
					gr.drawLine(start_x, start_y, e.getX(), e.getY());//将双击的终点位置和开始记录的起点位置相连
					}	
				}
			}
				
				//当鼠标按下的时候,获取起点的坐标
				 public void mousePressed(MouseEvent e)
				{
				//	System.out.println("按下");
					
					if("曲线".equals(command)){
						x0 = e.getX();
						y0 = e.getY();
					}
					// TODO Auto-generated method stub
					x1 = e.getX();
					y1 = e.getY();
					
				}
				
				//当鼠标松开的时候,获取终点的坐标
				public void mouseReleased(MouseEvent e)
				{
					// TODO Auto-generated method stub
					x2 = e.getX();
					y2 = e.getY();
				//	System.out.println("松开");
					if("直线".equals(command)){
						gr.drawLine(x1, y1, x2, y2);
					}
					else if("矩形".equals(command)) {
						gr.drawRect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1-x2), Math.abs(y1-y2));
					}
					else if("圆形".equals(command)) {
						//Math.min(x1, x2), Math.min(y1, y2),确定画图的方向起点
						//drawoval 和drawrect 都是以某个点 向着坐标点增大的方向画图
						//Math.abs(x1-x2)求绝对值
						gr.drawOval(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1-x2), Math.abs(y1-y2));
											}
				}
				//鼠标进入窗口
				public void mouseEntered(MouseEvent e)
				{
				}
                 //鼠标退出窗口
			    public void mouseExited(MouseEvent e)
			    {
			    }
				//鼠标在移动的过程中调用(没有点击)
			    public void mouseMoved(MouseEvent e)
		         {
		         }
			}

现在我们的画板基本算是完成了

Java 图形化实现连线 java做图形化界面_sed_03

图形保存

但是会发现,在隐藏窗口或者拉动窗口的时候图案又消失了,这是因为窗体会默认自动调用一个父类的paint()方法,他会重新绘制组件,但是不会重新绘制图形,所以我们就要想到重写父类的paint()方法,在重绘组件的基础上添加重绘图形方法。重绘图形就要用到之前绘制的图案的基本信息,因此我们还需要创建一个类用来存储之前画的图案的信息(图案名称,坐标点,颜色等)
如下:

public class Shape{
private int x1,y1,x2,y2;
private String name;
private Graphics g;
private Color color;

public Shape(int x1,int y1,int x2,int y2,String name,Color color)
{
	this.x1=x1;
	this.x2=x2;
	this.y1=y1;
	this.y2=y2;
	this.name=name;
	this.color=color;
}

public Color getcolor()
{
	return color;
}

public void Repaint(Graphics g)
{
	switch(name)
	{
	case "直线":
		g.drawLine(x1, y1, x2, y2);
		break;
	case "矩形":
		g.drawRect(Math.min(x1, x2), Math.min(y1 ,y2), Math.abs(x1-x2), Math.abs(y1-y2));
		break;
	case "圆形":
		g.drawOval(Math.min(x1, x2), Math.min(y1 ,y2), Math.abs(x1-x2), Math.abs(y1-y2));
		break;
	case "曲线":
		g.drawLine(x1, y1, x2, y2);
		break;
	case "多边形":
		g.drawLine(x1, y1, x2, y2);
		break;
	}
}
}

这样就可以问题就解决了,再补充一个要点,在重写了父类paint()方法后,响应类清除图案这里就不能直接调用repaint()方法了,因为repaint()方法调用的就是之前重写过的paint()方法。所以需要重写创建一个paint1()方法,这个方法里只有父类重绘组件的功能,然后在响应类里面调用。

下面附上源代码:
Paint类:

public class Paint extends JPanel{

	//建立一个很大的数组,用来存放图形元素,
	private Shape[] shape=new Shape[10000];
	public static void main(String[] args) {
		Paint p = new Paint();
	}
	  JFrame jf = new JFrame();
	//实例化一个ButtonListener对象,实现了多种接口
	Listener lis= new Listener();
	
	public Paint() {
		  //实现一个窗体
		  jf.setTitle("画图板");
		  jf.setLocation(600,250);//窗口左上角起点
		  jf.setSize(700, 600);
		  jf.setLayout(new FlowLayout());	
		  
           //声明两个数组,包含各种指令
      		String[] command = { "开始", "清除", "直线", "曲线", "多边形", "矩形","圆形" };
      		Color[] color = { Color.BLACK, Color.BLUE, Color.YELLOW, Color.RED, Color.GREEN };
 
    		/*在下面的两个循环中将各种按钮添加进入动作监听器中,其中addActionListener参数为lis,lis是一个Buttonlistener的对象*/
   		   for (int i = 0; i < command.length; i++) {
   			JButton jb = new JButton(command[i]);
   			jb.addActionListener(lis);
   			jf.add(jb);
   		   }
   		   for (int i = color.length - 1; i >= 0; i--) {
   			JButton jb = new JButton();
   			jb.setBackground(color[i]);//设置背景颜色
   			Dimension dm = new Dimension(20, 20);//设置大小
   			jb.setPreferredSize(dm);
   			jb.addActionListener(lis);
   			jf.add(jb);
   		}
   		
   	     //将JPanel对象添加进入jf窗体对象中,让他生效
   	    //jpanel画板的大小和颜色
		  this.setPreferredSize(new Dimension(1600, 1400));
          this.setBackground(Color.LIGHT_GRAY);
			jf.add(this);
			jf.setDefaultCloseOperation(3);
			jf.setVisible(true);	//设置可见
   			Graphics gf = this.getGraphics();	//获取画笔,我们的this代表当前类的对象,正好是一个JPanel的对象
   			this.addMouseListener(lis);		
   			this.addMouseMotionListener(lis);	
   			lis.set_gr(gf);		
   			lis.set_shape(shape);
   			lis.set_p(this);
	}
	
	//这里是重写jpanel中的paint   
	public void paint(Graphics g)
	{
		super.paint(g);//这是原先的父类方法 本来只要绘制组件的功能
                       //所以现在我们要加一个绘制图形的功能如下 
		for(int i=0;i<=shape.length;i++)
		{
			if(shape[i]!=null)
			{  
			     g.setColor(shape[i].getcolor());
				shape[i].Repaint(g);
			}
			else
				break;
		}
	}
	
	public void paint1(Graphics g)
	{
		super.paint(g);
	}
	
}

Listener类:

public class Listener implements ActionListener,MouseListener,MouseMotionListener {
	//一定要将接口的所有方法重写
	private int index=0;
	private Graphics gr;
	private String command="";
	private Shape[] shape=new Shape[10000];
	private Paint p;
	int x0=0,y0=0,//曲线的起始坐标
	    x1=0,y1=0,x2=0,y2=0,//其他图形
	    x3=0,x4=0,y3=0,y4=0,
	    start_x=0,start_y=0;//多边形起点
	    
	public void set_gr(Graphics G) {
		this.gr = G;
	}
	public void set_shape(Shape[] shape)
	{
		this.shape=shape;
	}
	public void set_p(Paint p)
	{
		this.p=p;
	}
			
	/*
	 * 这里必须先点击被命名的按钮(即要画什么图形),将"zhiling"初始化,  否则后面的监听回应方法都不能执行,如果不点击颜色按钮
	 * 则不会为画笔setcolor 默认黑色
	 */
	public void actionPerformed(ActionEvent e) 
	{
		// TODO Auto-generated method stub
		//每次使用之前把画笔的颜色修改
		x1 = 200; y1 = 200;
		if (e.getActionCommand()=="") {
			  //j暂存颜色按钮  
			  //getSource得到的组件的名称,而getActionCommand得到的是标签
			JButton j = (JButton)e.getSource();
			//将画笔设置为按钮的背景颜色
			gr.setColor(j.getBackground());
		}
		else {
			command = e.getActionCommand();
			if("清除".equals(command))
			{
				p.paint1(gr);
			}
		}
	}

	//鼠标按键在组件上按下(长按)并拖动时(在拖动的过程中)调用。                                (处理鼠标拖动事件)
			public void mouseDragged(MouseEvent e) {
				if("曲线".equals(command)) {
					x1 = x0; y1 = y0; //鼠标按下的时候就获得了起始坐标
					x0 = e.getX(); y0 = e.getY();
					gr.drawLine(x1,y1,x0,y0);
					shape[index++]= new Shape(x1,y1,x0,y0,command,gr.getColor());
				}
			}
	        
			// Override鼠标监听//点击鼠标点击时间,用来绘画多边形
			public void mouseClicked(MouseEvent e) //是点击和松开两步完成后再调用 在此之前,pressed 和released都已经被调用
			{
				// TODO Auto-generated method stub
				if("多边形".equals(command)) 
				{
					if(x4==0&&y4==0)
					    {
						x4 = e.getX(); y4 = e.getY();
						start_x = x4; start_y = y4;
						//记录多边形的起点位置
					      }
					
					else {
						
						x3 = x4; y3 = y4;//这里的x3,y3为上一条边的终点
						x4 = e.getX(); y4 = e.getY();
						gr.drawLine(x3, y3, x4, y4);//将这点与上一条边的终点连起来
						shape[index++]= new Shape(x3,y3,x4,y4,command,gr.getColor());
					     }
					if(e.getClickCount()==2)
					{
					x4 =0; y4=0;//这里设置为零是为了能都再次执行if语句 
					gr.drawLine(start_x, start_y, e.getX(), e.getY());//将双击的终点位置和开始记录的起点位置相连
					shape[index++]= new Shape(start_x, start_y, e.getX(), e.getY(),command,gr.getColor());
					}	
				}
			}
				//当鼠标按下的时候,获取起点的坐标
				 public void mousePressed(MouseEvent e)
				{
					if("曲线".equals(command)){
						x0 = e.getX();
						y0 = e.getY();
					}
					// TODO Auto-generated method stub
					x1 = e.getX();
					y1 = e.getY();
					
				}
				
				//当鼠标松开的时候,获取终点的坐标
				public void mouseReleased(MouseEvent e)
				{
					// TODO Auto-generated method stub
					x2 = e.getX();
					y2 = e.getY();
					if("直线".equals(command)){
						gr.drawLine(x1, y1, x2, y2);
						shape[index++]= new Shape(x1,y1,x2,y2,command,gr.getColor());
					}
					else if("矩形".equals(command)) {
						gr.drawRect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1-x2), Math.abs(y1-y2));
						shape[index++]= new Shape(x1,y1,x2,y2,command,gr.getColor());
					}
					else if("圆形".equals(command)) {
						//Math.min(x1, x2), Math.min(y1, y2),
						//drawoval 和drawrect 都是以某个点 向着坐标点增大的方向画图
						//Math.abs(x1-x2)求绝对值
						gr.drawOval(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1-x2), Math.abs(y1-y2));
						shape[index++]= new Shape(x1,y1,x2,y2,command,gr.getColor()); 
											}
				}
				//鼠标进入窗口
				public void mouseEntered(MouseEvent e)
				{
					
				}
                 //鼠标退出窗口
			    public void mouseExited(MouseEvent e)
			    {
			    	
			    }
				//鼠标在移动的过程中调用(没有点击)
			    public void mouseMoved(MouseEvent e)
		         {
		        	 
		         }
		         
				
			}

Shape类:

public Shape(int x1,int y1,int x2,int y2,String name,Color color)
{
	this.x1=x1;
	this.x2=x2;
	this.y1=y1;
	this.y2=y2;
	this.name=name;
	this.color=color;
}

public Color getcolor()
{
	return color;
}

public void Repaint(Graphics g)
{
	switch(name)
	{
	case "直线":
		g.drawLine(x1, y1, x2, y2);
		break;
	case "矩形":
		g.drawRect(Math.min(x1, x2), Math.min(y1 ,y2), Math.abs(x1-x2), Math.abs(y1-y2));
		break;
	case "圆形":
		g.drawOval(Math.min(x1, x2), Math.min(y1 ,y2), Math.abs(x1-x2), Math.abs(y1-y2));
		break;
	case "曲线":
		g.drawLine(x1, y1, x2, y2);
		break;
	case "多边形":
		g.drawLine(x1, y1, x2, y2);
		break;
	}
}
}

问题bug修改记录:日期:2021.7.25
①:拖动窗口之后再画图,发现画笔画图的位置和我们当前鼠标拖动的位置不一致,那是因为在创建jframe的时候就已经创建并获得了jframe的画笔graphics g,那么后面无论在哪里调用g,他的使用范围就限制在了初始化的窗口大小。
解决方法:在jframe上真正开始画图的时候才获得画笔,以获得和改变窗口之后的大小相同的绘画范围。
改进代码如下:

//当鼠标按下的时候,获取起点的坐标
				 public void mousePressed(MouseEvent e)
				{
					  gr=p.getGraphics();
					  gr.setColor(color);
			       System.out.println("pressed事件发生");
					
					if("曲线".equals(command)){
						x0 = e.getX();
						y0 = e.getY();
					}
					// TODO Auto-generated method stub
					x1 = e.getX();
					y1 = e.getY();
					
				}

清除功能也是如此,(窗口变大后,只清除了原先窗口大小的内的图像),也需要在当前获得一个新的画笔。
改进代码:

public void actionPerformed(ActionEvent e) 
	{		
		   if (e.getActionCommand()=="") {			  
		  JButton j = (JButton)e.getSource();
	//将画笔设置为按钮的背景颜色
	//前面设置了当前类的全局变量private Color color=Color.black;
			color=j.getBackground();
		}
		else {
			command = e.getActionCommand();
			if("清除".equals(command))
			{    //改进代码:
				Graphics cleang=p.getGraphics();								
				p.paint1(cleang);
			    p.shape=new Shape[10000];
			    index=0;
			}
		}
	}

其他相应位置,读者可以自行调试修改。