ApiEdit9까지 많은 기능을 작성해 왔는데 코드량이 이 정도 되면 여기 저기서 생각지도 못한 버그들이 나타나기 마련이다. 여기서는 그 동안 보류해두었던 버그들을 찾아 보고 좀 더 완벽한 컨트롤이 되기 위해 이 버그들을 힘 닿는 데까지 수정할 것이다. 애초에 이 버그들이 있다는 것을 알았지만 일부러 한곳에 모아서 수정하는 이유는 버그 발생 시점에서 이 내용을 다루기에는 실습이 너무 번거로웠고 순서를 맞추기가 어려웠기 때문이다.

프로젝트 중간에 버그를 만나는 상황은 자연스러운 것이므로 복습도 할 겸 어디에 어떤 이상이 있는지 살펴 보자. 실습을 직접 해 본 사람들은 아마 이 버그들을 이미 발견했을지도 모르겠지만 아마 대부분은 발견하지 못하고 넘어왔을 것이다. 버그들이 얼마나 꼭꼭 숨어 있는지 기가 막힌 버그도 많다.

. 드래그중의 문자입력

이 버그는 ApiEdit4에 블록선택 기능이 들어감으로써 자연스럽게 생긴 것인데 버그라기보다는 반드시 처리해야 할 예외를 처리하지 않은 결과라 할 수 있다. 버그의 내용은 아주 간단하다. 문자열이 잔뜩 입력되어 있는 상황에서 마우스로 블록을 선택하면서 키보드로 문자열을 입력할 수 있다는 것이다. , 블록선택중에 문자가 입력되어 DeleteSelection 함수가 선택중인 블록을 삭제하는 것인데 이렇게 하더라도 문서가 이상하게 변하거나 하지는 않는다.

사용자의 명령을 충실하게 실행한 결과이긴 하지만 블록선택중에 문자열 입력이라는 동작은 사용자의 의도된 편집이라기보다는 단순히 키를 잘못 눌렀을 확률이 거의 99.99%일 정도로 확실하다. 이런 실수에 대해 선택중인 블록을 삭제한다는 것은 참 미련한 짓인 것 같다. 그래서 블록선택중일 때는 문자열 입력을 금지하도록 할 필요가 있다. 블록선택중일 때뿐만 아니라 선택된 블록을 마우스로 드래그해서 옮기고 있을 때도 마찬가지로 키입력을 금지해야 한다.

방법은 아주 간단하다. 드래그중일 때, bCapture TRUE일 때는 문서를 일시적인 읽기전용 상태로 바꿔버리면 된다. 즉 드래그중일 때는 bReadOnly TRUE인 상태와 동일한 처리가 필요한 것이다. OnChar, OnImeCompostion, VK_DELETE, VK_BACK, IDM_AE_CUT, IDM_AE_PASTE 에는 읽기전용 상태일 때 리턴하는 코드가 있는데 bReadOnly 조건에 bCapture 조건을 하나 더 붙여주면 된다. 예를 들어 OnChar의 경우 다음과 같이 수정된다.

 

void OnChar(HWND hWnd, TCHAR ch, int cRepeat)

{

     ....

     if (bReadOnly || bCapture)

          return;

 

커서가 캡처되었을 때는 읽기전용과 마찬가지로 문자입력을 거부한다. 클립보드 조작 코드도 거의 비슷하다.

 

void OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify)

{

     ....

     case IDM_AE_CUT:

          if (SelStart != SelEnd && bReadOnly==FALSE && bCapture==FALSE) {

 

캡처된 상태가 아닐 때만 잘라내기를 할 수 있다. bReadOnly를 참고하는 모든 곳에 bCapture 조건을 같이 넣기만 하면 된다. , Insert Delete는 굳이 bCapture 조건을 추가하지 않아도 상관없다. 왜냐하면 사용자가 문서를 변경할 수 있는 모든 곳, Insert, Delete를 호출하는 곳을 다 막아 버렸기 때문이다. 그러면 이제 실행해보고 드래그중에 문자입력이 잘 거부되는지 확인해보자.

이제 드래그 중에는 어떤 키를 누르더라도 문자열 입력이 거부된다. 하지만 아직 문제가 남아있다. 한글 모드에서 드래그중일 때는 일단은 입력이 되지 않지만 IME가 조립 문자열을 계속 조립하고 있다가 드래그가 끝난 후에 조립 문자열을 삽입한다. 드래그 중에 한글을 입력하고 블록선택이 끝난 후 한 칸 앞으로 이동해보면 이때 OnImeComposition이 호출되고 드래그중에 조립해둔 문자열이 블록 자리에 대신 출력된다. ApiEdit는 문자입력을 거부하지만 IME는 그렇지가 못한 것이다.

이 문제를 해결하려면 드래그중일 때는 IME에게 아예 한글조립을 하지 못하도록 막아야 한다. OnImeComposition을 막는다고 해서 IME의 한글조립을 금지할 수는 없다. 왜냐하면 WM_IME_COMPOSITION 메시지는 한글을 조립하겠다는 메시지가 아니라 이미 조립 완료했다는 메시지이기 때문이다. 이 메시지의 처리 여부와는 상관없이 IME는 항상 입력된 키값으로 한글을 조립하고 있다.

IME가 한글을 조립하는 시점은 메인 메시지 루프에서 TranslateMessage 함수가 호출될 때이다. 이 함수는 입력된 키와 입력모드 등을 종합 판단하여 한글 모드일 경우 IME 윈도우에게 입력된 문자를 전달해 줌으로써 조립을 지시한다. , IME의 한글조립은 WndProc외부에서 발생하기 때문에 WndProc에서 어떻게 하더라도 조립은 계속 하고 있는 것이다. 캡처 상태일 때 TranslateMessage 함수를 호출하지 않으면 이 문제를 해결할 수 있다. WinMain의 메시지 루프를 다음과 같이 수정해보자.

 

     while(GetMessage(&Message,0,0,0)) {

        if (!bCapture)

              TranslateMessage(&Message);

          DispatchMessage(&Message);

     }

     return (int)Message.wParam;

}

 

이렇게 수정한 후 다시 테스트해보면 과연 아무 문제없이 한글조립이 금지된다. TranslateMessage를 호출하지 않으면 어떤 키를 누르더라도 IME윈도우가 입력된 문자를 전달받지 못하므로 한글조립이 안되는 것이다. 그러나 이 방법은 구조적인 이유로 쓸 수 없다.

왜냐하면 ApiEdit는 잠시 후면 독립된 컨트롤이 되어야 하며 컨트롤은 메인 메시지 루프를 함부로 조작할 수 있는 권한이 없기 때문이다. 메시지 루프는 윈도우당 하나씩 할당되는 것이 아니라 스레드당 하나씩 할당되는데 컨트롤이 자신의 동작을 위해 스레드에 소속된 모든 윈도우가 공유하는 메시지 루프를 함부로 수정할 수는 없다. 만약 이렇게 된다면 ApiEdit 컨트롤을 사용하는 호스트의 메시지 루프는 항상 저런 모양이어야 하는데 이 얼마나 부담스러운 일인가?

만약 ApiEdit MFC로 작성되고 있다면 이 방법을 쓸 수 있다. MFC의 프레임 워크는 메시지를 변환하기 전에 스레드 내의 모든 윈도우에 대해 PreTranslateMessage 가상함수를 호출하기 때문이다. 이 함수에서 FALSE를 리턴하면 이 메시지는 무시되므로 캡처 상태일 때 FALSE를 리턴하면 된다. 아쉽게도 ApiEdit MFC의 이런 서비스를 받을 수 없으므로 다른 방법을 사용해야 한다.

TranslateMessage 호출을 금지하는 코드는 곤란하며 ApiEdit 내부에서 이 문제를 해결해야 한다. 내부에서 이 문제를 해결하는 방법에도 여러 가지가 있다. 그 중 하나는 캡처 모드로 들어가기 전에 IME 모드를 살짝 영문 모드로 바꿔 놓는 것이다. 이러면 OnChar에서 문자입력을 거부하고 있으므로 영문자는 입력이 안될 것이다. 물론 캡처 모드가 풀릴 때 원래 모드대로 바꿔야 한다. 가능한 방법이기는 하지만 OldImeMode라는 전역변수가 하나 더 필요한 것이 마음에 안 들고 왠지 꽁수같아 보인다.

ApiEdit가 선택한 방법은 마우스 캡처가 풀릴 때 조립중인 문자열을 삭제하는 방법이다. OnLButtonUp에 다음 코드를 작성하면 된다.

 

void OnLButtonUp(HWND hWnd, int x, int y, UINT keyFlags)

{

     BOOL bControl;

     int SelLen;

     int toff;

    HIMC hImc;

 

     bCapture=FALSE;

     bSelLine=FALSE;

     ReleaseCapture();

     KillTimer(hWnd,1);

    hImc=ImmGetContext(hWnd);

    ImmNotifyIME(hImc,NI_COMPOSITIONSTR,CPS_CANCEL,0);

    ImmReleaseContext(hWnd,hImc );

 

     bControl=((GetKeyState(VK_CONTROL) & 0x8000) != 0);

     ....

 

bCapture FALSE가 될 때 IME에게 드래그중에 조립한 문자열을 삭제하도록 명령하였다. 드래그중에 한글을 조립하는 것을 막는 것이 아니라 드래그가 끝난 후 조립된 문자열을 취소하는 것이다. 이제 모든 문제가 해결되었다.