MFC에서 Undo 와 Redo 구현하기
MSDN의 How Do I: Implement Undo and Redo in MFC 의 내용을 정리한 것입니다. 링크는 맨 아래.
이 강좌에서 중요하다고 여겨지는 부분은 Undo 와 Redo 를 구현하기 위해 데이터를 중복해서 저장하는 것을 방지하는 것이다. 그래서 이 강좌에서는 효율적으로 메모리를 사용하기 위해, 전체 Array 를 복사하는 것이 아니라 꼭 필요한 데이터만 백업해 놓고, 그 데이터를 이용하여 Undo/Redo 를 구현하였다. 동시에 Undo와 Redo 의 제한을 가하여 과다하게 메모리가 사용되는 것을 금지하였다.
강좌에서 사용된 예제는 마우스가 클릭될 때마다 그 지점에 원을 그리는 것이다. 그 코드는 아래와 같다.
Doc 클래스(CDocument를 상속)에 먼저 Array를 하나 선언하고,
//
Attributes public: CArray<CPoint> m_Points; |
Doc 클래스에 AddPoint 함수를 추가한다.
void AddPoint(CPoint point) { m_Points.Add(point); UpdateAllViews(NULL); } |
View 클래스에 마우스 클릭시 점을 추가하는 코드와 그리는 코드를 추가한다.
void CUndoRedoView::OnLButtonDown(UINT
nFlags, CPoint
point) { CUndoRedoDoc*
pDoc = GetDocument(); pDoc->AddPoint(point); CView::OnLButtonDown(nFlags,
point); } void CUndoRedoView::OnDraw(CDC* pDC) { CUndoRedoDoc*
pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: add
draw code for native data here for (int i = 0; i < pDoc->m_Points.GetCount();
i++) { CPoint&
pt = pDoc->m_Points[i]; pDC->Ellipse(pt.x - 10, pt.y - 10, pt.x + 10, pt.y + 10); } } |
이제 본격적으로 Undo/Redo를 구현해보자.
Undo/Redo 와 관련된 정보를 저장하는 CAction 클래스를 클래스를 정의한다. 이 클래스의 인스턴스는 CList 에 저장되므로 복사생성자 또한 구현한다.
class CAction { public: enum Actions { actionAdd, actionDelete, actionMove }; CAction() { } CAction(CAction& action) { m_nAction
= action.m_nAction; m_nInfo
= action.m_nInfo; m_Point
= action.m_Point; } int m_nAction; int m_nInfo; CPoint
m_Point; }; |
Doc에 Undo/Redo 를 위한 리스트 멤버 변수를 추가한다.
protected: CList<CAction> m_Undo; CList<CAction> m_Redo; |
이제 AddPoint 를 수정하고, Undo, Redo, CanUndo, CanRedo 함수를 구현한다.
#define MAX_UNDO 50 void AddPoint(CPoint point) { // Record
action for undo CAction
action; action.m_nAction = CAction::actionAdd; action.m_nInfo = (int)m_Points.Add(point); action.m_Point = point; m_Undo.AddHead(action); while
(m_Undo.GetCount()
> MAX_UNDO) m_Undo.RemoveTail(); // Redo
sequence m_Redo.RemoveAll(); UpdateAllViews(NULL); } bool CanUndo() { return
(m_Undo.GetCount()
> 0); } void Undo() { CAction
action = m_Undo.RemoveHead(); switch
(action.m_nAction) { case
CAction::actionAdd: m_Points.RemoveAt(action.m_nInfo); m_Redo.AddHead(action); while (m_Redo.GetCount() > MAX_UNDO) m_Redo.RemoveTail(); break; } UpdateAllViews(NULL); } bool CanRedo() { return
(m_Redo.GetCount()
> 0); } void Redo() { CAction
action = m_Redo.RemoveHead(); switch
(action.m_nAction) { case
CAction::actionAdd: m_Points.Add(action.m_Point); m_Undo.AddHead(action); while (m_Undo.GetCount() > MAX_UNDO) m_Undo.RemoveTail(); break; } UpdateAllViews(NULL); } |
여기서 주의깊게 봐야 할 부분은 AddPoint 함수내에서 m_nInfo 에 저장되는 값이다.
action.m_nInfo = (int)m_Points.Add(point); |
CArray<>::Add 에 의해 반환되는 값은 추가된 엘리먼트의 인덱스값이다. 이 값은 Undo 가 수행될 때 이 인덱스를 이용하여 Array 에서 해당 값을 삭제한다.
m_Points.RemoveAt(action.m_nInfo); |
이처럼 Redo/Undo 시에 Doc 에 존재하는 전체 데이터의 Snapshot 을 가지는 게 아니라, 최소한의 데이터만으로 구현하는 것이 젤 중요한 포인트일 것이다.
참고 : http://msdn.microsoft.com/ko-kr/visualc/cc948995(en-us).aspx