编辑器程序少不了要支持undo, redo功能,如何实现?本文就是参考了设计模式中给出的思路实现了一个。
这里主要用到了两个模式:命令(command)模式, 备忘录(memento)模式
所谓编辑,功能上可以分成3个原子操作:添加新内容,编辑已有内容,删除已有内容, 因此编辑功能3个command实现,AddCommand, EditCommand, DeleteCommand. 这3个命令实现了相同的接口do, undo.
先说一下代码的风格,自从使用WTL,就喜欢上了WTL的代码风格,本文就是仿照WTL风格写的.
先看一下Command的接口:
- class CCommand
- {
- public:
- virtual ~CCommand();
- public:
- virtual bool Do() = 0;
- virtual bool Undo() = 0;
- virtual bool CanUndo() = 0;
- };
AddCommand, EditCommand, DeleteCommand都实现了这个接口类。
下面WTL风格代码开始了:
先留个空架子
- template<class TBase>
- class CCommandImplBase : public TBase
- {
- //先空着,以后留着扩展
- };
下面是主要实现:用到了event, 当Command执行时会触发event,你可以在时间里做一些诸如试图更新, 所谓event,跟C#里的事件相似,感兴趣的可以看我前面的文章用C++模拟C#事件机制。
- template< class T, class TBase = CCommand, class TTraits = CommandTraits >
- class CCommandImpl : public CCommandImplBase
- {
- public:
- typedef TTraits::CmdEvent CmdEvent;
- typedef CmdEvent::EventHandler EventHandler;
- public:
- CCommandImpl(typename CmdEvent::EventArgsargs) : m_Args(args)
- {
- #if (_MSC_VER >= 1300)
-
- bool bRet = TypeTraits::IsPointer();
- #endif
- }
- virtual ~CCommandImpl()
- {
- }
- public:
- void RegisterDoHandler(typename CmdEvent::EventHandler& handler)
- {
- m_DoEvent += handler;
- }
- void UnRegisterDoHandler(typename CmdEvent::EventHandler& handler)
- {
- m_DoEvent -= handler;
- }
- void RegisterUndoHandler(typename CmdEvent::EventHandler& handler)
- {
- m_UndoEvent += handler;
- }
- void UnRegisterUndoHandler(typename CmdEvent::EventHandler& handler)
- {
- m_UndoEvent -= handler;
- }
- public:
- virtual bool Do()
- {
- if (!DoCommand())
- {
- return false;
- }
- typename CmdEvent::ReturnValue ret = m_DoEvent(GetEventArgs());
-
- return true;
- }
- virtual bool Undo()
- {
- if (!CanUndo())
- {
- return false;
- }
-
- if (!UndoCommand())
- {
- return false;
- }
- typename CmdEvent::ReturnValue ret = m_UndoEvent(GetEventArgs());
-
- return true;
- }
- virtual bool CanUndo()
- {
- return TTraits::CanUndo;
- }
- protected:
- virtual bool DoCommand() = 0;
- virtual bool UndoCommand(){return true;}
- protected:
- typename CmdEvent::EventArgs GetEventArgs() const
- {
- return m_Args;
- }
- private:
- CmdEvent m_DoEvent;
- CmdEvent m_UndoEvent;
- typename CmdEvent::EventArgs m_Args;
- };
这里用到了CommandTraits模板,所谓Traits,能在编译时提供一些类型信息,感兴趣的同学可以去侯捷的网站看看Traits: 類型的else-if-then機製,相关的文章好多。
CommandTraits是一系列模板特化, 每个Traits包含一个event类型, 这在前面已经用到了.
- template<class T>
- struct CommandTraits;
- template<>
- struct CommandTraits
- {
- typedef Event<bool, CAddEventArgs const*> CmdEvent;
- enum { CanUndo = true};
- };
- template<>
- struct CommandTraits
- {
- typedef Event<bool, CEditEventArgs const*> CmdEvent;
- enum { CanUndo = true};
- };
- template<>
- struct CommandTraits
- {
- typedef Event<bool, CDeleteEventArgs const*> CmdEvent;
- enum { CanUndo = true};
- };
在接下来就是
AddCommand, EditCommand, DeleteCommand 的实现了
可以使用备忘录模式将每个命令需要保存的数据(CmdEvent::EventArgs)提取出来,可以用一组get,set操作实现状态的提取和保存,具体保存的内容因项目而易,很简单,不罗嗦了:
- class CAddCommand : public CCommandImpl
- {
- public:
- CAddCommand(CAddEventArgs const* pArgs);
- virtual ~CAddCommand();
- protected:
- virtual bool DoCommand()
- {
-
- }
- virtual bool UndoCommand()
- {
-
- }
- };
- class CEditCommand : public CCommandImpl
- {
- public:
- CEditCommand(CEditEventArgs const* pArgs);
- virtual ~CEditCommand();
- protected:
- virtual bool DoCommand();
- virtual bool UndoCommand();
};
class
CDeleteCommand :
public
CCommandImpl
{
public
:
CDeleteCommand(CDeleteEventArgs
const
* pArgs);
virtual
~CDeleteCommand();
protected
:
virtual
bool
DoCommand();
virtual
bool
UndoCommand();
};
所有的命令都全了,可是还要把所有命令按顺序存储,Undo的时候这按这个相反的顺序拿出来就可以了,用std::stack正好,下面实现了一个CCommandManager , 就是用来管理命令的:
- template< class TCommand >
- class CCommandManager
- {
- public:
- CCommandManager()
- :m_UndoStack()
- {
- }
- virtual ~CCommandManager(){}
- public:
- bool Excecute(TCommand* cmd)
- {
- if (!cmd->Do())
- {
- return false;
- }
- if (cmd->CanUndo())
- {
- m_UndoStack.push(cmd);
- }
- return true;
- }
- bool ReExecute()
- {
- TCommand* cmd = m_RedoStack.top();
-
- if (!Excecute(cmd))
- {
- return false;
- }
- m_RedoStack.pop();
- return true;
- }
- bool UnExecute()
- {
- TCommand* cmd = m_UndoStack.top();
-
- if (!cmd->Undo())
- {
- return false;
- }
- m_UndoStack.pop();
- m_RedoStack.push(cmd);
- return true;
- }
- void Reset()
- {
- if (!m_UndoStack.empty())
- {
- TCommand* pCmd = m_UndoStack.top();
- m_UndoStack.pop();
- delete pCmd;
- }
- }
- private:
- std::stack m_UndoStack;
- std::stack m_RedoStack;
- };
CCommandManager还可以实现为单件模式,这样可以提供一个全局访问点,这里因为篇幅关系不实现了。
至此整个Undo,Redo的框架已经实现完了,下面介绍如何使用
- CAddEventArgs* pArgs = new CAddEventArgs(......);
- CAddCommand* cmd = new CAddCommand(pArgs);
- CAddCommand::EventHandler Handler(GetView(), &CKeyinToolView::OnEditPOI);
- cmd ->RegisterDoHandler(Handler);
- GetCommandManager()->Excecute(cmd)。
- GetCommandManager()->UnExcecute()。
- GetCommandManager()->ReExcecute()。