设计模式在鼠标绘图中的应用

                                      

设计模式是代码重构的最终目标,在程序设计中有效的运用这项技术,可以大大提高代码的可读性和可维护性。使整个程序设计结构趋向精致完美。下面我就从常见矢量绘图的例子来阐述一些常见模式的运用。

 

矢量绘图系统中,用户需要用鼠标在视图中绘制多种图形(点,折线,多边形,圆,曲线等),并对其进行编辑。

我们来看看鼠标绘制图形的操作,这里主要响应3个消息

OnLButtonDown()鼠标左键单击消息,开始绘制图形和图形特征点的定位.

OnMouseMove()鼠标移动消息,处理当前图形特征点的位置移动以及图形随之相应的动态绘制.

OnLButtonDblClk()鼠标左键双击消息,结束操作,完成图形绘制。

 

我看过一些初学者写的绘图例子,在没有学习设计模式情况下,大多数人的代码可能就像以下这样:

 

void CDrawView:: OnLButtonDown(UINT nFlags, CPoint point)

{

   If (绘制点)

{

。。。
}

Else if(绘制折线)

{

。。。
}

Else if(绘制多边形)

{

。。。
}

。。。

}

OnMouseMove()OnLButtonDblClk()也与之类似。

这种代码充斥大量的代码分支,无论是阅读还是维护都极其困难,我曾经看过长达1000多行这样的函数,第一感觉就是晕眩。

我们来看看设计模式中对于状态模式的描述.

在下面的情况下可以使用状态模式:

1)     一个对象的行为取决于它的状态,并且它必须在运行时改变它的行为.

2)     一个操作中含有庞大的多分支条件语句,且这些分支依赖于对象的状态,这个状态通常用一个或者多个枚举常量来表示,通常有多个操作包含这一相同的条件结构.state将每一个分支条件放入一个独立的类中,这使得你可以根据对象自身的情况将对象的状态作为一个对象,这个对象可以不依赖于其他对象而独立变化.

 

这刚好适用于我们现在要处理的问题,我们可以设计一个类专门处理鼠标消息的类CMouseTool,它提供了与以上相对应的几个接口:

OnLButtonDown(), OnMouseMove(),OnLButtonDblClk().

我们一个在CDrawView声明一个指向当前绘制工具对象的指针变量

CMouseTool* m_pcCurTool

现在我们的程序可以简单的这么写了:

 

void CDrawView:: OnLButtonDown(UINT nFlags, CPoint point)

{

   if (m_pcCurTool)

      m_pcCurTool-> OnLButtonDown (nFlags, point);

}

 

void CDrawView::OnMouseMove(UINT nFlags, CPoint point)

{

   if (m_pcCurTool)

      m_pcCurTool->OnMouseMove(nFlags, point);

}


void CDrawView:: OnLButtonDblClk (UINT nFlags, CPoint point)

{

   if (m_pcCurTool)

      m_pcCurTool-> OnLButtonDblClk (nFlags, point);

}

接下来就是鼠标工具类的具体设计了.

我们逐个分析一下这些鼠标操作的具体行为.

OnLButtonDown鼠标左键单击消息中,我们要记录:鼠标点击的起始点, 鼠标当前点击位置, 鼠标上一位置,鼠标当前位置,记录鼠标此次操作点击次数.并在绘制不同图形的情况下去进行一些操作.我们可以看到除了 在绘制不同图形的情况去进行一些操作之外,其余都是所有图形绘制工具子类共有的部分,因此OnLButtonDown函数在一般情况下,可以设计为以下的方式.

void CMouseTool::OnLButtonDown(UINT nFlags, CPoint pt)

{

   if (0L == m_lClickTimes)

      m_cStartPoint = pt; // 记录初始点

   SetCurPoint(pt); // 设置当前点

   m_cDownPoint = m_cLastPoint = m_cCurPoint;

   m_lClickTimes++;

   Perform_LButtonDown(nFlags);

}

其中Perform_LbuttonDown是私有的纯虚函数,如果具现的鼠标工具子类的左键单击消息遵从以上的行为的话,只需要那么重载Perform_LbuttonDown实现.比如多边形绘制工具CPolygonTool就可以如此实现:

void CPolygonTool:: Perform_LButtonDown(UINT nFlags)

{

m_pcNewPolygon->AddPoint(m_cCurPoint);

}

这时候CMouseTool::OnLButtonDown()又表现为另外一种经典的设计模式 - 模板模式.

我们看看模板模式的描述:

   定义一个操作中的算法的骨架,而将一些步骤延续到子类中,Template模式使得子类可以不改变一个算法的结构即可重定义该算法某些特定步骤.

根据这个设计思想以及消息响应的具体情况,其它两个消息响应函数也是类似:

   void CMouseTool::OnMouseMove(UINT nFlags, CPoint pt)

{

   // 尚未点击触发鼠标操作,不予处理

   if (0L == m_lClickTimes)

      return;

 

   // 记录鼠标轨迹

   m_cLastPoint = m_cCurPoint;

   SetCurPoint(pt);

   CRect rc;

   // 执行响应鼠标移动的操作

   Perform_MouseMove(nFlags, rc);

   // 绘制视图

RefreshView(rc, &dc);

}

Void CMouseTool::OnLButtonDblClk(UINT nFlags,CPoint pt, CdrawView* pcView)

{

   // 执行响应鼠标双击的操作

   Perform_LButtonDblClk();

   pcView->Invalidate();

   m_lClickTimes = 0L;

}

其中的Perform_MouseMove(nFlags, rc), RefreshView (rc, &dc);Perform_LButtonDblClk()都是私有的纯虚函数, 具现的鼠标工具子类的鼠标左键单击消息遵从以上的行为模式的话重载即可.

从上分析可以看到,对于鼠标操作的处理,整体上可以看作是状态模式和模板模式的杂糅体,在实际工作中为了解决某个问题,我们的使用的方式往往不是某一单一的模式,而是根据具体情况找出其中的规律,发挥自己的创造力灵活运用,这样才能达到最好的效果.

另附上各类的基本接口代码:

class CMouseTool

{

protected:

   CMouseTool(const long lToolType, const UINT nCursorID);

   virtual ~CMouseTool(){}

  

public:

   /* ------------------------ 消息响应函数 ------------------------------- */

   virtual void  OnMouseMove(UINT nFlags,CPoint pt);

   virtual void  OnLButtonDown(UINT nFlags, CPoint pt);

   virtual void  OnLButtonDblClk(UINT nFlags, CPoint pt);

 

private:

   /* ------------------------ 消息执行函数 ------------------------------- */

   // 移动鼠标时响应的操作

   virtual BOOL  Perform_MouseMove(UINT nFlags, CRect& rc);

   // 单击鼠标左键时的操作

   virtual void  Perform_LButtonDown(UINT nFlags);

   // 双击鼠标左键时响应的操作

   virtual BOOL  Perform_LButtonDblClk();

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章