Programming

다이알로그에 상태표시줄 달기~

bcheul 2006. 12. 21. 16:57
다이얼로그에 있는 툴바와 상태 표시줄


--------------------------------------------------------------------------------


이 글을 ZEKSER Cyril님이 제공했고, 하이텔 소프트웨어 동호회 번역 소모임 김소영님이 번역했습니다.

디폴트로, MFC는 CFrameWnd를 서브 클래스로 하는 객체에는 툴바와 상태표시줄을 추가할 수 있도록 허용하고 있다. 그렇지만, 만일 여러분이 다이얼로그에 툴바를 추가하기 원한다면, 여러분은 그것이 쉬운문제가 아님을 깨닫게 될것이다.

첫번째 문제는, 모든 핸들러 함수는 부모가 CFrameWnd를 서브클래스로 하는 객체가 되어야 한다고 예상하는 것이고, 다른 하나는 다이얼로그가 IDLE상태일때만 툴팁이나 상황 표시줄 메시지를 나타낼 수 있다는것 이다. 실제로, 상태 표시줄이 적당한 제 기능을 하기 위해서는, 프레임 윈도우가 사용하는 메시지를 포착해야 할 것이다.

나는 도움이 될만한 자료를 구하기 위해서 많은 문서를 탐독했고, Mihai Filimon (1998년 3월 16일 월요일에 게시된 자료) 로부터 아이디어를 얻게 되었다. 그것은 훌륭하면서도 매우 단순한것으로, 비시각적인 static 객체를 통해서 상태 표시줄을 위한 방 (room) 을 만드는 것이다. (긴 어둠을 터널을 해메고 있는 나에게 한줄기 광명한 빛을 주신 Mihai Filimon 님에게 감사드린다.)

여러분이, 어플리케이션에서 한개의 툴바와 (고정된것, 가능한 다른 방법이 없다), 상태 표시줄과 통신할 수 있는 메뉴를 원할때, 어떻게 하는지 나에게 알려주었으면 좋겠다. (비록 다이얼로그에서 이루어지는 것 이라고 할지라도 나는 이것이 하나의 주제가 될 수 있다고 생각한다).

나는, 여러분이 스스로 다이얼로그를 정의하고, 그 다이얼로그를 리소스 에디터에 추가한 후에, 툴바에 다이알로그를 어떻게 추가하는지 만을 설명할것이다.

일단, 여러분은뭠l 툴바에 들어있지 않은 여러분의 다이알로그를 개발하고, 이 다이알로그 템플릿을 사용하기 위해서 CDialog를 서브클래싱해야만 한다 (새로울것은 아무것도 없다).

그다음, CDialog::OnInitDialog 함수에 다음 코드를 추가한다 (m_wndToolBar 변수는 CToolBarEX 형이다) :

BOOL CMyDlg::OnInitDialog()
{
       // TODO: 나머지 초기화부분을 이곳에 추가한다
       CDialog::OnInitDialog();
       // ToolBar를 추가한다.
       if (!m_wndToolBar.Create( this ) ||
               !m_wndToolBar.LoadToolBar(IDR_CORPS_EMIS) )
       {
               TRACE0("Failed to create toolbar\n");
               return -1;      // fail to create
       }
//  TODO: 만일 툴팁이나 크기 조절이 가능한 툴바를 원하지 않는다면 이 부분을 제거하라
       m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
               CBRS_TOOLTIPS | CBRS_FLYBY  );
// 컨트롤 바를 위한 방 (room) 을 만들기 위해서는 다이얼로그의 크기를 조정할 필요가 있다.
       // 일단, 컨트롤 바의 크기가 얼마나 큰지 조사한다.
       CRect rcClientStart;
       CRect rcClientNow;
       GetClientRect(rcClientStart);
       RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST,
                                  0, reposQuery, rcClientNow);
       
// 아무런 컨트롤 바가 없는
        // 나머지 클라이언트 영역안의 동일한 상대적 위치에
// 모든 컨트롤을 배치한다.
       CPoint ptOffset(rcClientNow.left - rcClientStart.left,
                                       rcClientNow.top - rcClientStart.top);
       CRect  rcChild;                                
       CWnd* pwndChild = GetWindow(GW_CHILD);
       while (pwndChild)
       {                              
               pwndChild->GetWindowRect(rcChild);
               ScreenToClient(rcChild);
               rcChild.OffsetRect(ptOffset);
               pwndChild->MoveWindow(rcChild, FALSE);
               pwndChild = pwndChild->GetNextWindow();
       }
       // Adjust the dialog window dimensions
       // 다이알로그 윈도우 차원을 조정한다
       CRect rcWindow;
       GetWindowRect(rcWindow);
       rcWindow.right += rcClientStart.Width() - rcClientNow.Width();
       rcWindow.bottom += rcClientStart.Height() - rcClientNow.Height();
       MoveWindow(rcWindow, FALSE);
       
       // And position the control bars
               // 그리고 컨트롤 바의 위치를 조정한다.
       RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
       return TRUE;  // 컨트롤로 초점을 설정하지 않는한 참을 리턴한다.
                     // 예외: OCX 속성 페이지는 거짓을 리턴한다.
}
위와같이 명백하지가 않다는것을 알 수 있다 (나는 DLGCBR32라고 불리는 좀더 복잡한 예제에서 그것을 발견했고, 나는 필수 함수와 아닌 함수를 조사하기 위해서 많이 노력해야 했다.)

이 시점에서, 여러분은 다이알로그 꼭대기에 오로지 툴바만을 가지고 있다 (다이알로그에서 보이도록 하는 요청을 감당할 수 있는 툴바이다.). 여러분의 다이알로그에 많은 툴바를 가질수는 있지만, 여러분 스스로 그 툴바의 위치를 관리해야만 한다.

이제, 여러분이 툴바에서 툴팁을 원한다면, 여러분은 다음과 같은 방법으로, TTN_NEEDTEXTA 와 TTN_NEEDTEXTW (안시와 유니코드 문자 셋을 위해서) 메시지를 처리해야만 한다:

1. 메시지 핸들러 선언을 추가한다...
BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
       ...
       ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
       ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
END_MESSAGE_MAP()
2. 여러분의 헤더파일에 함수를 선언한다 :
// 생성된 메시지 맵 함수
//{{AFX_MSG(CMyDlg)
afx_msg BOOL OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult);
//}}AFX_MSG
3. 그리고 마지막으로 OnToolTipText 함수를 코딩한다 :
BOOL CMyDlg::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
       ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);
       // allow top level routing frame to handle the message
       if (GetRoutingFrame() != NULL)
               return FALSE;
       // ANSI 와 UNICODE 버전의 메시지를 각각 처리한다
       TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
       TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
       TCHAR szFullText[256];
       CString cstTipText;
       CString cstStatusText;
       UINT nID = pNMHDR->idFrom;
       if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
               pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
       {
               // idFrom 은 실제로는 툴의 HWND 이다
               nID = ((UINT)(WORD)::GetDlgCtrlID((HWND)nID));
       }
       if (nID != 0) // 구분 기호 (seperator)에서 0이 될것이다
       {
               AfxLoadString(nID, szFullText);
                       // 이것은 버튼의 인덱스가 아니라, 커맨드의 id 이다.
               AfxExtractSubString(cstTipText, szFullText, 1, "\n");
               AfxExtractSubString(cstStatusText, szFullText, 0, "\n");
       }
       // Non-UNICODE 문자열은 툴팁 윈도우에서만 볼 수 있다...
       if (pNMHDR->code == TTN_NEEDTEXTA)
               lstrcpyn(pTTTA->szText, cstTipText,
           (sizeof(pTTTA->szText)/sizeof(pTTTA->szText[0])));
       else
               _mbstowcsz(pTTTW->szText, cstTipText,
           (sizeof(pTTTW->szText)/sizeof(pTTTW->szText[0])));
       *pResult = 0;
       // 팝업 윈도우앞에 툴팁 윈도우를 가져온다
       ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
               SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE);
       return TRUE;    // 메시지가 처리되었다
}
이제 여러분은 툴팁이 있는 툴바를 가지게 되었다 (툴팁 박스에 있는 텍스트는 리소스 에디터를 이용한, 고전적인 방법으로 정의되었다.).

여러분이 메인프레임의 상태 표시줄이 텍스트를 표시하기 원한다면, return 문 이전에 추가해야 하는 유일한 코드 라인이 위의 함수에 있다 (m_wndStatusBar를 public으로 가정한다) :

// 메인 프레임의 상태 표시줄에 텍스트를 나타낸다 (Help 판의 텍스트가 인텍스 0
// 라고 가정한다.
      ((CMainFrame*)GetParent())->m_wndStatusBar.SetPaneText(0, cstStatusText);
여기까지 이다.

이제, 여러분이 메인 프레임의 (여러분이 이미 만들었던 다른 상황 표시줄) 상황 표시줄에 텍스트를 표시하는 경우에 대해서 더 말하고자 한다. 여러분은 WM_MENUSELECT를 위한 핸들러에 다른 코드를 추가할 수 있다:

void CMyDlg::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu)
{
       CDialog::OnMenuSelect(nItemID, nFlags, hSysMenu);
       
       TCHAR szFullText[256];
       CString cstStatusText;
       // TODO: 이곳에 여러분의 메시지 핸들러 코드를 추가한다.
       // 메인 프레임의 상황 표시줄에 디스플레이한다.
       if (nItemID != 0) // will be zero on a separator
       {
               AfxLoadString(nItemID, szFullText);
                       // 이것은 버튼 인덱스가 아니라 커맨드 id이다.
               AfxExtractSubString(cstStatusText, szFullText, 0, "\n");
               ((CMainFrame*)GetParent())->m_wndStatusBar.SetPaneText(0,cstStatusText);
}
}

여러분은 이 툴바에서 ON_UPDATE_COMMAND_UI 메시지를 사용할 수 없는데, 그것은 아이들(idle) 상태일때만 리프레시(refresh)될 수 있기 때문이다. ON_UPDATE_COMMAND_UI 메시지를 사용하기 위해서는, CtoolBar(또는 CstatusBar) 클래스를 서브클래싱하고, WM_IDLEUPDATECMDUI를 위한 메시지 핸들러를 추가하고, 이 함수에 다음과 같은 코드를 넣는다:

/////////////////////////////////////////////////////////////////////////////
// CMyToolBar::OnIdleUpdateCmdUI
//      OnIdleUpdateCmdUI는 MFC 프레임웍에 있는 사용자-인터페이스 요소의 상황
//      을 갱신하기 위해서 사용되는, WM_IDLEUPDATECMDUI 메시지를 처리한다.
//
//      우리는 여기에 묘수가 필요하다: CtoolBar::OnUpdateCmdUI는 첫번째
//      파라미터로 CFrameWnd 포인터를 필요로 하지만, CcmdTarget 포인터를
//      필요로 하는 다른 함수로 파라미터를 전달하는것 외에는 아무일도 하지
//      않는다. 우리는 CCmdTarget인, 부모 윈도우로부터 CWnd 포인터를 얻을 수는
//      있지만, 그것은 CFrameWnd가 아니다. 그래서, CtoolBar::OnUpdateCmdUI를
//      충족시키기위해서, 우리는 임시로 CframeWnd 포인터 대신에 CWnd 포인터를
//      호출할 것이다.
LRESULT CMyToolBar::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM)
{
       if (IsWindowVisible())
       {
               CFrameWnd *pParent = (CFrameWnd *)GetParent();
               if (pParent)
                       OnUpdateCmdUI(pParent, (BOOL)wParam);
       }
       return 0L;
}

개정된 툴바를 가지려면, 여러분은 반드시 ContinueModal() 함수를 다음과 같은 방법으로 오러바이드해야만 한다:

BOOL CMyDlg::ContinueModal()
{
   m_wndToolbar.SendMessage( WM_IDLEUPDATECMDUI, WPARAM(TRUE), 0);
   return CDialog::ContinueModal();
}

AfxLoadString을 컴파일하기 위해서, 여러분은 CPP 파일의 처음에 "Afxpriv.h" 파일을 포함시켜야만 한다.