Programming/MFC2009. 1. 12. 13:32

1. Introduction

MFC를 사용해서 트레이 기반으로 동작하는 다이알로그 베이스드의 프로그램을 개발해 본 사람이라면 누구나 한번쯤은 이런 생각을 해 보았을것이다. 이놈의 모달 왜 시작하기만 하면 나타나지? ShowWindow API를 사용해서 SW_HIDE를 몇 군데 넣어보아도 뾰족한 수가 없었을 것이다. 최대한 노력을 해 보았자, 화면에 나타났다 금새 사라지는게 전부다.

그럼 왜 이런 현상이 나타날까? 그건 MFC 내부적으로 DoModal안에서 다이알로그를 Show하게 만들기 때문이다. 그렇다면 방법은 없을까? 불행하게도 일반적인 ShowWindow를 사용한 방법은 없다. 이 문서에서는 문제를 해결하는 다른 방법을 제시하고 있다. 그럼 2장에서 좀 더 자세히 살펴보기로 하자.

2. HOWTO

이미 1장에서 ShowWindow를 통한 답이 없다고 밝혔다. 그렇다면 도대체 무엇을 사용해야 깔끔하게 다이알로그를 제어할 수 있을까? 그 답은 바로 WM_WINDOWPOSCHAINGING에 있다. 보기에도 상당히 긴 이름을 가진 메시지이다. 아마도 윈도우 위치가 변경될때 발생할 것 같은 느낌을 주지 않는가? 그렇게 생각했다면 정답이다.

MSDN에 따르면 WM_WINDOWPOSCHANGING 메시지는 윈도우의 사이즈, 포지션, 또는 Z-order 순서가 변경되는 경우 윈도우에 통보된다고 한다. 그렇다면 윈도우의 거의 모든 외부 변화에 이 메시지가 발생한다고 보면된다. 이 메시지에 대해서 좀 더 살펴보면 다음과 같다.

WM_WINDOWPOSCHANGING

    WPARAM wParam : 사용하지 않음;
    LPARAM lParam : WINDOWPOS 구조체 포인터

typedef struct {
    HWND hwnd;
    HWND hwndInsertAfter;
    int x;
    int y;
    int cx;
    int cy;
    UINT flags;
} WINDOWPOS;

위는 해당 메시지의 wParam과 lParam및 lParam에 사용되는 구조체의 설명을 나타낸 것이다. 오늘 우리의 문제를 해결하는데 가장 중요한 것은 이 WINDOWPOS 구조체다. 그 중에서도 flags라고 할 수 있다.

flags는 윈도우 포지션과 관련된 다양한 값들을 가질 수 있다고 한다. 일반적으로 아래와 같은 값들의 조합으로 구성된다.

  • SWP_DRAWFRAME - 프레임을 그린다.
  • SWP_FRAMECHANGED
  • SWP_HIDEWINDOW - 윈도우를 숨긴다.
  • SWP_NOACTIVATE - 윈도우를 활성화 시키지 않는다.
  • SWP_NOCOPYBITS - 클라이언트 영역과 관련된 모든 정보를 무시한다.
  • SWP_NOMOVE - 현재 위치를 유지한다. (x,y 파라미터를 무시한다.)
  • SWP_NOOWNERZORDER - 소유주 윈도우의 Z-order를 변경하지 않는다.
  • SWP_NOREDRAW - 윈도우를 새로 그리지 않는다. 어떠한 페인팅 관련 메시지도 포스트 되지 않는다.
  • SWP_NOREPOSITION - SW_NOOWNERZORDER 플래그와 같음
  • SWP_NOSENDCHANGING - 윈도우가 WM_WINDOWPOSCHANGING 메시지를 받는 것을 막는다.
  • SWP_NOSIZE - 사이즈를 변경하지 않는다. (cx, cy 파라미터를 무시한다.)
  • SWP_NOZORDER - Z-order를 변경하지 않는다.
  • SWP_SHOWWINDOW - 윈도우를 출력한다.
z-order
윈도우는 일반적으로 2차원 평면 모니터에 출력된다. 그래서 x,y축이 기본적으로 존재한다. 거기에 더해서 윈도우 시스템에서는 z-order라는 개념이 있다. 이것은 수학과 마찬가지로 x,y축 외에 z축을 기준으로 한 순서가 된다. 윈도우로 따지면 특정 윈도우 뒤에 무슨 윈도우가 있는지를 기술하는 것을 z-order라고 생각하면 된다.

여기서 특히나 중요한 것은 SWP_SHOWWINDOW다. 화면에 윈도우가 출력되려고 하면 분명 WM_WINDOWPOSCHANGING 메시지가 포스팅 될 것이다. 당연히 넘어온 WINDOWPOS 구조체의 flags 필드값에는 SWP_SHOWWINDOW가 설정되어 있을 것이다. 그렇다면 값을 살짝 제거해 버리면 어떻게 될까? 아마도 MFC의 다이알로그 베이스드 프로그램이라면 아래와 같은 코드가 될 것이다.

void CHidDlgDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
    CDialog::OnWindowPosChanging(lpwndpos);
    
    // TODO: Add your message handler code here
    lpwndpos->flags &= ~SWP_SHOWWINDOW;
}

위와 같이 해두고 프로그램을 실행하면, 다이알로그가 화면에 출력되지 않을 것이다. 프로세스 목록에서 해당 프로그램을 종료시키자. 이것이 우리가 적용할 모달 다이알로그를 간단하게 숨기는 방법이다. 이것들을 합쳐서 좀 더 우아하게 사용하는 방법을 다음 장에서 살펴보도록 하자.

참고
기본적으로 다이알로그를 클래스 위저드로 열게 되면, 메시지 상자에 WM_WINDOWPOSCHANGING 메시지가 없다. 클래스 위저드의 마지막 탭에 위치한 클래스 인포로 가셔서 메시지 필터를 윈도우로 변경한후에 보면 WM_WINDOWPOSCHANGING 메시지가 추가되어 있는 것을 확인할 수 있다.

3. Codes

2장에서 살펴본 내용의 의하면 결국 우리는 기존의 ShowWindow행동을 무시하고 독자적인 행동을 해야 한다는 것을 알 수 있다. 쉽게 생각할 수 있는 방법으로 간단하게 멤버변수등을 하나 만들고 그 값에 따라서 WM_WINDOWPOSCHANGING 핸들러에서 SWP_SHOWWINDOW 플래그를 설정또는 제거해 주면 쉽게 될 것 이다. 하지만 이렇게 인터페이스를 분리시킬때에는 꼭 기존의 행동이랑 유사하게 내지는 기존의 행동을 통해서 변경시켜 주는 것이 좋다. 그렇게 해야 추후에 보더라도 헷갈리지 않기 때문이다.

이러한 작업을 가장 완벽하게 할 수 있는 곳은 아마도 ShowWindow일 것이다. CWnd의 ShowWindow를 재정의해서 사용하는 것이다. 하지만 이 경우 ShowWindow가 CWnd에 virtual함수로 선언되어져 있지 않기때문에 다이알로그를 CWnd포인터로 사용하는 경우에는 동작하지 않을 수 있다. 그렇게 되면 오류는 나지 않지만 결과가 이상하게 되므로 다른 사람이 쓰게 될 때 오해를 줄 수 있다. 따라서 ShowWindow를 오버라이드 하지 않고, 그와 비슷한 이름의 ShowWindowEx를 만들어 그놈을 사용하기로 하자. 아래는 간단하게 작성해본 샘플 코드다.

void CHidDlgDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
    CDialog::OnWindowPosChanging(lpwndpos);
    
    // TODO: Add your message handler code here
    if(m_bShowFlag)
        lpwndpos->flags |= SWP_SHOWWINDOW;
    else
        lpwndpos->flags &= ~SWP_SHOWWINDOW;

}

BOOL CHidDlgDlg::ShowWindowEx(int nCmdShow)
{
    m_bShowFlag = (nCmdShow == SW_SHOW);
    return (GetSafeHwnd()) ? ShowWindow(nCmdShow) : TRUE;
}

m_bShowFlag는 BOOL형으로 선언된 멤버 변수다. 이제 다이알로그의 ShowWindowEx 함수를 DoModal전에 사용하면, DoModal이 실행되더라도 다이알로그가 화면에 표시되지 않을 것이다.

주의해서 보아야 할 점 중에 하나는 ShowWindowEx의 마지막 호출은 ShowWindow로 이루어진다는 점이다. 하지만 DoModal 이전에 다이알로그는 생성되지 않으므로 그 경우에 ShowWindow를 호출하면 오류가 난다. 왜냐하면 아직 만들어진 윈도우 핸들이 없기 때문이다. 따라서 이 경우는 ShowWindow를 호출하지 않고 그냥 리턴해야 한다는 점을 기억해야 한다.


Posted by skensita