使用设计模式实现Undo,Redo框架
2008-12-10 13:38
771 查看
编辑器程序少不了要支持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/*where TBase : public CCommand*/>
class CCommandImplBase : public TBase
{
//先空着,以后留着扩展
};
下面是主要实现:用到了event, 当Command执行时会触发event,你可以在时间里做一些诸如试图更新, 所谓event,跟C#里的事件相似,感兴趣的可以看我前面的文章用C++模拟C#事件机制。
template< class T, class TBase = CCommand, class TTraits = CommandTraits<T> >
class CCommandImpl : public CCommandImplBase<TBase>
{
public:
typedef TTraits::CmdEvent CmdEvent;
typedef CmdEvent::EventHandler EventHandler;
public:
CCommandImpl(typename CmdEvent::EventArgs/* const& */args) : m_Args(args)
{
#if (_MSC_VER >= 1300)
//COMPLIE_ASSERT(IsPointer(args));
bool bRet = TypeTraits<CmdEvent::EventArgs>::IsPointer();
#endif
}
virtual ~CCommandImpl()
{
}
public:
void RegisterDoHandler(typename CmdEvent::EventHandler/* const*/& handler)
{
m_DoEvent += handler;
}
void UnRegisterDoHandler(typename CmdEvent::EventHandler/* const*/& handler)
{
m_DoEvent -= handler;
}
void RegisterUndoHandler(typename CmdEvent::EventHandler/* const*/& handler)
{
m_UndoEvent += handler;
}
void UnRegisterUndoHandler(typename CmdEvent::EventHandler/* const*/& 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;}
// implementations
protected:
typename CmdEvent::EventArgs /*const&*/ 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;
//Common Command Traits
template<>
struct CommandTraits<CAddCommand>
{
typedef Event<bool, CAddEventArgs const*> CmdEvent;
enum { CanUndo = true};
};
template<>
struct CommandTraits<CEditCommand>
{
typedef Event<bool, CEditEventArgs const*> CmdEvent;
enum { CanUndo = true};
};
template<>
struct CommandTraits<CDeleteCommand>
{
typedef Event<bool, CDeleteEventArgs const*> CmdEvent;
enum { CanUndo = true};
};
在接下来就是 AddCommand, EditCommand, DeleteCommand 的实现了
可以使用备忘录模式将每个命令需要保存的数据(CmdEvent::EventArgs)提取出来,可以用一组get,set操作实现状态的提取和保存,具体保存的内容因项目而易,很简单,不罗嗦了:
class CAddCommand : public CCommandImpl<CAddCommand>
{
public:
CAddCommand(CAddEventArgs const* pArgs);
virtual ~CAddCommand();
protected:
virtual bool DoCommand()
{
//这里写实际的代码,比如可以将输入的内容存起来. 可以考虑用备忘录模式实现下同,就不罗列代码了
}
virtual bool UndoCommand()
{
//这里写实际的代码,比如可以将输入的内容删掉. 下同,就不罗列代码了
}
};
class CEditCommand : public CCommandImpl<CEditCommand>
{
public:
CEditCommand(CEditEventArgs const* pArgs);
virtual ~CEditCommand();
protected:
virtual bool DoCommand();
virtual bool UndoCommand();
};
class CDeleteCommand : public CCommandImpl<CDeleteCommand>
{
public:
CDeleteCommand(CDeleteEventArgs const* pArgs);
virtual ~CDeleteCommand();
protected:
virtual bool DoCommand();
virtual bool UndoCommand();
};
所有的命令都全了,可是还要把所有命令按顺序存储,Undo的时候这按这个相反的顺序拿出来就可以了,用std::stack正好,下面实现了一个CCommandManager , 就是用来管理命令的:
template< class TCommand /*where TCommand : public CCommand*/ >
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<TCommand*> m_UndoStack;
std::stack<TCommand*> m_RedoStack;
};
CCommandManager<CCommand>还可以实现为单件模式,这样可以提供一个全局访问点,这里因为篇幅关系不实现了。
至此整个Undo,Redo的框架已经实现完了,下面介绍如何使用
//1. 定义命令参数并填写需要的值
CAddEventArgs* pArgs = new CAddEventArgs(......);
//2. 定义Command, 并把相应的参数填进去.
CAddCommand* cmd = new CAddCommand(pArgs);
//3. 定义事件响应函数.
CAddCommand::EventHandler Handler(GetView(), &CKeyinToolView::OnEditPOI);
//4. 把事件响应函数注册到event
cmd ->RegisterDoHandler(Handler);
//5. 现在放心的运行命令,运行成功则CommandManager会信息保存到Undo栈中,并触发event处理函数.
GetCommandManager()->Excecute(cmd)。
//6. 在需要Undo的时候只需这样
GetCommandManager()->UnExcecute()。
//7. 在需要Redo的时候只需这样
GetCommandManager()->ReExcecute()。
这里主要用到了两个模式:命令(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/*where TBase : public CCommand*/>
class CCommandImplBase : public TBase
{
//先空着,以后留着扩展
};
下面是主要实现:用到了event, 当Command执行时会触发event,你可以在时间里做一些诸如试图更新, 所谓event,跟C#里的事件相似,感兴趣的可以看我前面的文章用C++模拟C#事件机制。
template< class T, class TBase = CCommand, class TTraits = CommandTraits<T> >
class CCommandImpl : public CCommandImplBase<TBase>
{
public:
typedef TTraits::CmdEvent CmdEvent;
typedef CmdEvent::EventHandler EventHandler;
public:
CCommandImpl(typename CmdEvent::EventArgs/* const& */args) : m_Args(args)
{
#if (_MSC_VER >= 1300)
//COMPLIE_ASSERT(IsPointer(args));
bool bRet = TypeTraits<CmdEvent::EventArgs>::IsPointer();
#endif
}
virtual ~CCommandImpl()
{
}
public:
void RegisterDoHandler(typename CmdEvent::EventHandler/* const*/& handler)
{
m_DoEvent += handler;
}
void UnRegisterDoHandler(typename CmdEvent::EventHandler/* const*/& handler)
{
m_DoEvent -= handler;
}
void RegisterUndoHandler(typename CmdEvent::EventHandler/* const*/& handler)
{
m_UndoEvent += handler;
}
void UnRegisterUndoHandler(typename CmdEvent::EventHandler/* const*/& 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;}
// implementations
protected:
typename CmdEvent::EventArgs /*const&*/ 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;
//Common Command Traits
template<>
struct CommandTraits<CAddCommand>
{
typedef Event<bool, CAddEventArgs const*> CmdEvent;
enum { CanUndo = true};
};
template<>
struct CommandTraits<CEditCommand>
{
typedef Event<bool, CEditEventArgs const*> CmdEvent;
enum { CanUndo = true};
};
template<>
struct CommandTraits<CDeleteCommand>
{
typedef Event<bool, CDeleteEventArgs const*> CmdEvent;
enum { CanUndo = true};
};
在接下来就是 AddCommand, EditCommand, DeleteCommand 的实现了
可以使用备忘录模式将每个命令需要保存的数据(CmdEvent::EventArgs)提取出来,可以用一组get,set操作实现状态的提取和保存,具体保存的内容因项目而易,很简单,不罗嗦了:
class CAddCommand : public CCommandImpl<CAddCommand>
{
public:
CAddCommand(CAddEventArgs const* pArgs);
virtual ~CAddCommand();
protected:
virtual bool DoCommand()
{
//这里写实际的代码,比如可以将输入的内容存起来. 可以考虑用备忘录模式实现下同,就不罗列代码了
}
virtual bool UndoCommand()
{
//这里写实际的代码,比如可以将输入的内容删掉. 下同,就不罗列代码了
}
};
class CEditCommand : public CCommandImpl<CEditCommand>
{
public:
CEditCommand(CEditEventArgs const* pArgs);
virtual ~CEditCommand();
protected:
virtual bool DoCommand();
virtual bool UndoCommand();
};
class CDeleteCommand : public CCommandImpl<CDeleteCommand>
{
public:
CDeleteCommand(CDeleteEventArgs const* pArgs);
virtual ~CDeleteCommand();
protected:
virtual bool DoCommand();
virtual bool UndoCommand();
};
所有的命令都全了,可是还要把所有命令按顺序存储,Undo的时候这按这个相反的顺序拿出来就可以了,用std::stack正好,下面实现了一个CCommandManager , 就是用来管理命令的:
template< class TCommand /*where TCommand : public CCommand*/ >
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<TCommand*> m_UndoStack;
std::stack<TCommand*> m_RedoStack;
};
CCommandManager<CCommand>还可以实现为单件模式,这样可以提供一个全局访问点,这里因为篇幅关系不实现了。
至此整个Undo,Redo的框架已经实现完了,下面介绍如何使用
//1. 定义命令参数并填写需要的值
CAddEventArgs* pArgs = new CAddEventArgs(......);
//2. 定义Command, 并把相应的参数填进去.
CAddCommand* cmd = new CAddCommand(pArgs);
//3. 定义事件响应函数.
CAddCommand::EventHandler Handler(GetView(), &CKeyinToolView::OnEditPOI);
//4. 把事件响应函数注册到event
cmd ->RegisterDoHandler(Handler);
//5. 现在放心的运行命令,运行成功则CommandManager会信息保存到Undo栈中,并触发event处理函数.
GetCommandManager()->Excecute(cmd)。
//6. 在需要Undo的时候只需这样
GetCommandManager()->UnExcecute()。
//7. 在需要Redo的时候只需这样
GetCommandManager()->ReExcecute()。
相关文章推荐
- Undo/Redo的使用,以及用命令模式实现Undo/Redo无限制
- C++实现Undo和Redo框架(命令模式)
- 设计模式:利用Command模式实现无限次数的Undo/Redo功能
- 设计模式——命令模式实现undo、redo
- 设计模式学习(十四)————抽象工厂模式(使用Qt框架的反射技术——根据字符串动态创建类来实现)
- 用 Command设计模式实现 Undo和Redo 功能
- C++下利用Command设计模式实现undo和redo
- JTABLE 一个小格子添加2个按钮实现国际化 使用观察者设计模式
- 使用设计模式中的单例模式来实现C++的boost库
- app UI自动化框架改进----使用pa设计模式抽离页面元素
- 怎样使用Java实现Factory设计模式
- 通过例子学设计模式之--建造者模式以及使用场景说明(C++实现)
- 【设计模式】使用unity实现代理模式(Proxy mode)
- 使用Object-C实现23种设计模式之建造者模式
- 使用Java来实现编辑器的Undo Redo功能
- 使用 IBM 中间件实现 SaaS 解决方案,第 4 部分: 单一实例多租户应用程序中资源共享的设计模式
- 基于事件的监听,消息订阅设计模式的实现框架,ERP,OA,复杂,灵活多变的项目的福音
- PHP设计模式笔记:使用PHP实现适配器模式
- 设计模式实战(一)——使用策略模式(strategy pattern)实现多关键字排序
- 设计模式(4)--使用ASM实现AOP