2009. 7. 4. 09:40

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