. 변경 관리

편집기의 편집 대상은 텍스트인데 텍스트는 일반적으로 파일 형태로 존재하므로 파일에 있는 텍스트를 읽어 오고 편집된 텍스트를 다시 파일로 저장할 수 있어야 한다. 이 기능이 없다면 매번 텍스트를 처음부터 입력해야 하고 편집한 내용을 보관할 방법이 없게 되므로 파일 입출력 기능은 편집기의 가장 필수적인 기능이라 할 수 있다. 하지만 편집 컨트롤이 파일 입출력을 직접 하지는 않으므로 컨트롤은 파일 관리를 할 필요가 없으며 호스트 프로그램이 해야 한다. ApiEdit는 호스트가 읽어준 파일을 버퍼로 입출력을 할 수 있는 기능만 제공하면 된다.

파일 입출력 기능은 Dangeun에 작성될 것이며 ApiEdit는 호스트가 파일 내용을 버퍼에 읽고 쓸 수 있도록 하는 함수만 제공하면 된다. 이 기능을 추가하기 위해 새로 Dangeun2 프로젝트를 작성하자. 지금까지 해 왔던 것처럼 Dangeun1 폴더를 통째로 복사한 후 폴더 이름만 Dangeun2로 변경하면 된다. 부속파일과 리소스가 있기 때문에 처음부터 프로젝트를 만드는 것은 비효율적이다. 앞으로도 새로 프로젝트를 만들 때는 이 방법대로 이전 프로젝트 폴더를 복사하여 새 프로젝트를 만들 것이다.

프로젝트 폴더를 복사한 후에는 무조건 빌드/솔루션 다시 빌드 항목을 선택하여 완전 빌드를 한 번 하는 것이 좋다. 아니면 Debug, Release 폴더를 삭제해도 된다. 이전 폴더의 임시적인 설정상태가 아직 남아 있을 수 있으므로 빌드를 다시 하여 원본 폴더와의 관계를 완전히 끊어야 한다. 특히 비주얼 C++ 7.0의 경우 편집하던 파일의 경로를 절대경로로 가지고 있기 때문에 문제점이 있다.

파일 입출력은 편집기가 임의로 할 수 없으며 반드시 사용자의 명시적인 명령, 또는 자동 백업 등의 암시적인 동의가 있을 때만 수행해야 한다. 파일은 사용자의 소유이므로 허가없이 편집기가 파일 내용을 바꾸어서는 안되는 것이다. 하지만 가끔 사용자들이 파일을 변경해놓고도 저장하지 않는 실수를 하는 경우가 있으므로 편집기는 이 사실을 사용자에게 알려야 한다. 편집기뿐만 아니라 파일을 만드는 모든 프로그램은 이 기능을 가지고 있는데 다음은 메모장의 경고 메시지이다.

저장하지 않고 종료하고자 할 때는 편집 내용을 정말 버릴 것인지 아니면 실수로 종료를 하는 것인지 확인할 필요가 있다. 그렇지 않으면 단순한 실수에 의해 몇 시간 동안 열심히 편집한 내용을 잃어버리게 될 것이고 이런 상황을 방치해서는 안된다. 편집기가 이 경고를 보여주려면 텍스트를 읽은 후 변경되었는지 아닌지를 항상 감시하고 있어야 하는데 이 정보를 변경플래그(Modification Flag)라고 한다.

ApiEdit.h의 클래스 선언부에 다음 변수를 추가한다. 이 변수가 변경플래그로 사용되며 변경 또는 안변경 둘 중 하나의 상태를 가지므로 BOOL형이면 된다.

 

class CApiEdit

{

     ....

     BOOL bModified;

 

이제 ApiEdit는 클래스가 되었으므로 필요한 모든 변수는 헤더 파일의 클래스 선언부에 이런 식으로 추가해야 한다. 최초 실행될 때는 문서가 전혀 편집되지 않은 상태이므로 OnCreate에서 이 플래그를 FALSE로 초기화한다.

 

BOOL CApiEdit::OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     ....

     bModified=FALSE;

 

최초 FALSE값을 가지다가 문서가 조금이라도 편집되면 TRUE로 바꿔 주는데 Insert Delete 함수에서만 문서를 변경할 수 있으므로 두 함수에서 이 변수값을 TRUE로 바꾸면 된다. CopyString이나 클립보드 조작 등 문서를 변경하는 모든 코드들은 이 두 함수를 거치도록 되어 있으므로 여기서만 플래그를 조작하면 된다.

 

void CApiEdit::Insert(int nPos, TCHAR *str)

{

     ....

     UpdateLineInfo(nPos,lstrlen(str));

     UpdateScrollInfo();

    SetModified(TRUE);

     ....

 

void CApiEdit::Delete(int nPos, int nCount)

{

     ....

     UpdateLineInfo(nPos,-nCount);

     UpdateScrollInfo();

    SetModified(TRUE);

     doclen-=nCount;

}

 

플래그를 관리하는 책임은 ApiEdit에게 있지만 이 플래그를 사용하는 것은 호스트 프로그램이다. 파일을 저장하거나 새 파일을 만들기 직전에 이 플래그의 상태를 보고 사용자에게 저장 여부를 질문해야 한다. ApiEdit는 플래그를 관리하고 호스트가 이 플래그를 읽고 쓸 수 있도록 함수만 제공하면 된다. 다음 두 멤버함수를 추가한다.

 

class CApiEdit

{

     ....

     BOOL GetModified() { return bModified; }

     void SetModified(BOOL aModi);

 

bModified 멤버변수를 바로 읽을 수도 있는데 왜 이런 식으로 함수를 제공해야 하는가 하면 bModified private 속성을 가지는 숨겨진 멤버이기 때문에 ApiEdit외부에서 함부로 값을 읽거나 변경할 수 없다. 그래서 안전하게 값을 읽고 쓸 수 있도록 하기 위해 멤버변수를 액세스하는 public 함수를 제공한다. OOP의 정보 은폐 기능을 십분 활용하기 위해 가급적이면 멤버변수를 숨기는 것이 바람직하다.

또한 멤버변수 변경시 추가적인 처리가 필요할 수도 있으므로 호스트가 직접 이 처리를 하는 것은 부적당하다. 예를 들어 줄간(LineRatio)을 바꾸면 화면을 다시 그려야 하고 탭 크기(TabSize)를 바꾸면 정렬을 다시 해야 하는 추가 처리가 필요하다. 그러므로 호스트에서 Ae.LineRatio=150 과 같이 바로 대입하도록 내 버려 두어서는 안된다. 이렇게 되면 줄간만 바뀌고 화면은 그대로이므로 짧은 순간이나마 불일치가 발생한다.

호스트는 멤버변수를 직접 액세스하지 않고 읽고 쓸 때 Get/Set 함수를 사용하도록 하면 프로그래밍 인터페이스를 일관적으로 유지할 수 있다. 위 예처럼 앞으로 호스트에서 멤버변수를 액세스할 필요가 있으면 그때마다 Get/Set 함수쌍을 만들 것이다. Get 함수는 보통 값을 단순히 리턴하는 정도의 코드밖에 없으므로 인라인으로 바로 작성하고 Set 함수는 CPP 파일에 코드를 작성하는 것이 관례적이다. 다음은 SetModified 함수이다.

 

void CApiEdit::SetModified(BOOL aModi)

{

     bModified=aModi;

}

 

인수로 전달된 aModi값으로 bModified 플래그를 변경한다. 지금은 멤버에 값을 대입하는 코드만 있지만 변경플래그의 변경시 필요한 동작이 있다면 앞으로 여기에 추가 코드가 들어가게 된다.