状态模式在绘图程序中的应用

来源:互联网 发布:站外优化 编辑:程序博客网 时间:2024/05/01 00:09
 
状态模式在绘图程序中的应用
设计绘图程序会面临根据绘图上下文,确定用户动作的问题。例如用户在画布上的操作只有鼠标点击,双击,拖动等很少几种,但在与不同的绘图工具组合后,各种操作的结果有很大不同。例如在选择绘制矩形后,鼠标点击应创建一个矩形,继续拖动应改变矩形的大小;而选择绘制折线时,鼠标点击应创建一个点,并根据绘图上下文与上一个点进行直线连接,直到用户双击结束。随着绘图工具种类的增加,鼠标事件处理函数会变得非常负责,而且会有很多的判断结构,不利于软件的维护和扩展。
状态模式就很适于解决这个问题。首先将每个绘图工具视为一个绘制状态,例如客户选择矩形工具后进入矩形绘制状态,而选择折线工具则进入折线绘制模式等。每个模式均实现了绘图的操作,只是操作的内容有所不同。因此可以将操作提取成为一个接口,而所有的模式均实现该接口。
绘图操作不仅仅依赖于鼠标或键盘的操作,也依赖于一些用户的全局设置,例如图形文件、笔画粗细和填充颜色等,由于这些选择可以应用于多种图形,因此可以将其保存在绘图上下文对象中。在用户更改了图形文件,例如新建了一个文件,改变颜色选择后,可以修改绘图上下文对象。
上面提到的当前绘图状态也是绘图上下文的一部分,因此绘图上下文对象中也保存当前绘图状态对象,用户可以通过选择绘图工具切换绘图状态对象。在绘制时,绘图状态对象得到该绘图上下文,并根据其设置进行相应的操作。
绘图上下文对象一般是全局唯一的,因此可以考虑设计成单例模式。
该部分的类图如下:
其中Context为绘图上下文,MouseDraw为鼠标操作接口,State为状态基类,实现了MouseDraw接口,但没有具体的操作。DrawRect为绘图矩形的状态类,SelectRect为选择矩形的状态类。
Context为单例模式,可以通过getInstance得到它的全局唯一对象,其中保存了当前图形文档,颜色和笔画的选择以及当前的绘图模式对象。
Context context = Context.getInstance();
Context类实现了事件处理接口,并将得到事件操作派发到当前状态对象实现的操作中。在使用时,可以使用以下代码切换绘图操作到绘制矩形的模式。状态模式的设计也很好的使用接口隔离了具体操作。
DrawRect dr = new DrawRect(context);
              context.setState(dr);
绘图状态在创建时也得到了绘图上下文,实际上对单例模式并不需要通过参数传入。具体的绘图在当前状态对象中根据鼠标操作和上下文进行。
与状态模式相关的代码示例如下:
import java.awt.Cursor;
 
import org.apache.batik.dom.events.DOMMouseEvent;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.swing.JSVGCanvas;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGLocatable;
import org.w3c.dom.svg.SVGMatrix;
import org.w3c.dom.svg.SVGPoint;
import org.w3c.dom.svg.SVGSVGElement;
 
 
//Singleton
class Context implements EventListener
{
    privatestaticfinal Context m_instance = new Context();
   
    //Because we have only one instance of Context class, so we don't
    //need set context attribute to static
    private State s;
   
    //Drawing Context
    private JSVGCanvas svgCanvas;
    Element target = null;
   
    privateintstrokeWidth;
    privateintfrontColor = 1;
    privateintbackColor = 0;
    privateintdragFlag = 0;
   
    //Private constructor
    private Context()
    {
       s = new State(this);
    }
   
    //Return the only one instance
    publicstatic Context getInstance()
    {
       returnm_instance;
    }
 
    publicvoid handleEvent(Event evt) {
       //System.out.println(evt.getType());
      
       if(evt.getType().equalsIgnoreCase("mousedown"))
       {
           DOMMouseEvent mevt = (DOMMouseEvent)evt;
           target = (Element)mevt.getTarget();
           s.mousePress(mevt.getClientX(), mevt.getClientY(),target);
           dragFlag = 1;
       }
       elseif(evt.getType().equalsIgnoreCase("mouseup"))
       {
           DOMMouseEvent mevt = (DOMMouseEvent)evt;
           s.mouseRelease(mevt.getClientX(), mevt.getClientY());
           dragFlag = 0;
       }
       elseif(evt.getType().equalsIgnoreCase("mousemove"))
       {
           if(dragFlag==1)
           {
              DOMMouseEvent mevt = (DOMMouseEvent)evt;
              s.mouseDrag(mevt.getClientX(), mevt.getClientY());
           }
       }
    }
   
    //Attribute Getter/Setters
    publicint getFrontColor() {
       returnfrontColor;
    }
 
    publicvoid setFrontColor(int frontColor) {
       this.frontColor = frontColor;
    }
 
    public State getState() {
       returns;
    }
 
    publicvoid setState(State s) {
       this.s = s;
    }
 
    public JSVGCanvas getSvgCanvas() {
       returnsvgCanvas;
    }
 
    publicvoid setSvgCanvas(JSVGCanvas svgCanvas) {
       this.svgCanvas = svgCanvas;
    }
 
    public Element getTarget() {
       returntarget;
    }
 
    publicvoid setTarget(Element target) {
       this.target = target;
    }
}
 
interface MouseDraw {
    publicvoid mousePress(int x, int y, Element e);
    publicvoid mouseDrag(int x, int y);
    publicvoid mouseRelease(int x, int y);
}
 
class State implements MouseDraw
{
    private Context currentContext;
    String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI;
   
    State(Context c)
    {
       currentContext = c;
    }
   
    publicvoid mousePress(int x, int y, Element e)
    {
      
    }
    publicvoid mouseDrag(int x, int y)
    {
      
    }
    publicvoid mouseRelease(int x, int y)
    {
      
    }
   
    // Convert client point to current svg document point
    public SVGPoint clientPointToSvgDocument(JSVGCanvas canvas, int x, int y)
    {
       Document svgDoc = canvas.getSVGDocument();
       Element svgRoot = svgDoc.getDocumentElement();
       SVGMatrix ctm = ((SVGLocatable)svgRoot).getScreenCTM();
      
       SVGPoint point = ((SVGSVGElement)svgRoot).createSVGPoint();
       point.setX(x);
       point.setY(y);
       point = point.matrixTransform(ctm.inverse());
      
       return point;
    }
 
    public Context getCurrentContext() {
       returncurrentContext;
    }
 
    publicvoid setCurrentContext(Context currentContext) {
       this.currentContext = currentContext;
    }
}
 
class DrawRect extends State
{
    SVGPoint orgPoint, currentPoint;
    SVGDocument svgDoc;
    DrawRect(Context c)
    {
       super(c);
    }
   
    publicvoid mousePress(int x, int y, Element e)
    {
       JSVGCanvas svgCanvas = getCurrentContext().getSvgCanvas();
       svgCanvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));  
       orgPoint = clientPointToSvgDocument(svgCanvas, x, y);
       svgDoc = svgCanvas.getSVGDocument();
       if(svgDoc == null)
           return;
      
       svgCanvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(new Runnable(){
           publicvoid run(){
              Element current = svgDoc.createElementNS(svgNS, "rect");
              current.setAttributeNS(null, "x", String.valueOf(orgPoint.getX()));
              current.setAttributeNS(null, "y", String.valueOf(orgPoint.getY()));
              current.setAttributeNS(null, "width", "1");
              current.setAttributeNS(null, "height", "1");
              current.setAttributeNS(null, "fill", "red");
             
              String currentId = "Elem" + String.valueOf(Math.random());
              current.setAttributeNS(null, "id", currentId);
                    
              // attach the rectangle to the svg root element
              svgDoc.getDocumentElement().appendChild(current);
              getCurrentContext().setTarget(current);
           }
                    
        });
    }
    publicvoid mouseDrag(int x, int y)
    {
       JSVGCanvas svgCanvas = getCurrentContext().getSvgCanvas();
       svgDoc = svgCanvas.getSVGDocument();
       if(svgDoc == null)
           return;
      
       Element current = getCurrentContext().getTarget();
       if(current == null)
           return;
      
       currentPoint = clientPointToSvgDocument(svgCanvas, x, y);
 
       svgCanvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(new Runnable(){
           publicvoid run(){
              Element current = getCurrentContext().getTarget();
              if(currentPoint.getX() < orgPoint.getX())
                  current.setAttributeNS(null, "x", String.valueOf(currentPoint.getX()));
              current.setAttributeNS(null, "width", String.valueOf(Math.abs(currentPoint.getX()-orgPoint.getX())));
              if(currentPoint.getY() < orgPoint.getY())
                  current.setAttributeNS(null, "y", String.valueOf(currentPoint.getY()));
              current.setAttributeNS(null, "height", String.valueOf(Math.abs(currentPoint.getY()-orgPoint.getY())));
           }
       });
    }
    publicvoid mouseRelease(int x, int y)
    {
       getCurrentContext().setTarget(null);
    }
}
 
////////////////////////////////////////
 
class SelectRect extends State
{
    SVGPoint orgPoint, currentPoint;
    SVGDocument svgDoc;
   
    SelectRect(Context c)
    {
       super(c);
    }
   
    //Select a rect
    publicvoid mousePress(int x, int y, Element e)
    {
       JSVGCanvas svgCanvas = getCurrentContext().getSvgCanvas();
       svgCanvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));  
       orgPoint = clientPointToSvgDocument(svgCanvas, x, y);
       getCurrentContext().target = e;
    }
   
    //When select a rect and drag it, change rect x and y attribute
    publicvoid mouseDrag(int x, int y)
    {
       if(getCurrentContext().target == null)
           return;
       JSVGCanvas svgCanvas = getCurrentContext().getSvgCanvas();
       currentPoint = clientPointToSvgDocument(svgCanvas, x, y);
      
       svgCanvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(new Runnable(){
           publicvoid run(){
              Element current = getCurrentContext().getTarget();
              float xa = Float.parseFloat(current.getAttributeNS(null,"x"));
              float ya = Float.parseFloat(current.getAttributeNS(null,"y"));
              xa = (xa + currentPoint.getX() - orgPoint.getX());
              ya = (ya + currentPoint.getY() - orgPoint.getY());
              current.setAttributeNS(null, "x", String.valueOf(xa));
              current.setAttributeNS(null, "y", String.valueOf(ya));
              orgPoint = currentPoint;
           }
       });
    }
   
    //Release mouse and set selection to null
    publicvoid mouseRelease(int x, int y)
    {
       getCurrentContext().setTarget(null);
    }
}
 
代码中使用Batik SVG作为绘图基础实现方法,相信使用的人并不多,但其结构完全可以在其它绘图实现中使用,例如GDI或Java2D等,因为设计模式作为设计思想的体现,是超越具体实现的。
示例代码中仅实现了三种鼠标操作和两种绘图工具,基于这个结构,用户可以很方便的扩展自己的操作和工具种类。
 
原创粉丝点击