이 절에서는 복수 개의 파일을 대상으로 찾기와 바꾸기를 할 수 있는 기능을 만들어 볼 것이다. 이 기능은 지금까지 만들어 왔던 찾기/바꾸기 기능과 비슷한 코드를 공유하지만 근본적으로 수준이 다른 기능이다. 텍스트 편집기는 텍스트를 편집하는 것이 주된 목적이며 편집 대상 텍스트를 얻기 위해 파일 입출력을 다룰 뿐이다. 즉 취급하는 주 대상이 텍스트이지 파일이 아니며 폴더나 드라이브는 더욱 더 아니다. 그래서 파일검색 기능은 텍스트 편집기의 기본 기능이라 할 수 없으며 오히려 쉘이나 파일 관리 프로그램이 제공해야 할 기능이다.

이 기능과 지금까지 만들어왔던 기능과의 또 다른 큰 차이점은 ApiEdit와는 전혀 상관이 없다는 점이다. ApiEdit 컨트롤은 파일이라는 존재 자체를 아예 모르므로 파일검색에는 일체 관여하지 않으며 관여할 수도 없다. 파일검색 기능은 호스트 프로그램의 고유 기능이며 모든 코드는 호스트에 작성될 것이다. 이 장의 끝까지 ApiEdit는 전혀 수정되지 않는다. 이 절의 내용은 이전의 실습과 별 상관이 없으며 이후의 실습과도 논리적인 연관이 없으므로 읽기 싫은 사람은 건너 뛰어도 좋다.

. 검색결과창

파일검색은 디스크에서 조건에 맞는 파일들을 모두 읽어서 특정 문자열을 찾아 보여준다. 이미 열린 문서를 검색 대상으로 하는 것이 아니므로 문서에서의 검색처럼 선택 블록으로 검색결과를 보여줄 수 없으며 검색결과가 복수 개이므로 표 형식으로 보여주어야 한다. 파일검색 기능을 만들기 전에 먼저 검색된 결과를 보여줄 수 있는 결과창을 만들도록 하자. 아예 새로운 윈도우를 만들어야 하므로 WinMain에서 윈도우 클래스를 하나 더 등록한다.

 

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance

            ,LPSTR lpszCmdParam,int nCmdShow)

{

     ....

     WndClassEx.lpszClassName="DGOutput";

     WndClassEx.lpfnWndProc=(WNDPROC)DGOutputProc;

     WndClassEx.hIcon=NULL;

     WndClassEx.hIconSm=NULL;

     WndClassEx.cbWndExtra=0;

     RegisterClassEx(&WndClassEx);

     ....

 

윈도우 클래스 이름은 DGOutput으로 주었으며 아이콘도 필요없고 여분 메모리도 필요없다. 이 윈도우를 관리할 전역변수들을 선언한다.

 

HWND hOutput;

HWND hList;

int TotalFind;

TCHAR LastFIF[256];

 

검색 결과창의 핸들과 검색결과창에 포함된 리스트 뷰의 핸들, 그리고 총 검색 개수가 전역변수로 선언되었다. LastFIF 변수는 마지막 검색한 문자열이며 검색결과창에서 이 단어를 찾아갈 때 필요하다. 이 전역변수들은 실행중에 사용되는 것들이고 검색결과창의 상태를 관리할 두 개의 설정상태 변수가 필요하다. 이 변수들은 SOption에 이미 추가되어 있다.

 

struct SOption

{

     ....

     BOOL bShowOutput;

     int OutputHeight;

};

 

bShowOutput은 검색결과창이 보이는 상태인가 아닌가를 지정하며 OutputHeight는 검색결과창의 높이이다. 이 두 변수는 SOption::Init에서 다음과 같이 초기화한다.

 

void SOption::Init()

{

     ....

     bShowOutput=FALSE;

     OutputHeight=100;

}

 

검색 결과창은 최초 보이지 않는 상태이며 높이는 100픽셀이다. 이 두 값은 레지스트리에 영구적으로 저장되는 대상이며 SOption Load, Save 함수에서 저장 및 복구를 한다. 검색결과창은 OnCreate에서 생성한다. 다음 코드를 작성하도록 하자.

 

int OnCreate(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     ....

     hdc=GetDC(NULL);

     for (i=1;i<10;i++) {

          arCustomFont[i].lfHeight=arCustomFont[i].lfHeight*GetDeviceCaps(hdc,LOGPIXELSY)/72;

     }

     ReleaseDC(NULL,hdc);

 

    hOutput=CreateWindowEx(WS_EX_CLIENTEDGE,"DGOutput",NULL,WS_CHILD | WS_VISIBLE,

        0,0,0,0,hWnd,NULL,g_hInst,NULL);

 

bShowOutput 설정 옵션과는 상관없이 일단 윈도우를 만들어 놓고 보는데 이 윈도우는 별도의 함수에 의해 관리된다. 이제 MDI 프레임의 작업영역에는 MDI 클라이언트만 존재하는 것이 아니라 검색결과창이라는 별도의 창이 영역을 분할하게 되므로 WM_SIZE 메시지를 무조건 디폴트 처리해서는 안된다. 검색결과창이 보이는 상태일 때는 MDI 클라이언트를 재배치해야 한다. OnSize 함수를 추가하고 DGWndProc에서 이 함수를 호출한다.

 

LRESULT CALLBACK DGWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     switch(iMessage) {

          ....

        case WM_SIZE:if (OnSize(hWnd,wParam,lParam)) return 0;break;

     }

     return(DefFrameProc(hWnd,g_hMDIClient,iMessage,wParam,lParam));

}

 

BOOL OnSize(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     if (Option.bShowOutput==FALSE) {

          return FALSE;

     } else {

          Relayout();

          return TRUE;

     }

}

 

OnSize는 검색결과창이 숨겨진 상태일 때는 FALSE를 리턴하고 보이는 상태일 때는 Relayout 함수를 호출하여 작업영역을 분할한 후 TRUE를 리턴한다. OnSize FALSE를 리턴하면 반드시 DefFrameProc으로 이 메시지를 전달해 디폴트 처리를 해야 한다. 검색결과창이 보이는 상태이면 MDI 클라이언트와 검색결과창이 작업영역을 적당히 나누어 가져야 하는데 이 배치는 Relayout 함수가 한다.

 

void Relayout()

{

     RECT crt;

     int tOutputHeight;

 

     GetClientRect(g_hFrameWnd,&crt);

 

     if (Option.bShowOutput) {

          tOutputHeight=Option.OutputHeight;

          MoveWindow(hOutput,0,crt.bottom-tOutputHeight+2,

              crt.right,tOutputHeight-2,TRUE);

          ShowWindow(hOutput,SW_SHOW);

     } else {

          tOutputHeight=0;

          ShowWindow(hOutput,SW_HIDE);

     }

 

     MoveWindow(g_hMDIClient,0,0,crt.right,crt.bottom-tOutputHeight,TRUE);

}

 

bShowOutput TRUE이면 OutputHeight만큼 아래쪽에 검색결과창을 배치하고 그 나머지 영역을 MDI 클라이언트가 차지하도록 하였다. 검색결과창도 윈도우이므로 전달되는 메시지를 처리할 윈도우 프로시저가 필요하다. 다음 윈도우 프로시저를 작성한다.

 

LRESULT CALLBACK DGOutputProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

     LVCOLUMN COL;

 

     switch(iMessage) {

     case WM_CREATE:

          hList=CreateWindow(WC_LISTVIEW,NULL,WS_CHILD | WS_VISIBLE |

              LVS_REPORT | LVS_SHOWSELALWAYS,

              0,0,0,0,hWnd,NULL,g_hInst,NULL);

          ListView_SetExtendedListViewStyle(hList,LVS_EX_FULLROWSELECT);

 

          COL.mask=LVCF_FMT | LVCF_WIDTH | LVCF_TEXT |LVCF_SUBITEM;

          COL.fmt=LVCFMT_LEFT;

          COL.cx=40;

          COL.pszText="번호";

          COL.iSubItem=0;

          ListView_InsertColumn(hList,0,&COL);

 

          COL.cx=200;

          COL.pszText="위치";

          COL.iSubItem=1;

          ListView_InsertColumn(hList,1,&COL);

 

          COL.cx=500;

          COL.pszText="내용";

          COL.iSubItem=2;

          ListView_InsertColumn(hList,2,&COL);

          return 0;

     case WM_SIZE:

          MoveWindow(hList,0,0,LOWORD(lParam),HIWORD(lParam),TRUE);

          return 0;

     }

     return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

WM_CREATE에서 세 개의 헤더 컬럼을 가지는 리스트 뷰 컨트롤을 생성하고 WM_SIZE에서 이 컨트롤이 항상 작업영역을 가득 채우도록 하였다. 리스트 뷰에 검색결과가 출력되는데 이 컨트롤은 파일검색 루틴에서 수시로 참조하므로 hList 전역변수에 그 핸들을 저장하였다. 리스트 뷰 컨트롤만 만들었을 뿐 아직 별다른 코드는 없다.

보기 메뉴에서 검색결과 항목을 선택하면 검색결과창을 토글한다. 즉 보이는 상태이면 숨기고 숨겨져 있는 상태이면 보이도록 만든다. OnCommand에 다음 코드를 작성한다.

 

void OnCommand(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     ....

     case IDM_VIEW_OUTPUT:

          Option.bShowOutput = !Option.bShowOutput;

          Relayout();

          break;

 

OnInitMenu에서는 bShowOutput 변수값을 메뉴 상태에 반영시킨다.

 

void OnInitMenu(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     HMENU hMenu;

     HWND hActive;

     SInfo *pSi;

     int s,e;

 

     hMenu=(HMENU)wParam;

     hActive=(HWND)SendMessage(g_hMDIClient,WM_MDIGETACTIVE,0,NULL);

     if (hActive) {

     ....

     }

 

    if (Option.bShowOutput) {

        CheckMenuItem(hMenu, IDM_VIEW_OUTPUT, MF_BYCOMMAND | MF_CHECKED);

    } else {

        CheckMenuItem(hMenu, IDM_VIEW_OUTPUT, MF_BYCOMMAND | MF_UNCHECKED);

    }

}

 

검색 결과창 보기 메뉴는 활성창이 없을 때도 나타나므로 if (hActive) 블록 바깥에 코드를 작성해야 한다. 여기까지 작성하고 실행한 후 보기/검색 결과 항목을 선택하면 썰렁한 모양의 검색결과창이 나타날 것이다.

아직은 검색결과창에 아무것도 보이지 않지만 파일검색을 하면 리스트 뷰에 어떤 파일의 몇 번째 줄에서 요렇게 생긴 문장을 찾았다는 보고서가 출력될 것이다. 그리고 검색결과를 더블클릭하면 그 파일을 즉시 열어주는 기능과 기타 검색결과를 관리하는 기능들이 작성될 것이다.