/文 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,则又回到自由绘画的模式.事实上,我们应该在这样的一个框架上把程序不断的扩充和完善.  
 
  |