/文 14E.T.
2.用双缓冲实现各种图形的绘制
在一个画板程序中,用户应该能够用画笔绘制各种图形,除了上一节实现的自由画法(Freehand)外,还应该可以画直线,长方体,椭圆等等.以绘制直线为例,我们都知道,只有在松开鼠标键之后,直线才实实在在的显示在了画布上,而在拖拽鼠标的过程中,直线在画布中的显示是随着鼠标的箭头方位的变化而不断更新的.体现在程序中,这是一个不断擦除,显示,再擦除,再显示的过程.擦除的是箭头上一个点和起点间的直线,显示的是箭头当前点和起点间的的直线.这个显示的过程由update_buffer负责,而这个擦除的工作则和上一节出理刷新一样,由copy_from_offscreen_buf来完成.实际上,所谓擦除,也就是将画板恢复到某一个原来的时刻.
这一个过程在下面一个修改后的拖拽操作的处理程序中完成:
public void mouseDragged(MouseEvent e){ Graphics g = getGraphics(); copy_from_offscreen_buf(g); x1=e.getX(); y1=e.getY(); update_buffer(g,new DrawItem(x0,y0,x1,y1)); g.dispose(); }
注意,在该方法中,我们没有对后台缓冲进行更新,这是因为鼠标在拖拽的时候,虽然画板上会显示线条,但是这条直线并没有真正的画下去.那么在什么时候应该对后台缓冲更新呢?显然,是在鼠标松开的时候.我们需要在mouseReleased方法中做这个工作.
public void mouseReleased(MouseEvent e){ Graphics g = getGraphics(); copy_from_offscreen_buf(g); x1=e.getX(); y1=e.getY(); update_buffer(g,new DrawItem(x0,y0,x1,y1)); update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1)); g.dispose(); }
可以看到,只有在鼠标松开的时候,画到画板上的直线才最后确定了,我们才能够将这一条线备份到缓冲区里面去.
下面是升级后的完整的WhiteBoard.java程序.
//:WhiteBoard.java
import java.awt.*; import java.awt.event.*;
public class WhiteBoard extends Canvas implements MouseMotionListener,MouseListener{
final static int DEFAULT_BOARDWIDTH=700; final static int DEFAULT_BOARDHEIGHT=400; int x0,y0,x1,y1;
WhiteBoard(WBApplet WBApplet1){ parent = WBApplet1; off_screen_buf =parent.createImage(DEFAULT_BOARDWIDTH,DEFAULT_BOARDHEIGHT); off_screen_gc = off_screen_buf.getGraphics(); addMouseMotionListener(this); addMouseListener(this); draw_mode=2; }
synchronized public void update_buffer(Graphics g,DrawItem data) { g.drawLine(data.x0,data.y0,data.x1,data.y1); } public void mouseMoved(MouseEvent e){} public void mouseReleased(MouseEvent e){ switch(draw_mode){ case 2: Graphics g = getGraphics(); copy_from_offscreen_buf(g); x1=e.getX(); y1=e.getY(); update_buffer(g,new DrawItem(x0,y0,x1,y1)); update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1)); g.dispose(); } } public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} public void mouseClicked(MouseEvent e){} public void mouseDragged(MouseEvent e){ switch(draw_mode){ case 1: x1=e.getX(); y1=e.getY(); Graphics g = getGraphics(); update_buffer(g,new DrawItem(x0,y0,x1,y1)); update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1)); g.dispose(); x0=x1; y0=y1; break; case 2: Graphics g1 = getGraphics(); copy_from_offscreen_buf(g1); x1=e.getX(); y1=e.getY(); update_buffer(g1,new DrawItem(x0,y0,x1,y1)); g1.dispose(); } } public void mousePressed(MouseEvent e){ x0 =e.getX(); y0 =e.getY(); }
public void paint(Graphics g){ copy_from_offscreen_buf(g); }
void copy_from_offscreen_buf(Graphics g){ if(g != null) g.drawImage(off_screen_buf, 0, 0, null); }
private int draw_mode; private Image off_screen_buf; private Graphics off_screen_gc; WBApplet parent; }
class DrawItem{ DrawItem(int x0,int y0,int x1,int y1){ this.x0=x0; this.y0=y0; this.x1=x1; this.y1=y1; } int x0; int y0; int x1; int y1; }
///:~
注意到,在这个程序里面我们创建了一个新的私有变量draw_mode,用来存储绘图模式的代号.在这里,我们使用1来代表自由绘画,2来代表画直线.在构造函数中为draw_mode定义初值可以使我们对不同种类图形绘制的调试很方便,在上面的程序中,我们定义的是2,如果赋值为1,则又回到自由绘画的模式.事实上,我们应该在这样的一个框架上把程序不断的扩充和完善. 
|