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

梧桐168 2006-04-12 02:41:45
设计模式的应用

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

矢量绘图系统中,用户需要用鼠标在视图中绘制多种图形(点,折线,多边形,圆,曲线等),并对其进行编辑。
我们来看看鼠标绘制图形的操作,这里主要响应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().

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

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()都是私有的纯虚函数, 具现的鼠标工具子类的鼠标左键单击消息遵从以上的行为模式的话重载即可.
从上分析可以看到,对于鼠标操作的处理,整体上可以看作是状态模式和模板模式的杂糅体,在实际工作中为了解决某个问题,我们的使用的方式往往不是某一单一的模式,而是根据具体情况找出其中的规律,发挥自己的创造力灵活运用,这样才能达到最好的效果.
另附上CMouseTool 类的基本接口代码:
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();
// 重新绘制屏幕
virtual void RefreshScreen(const CRect& rc, CDC* pDC);
}
...全文
800 12 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
femalelover 2007-03-24
  • 打赏
  • 举报
回复
我到是觉得有时候用了模式之后, 代码更加难以看懂, 因为嵌套的层次会加深.
小程序里没必要模式.
lyg_zy 2007-03-24
  • 打赏
  • 举报
回复
赞同!
wanglovec 2006-11-02
  • 打赏
  • 举报
回复
好文章 MARK
梧桐168 2006-04-26
  • 打赏
  • 举报
回复
zengyongjoy:
这个是细节问题,文章中我没考虑,不过实际代码中已经解决.

对于绘制工具, 又有一个抽象基类:
class CDrawTool : public CMouseTool
{
...
protected:
const auto_ptr<CShape> m_cShape;
...
}
BOOL CDrawTool::Perform_LButtonDblClk(CDrawView* pcView)
{
....
// 完成图形绘制,添加到视的矢量图形容器中
m_pcView->AddNewShape(m_cShape->Clone());
....
}


重绘得问题是这样解决得:
1.在工具开始使用得时候,将窗口背景图保存下来.
2.鼠标移动得时候,使用脏矩形技术来完成常数时间得刷新.
3.鼠标双击的时候只要将图元绘制到背景图,然后再将背景图贴到屏幕上即可.
KDE 2006-04-25
  • 打赏
  • 举报
回复
STATE + OBSERVER
zengyongjoy 2006-04-24
  • 打赏
  • 举报
回复
理论是这样的。可是还有一个根本的问题没有提及
当perform_someFunction的时候,会把创建的临时对象加入到Document中保存
然后同时要更新相关视图。那么相关视图的更新要采取一定的措施避免View中的Client area全部重绘,优化刷新模式。
梧桐168 2006-04-13
  • 打赏
  • 举报
回复
to abbly:
对, m_pcCurTool是CMouseTool * 类型的指针,指向当前工具类的对象指针.

to goodboy1881:
再处理这个问题上,只用经典的state模式是不够的,我这里用的是state模式和template模式
杂糅的方法.

希望能和大家多多交流.
积木 2006-04-12
  • 打赏
  • 举报
回复
我记得我去年11月份的时候也在这个讨论区问过这个事情。
积木 2006-04-12
  • 打赏
  • 举报
回复
这就是经典的画图工具的做法了,
名字叫做state模式……
http://blog.csdn.net/goodboy1881/archive/2006/03/23/635963.aspx
我也写过一片相似的文章。
fei201 2006-04-12
  • 打赏
  • 举报
回复
楼主的想法很不错,值得借鉴.
abblly 2006-04-12
  • 打赏
  • 举报
回复
楼主说的还不是十分详细,比如没有说m_pcCurTool是什么,我理解是CMouseTool * 类型的;而CMouseTool的各个子类里面也应包含一个CDrawView * 的成员吧,不知道理解的对否。
abblly 2006-04-12
  • 打赏
  • 举报
回复
mark 一下

5,530

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 模式及实现
社区管理员
  • 模式及实现社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧