다이알로그(Dialog)에 툴바와 상태바 삽입하기
C++ 2007. 10. 27. 02:18 |(펌) - http://blog.naver.com/stkov?Redirect=Log&logNo=90014489284
기본적으로, MFC는 CFrameWnd 클래스를 상속한 객체만 툴바와 상태바를 추가할 수 있도록 허용한다. 이 기능이 좋긴 하나 만약 툴바와 상태바를 다이알로그에 넣으려고 한다면, 그것을 쉽게 할 수 없기 때문에 바보 같다는 기분을 느낄 것이다.
나는 이 일을 하는 데 도움이 될 만한 몇몇 문서를 찾아서 분석해 봐야 했다. Mihai Filimon ( 1998년 3월 16일 월요일 기사)이 내게 보여준 기사는 좋았으나, 바를 어떤 정적 객체와 함께 삽입해야하기 때문에 깔끔하지는 않았다. (나는 내가 터널의 끝을 본 데에 대해서 그를 매우 고맙게 생각한다.)
네가 만약 프로그램 상의 툴바(도킹된 상태로. 다른 방법이 없다)와 상태바와 통신이 가능한 메뉴를 넣는 방법을 설명할 것이다.
나는 툴바를 다이알로그에 넣는 방법만을 설명할 것이다. (메뉴를 넣는 것은 리소스 편집기로 메뉴를 정의하기만 하면 되는 뻔한 작업이기 때문이다)
첫째로, 툴바의 위치를 방해하지 않도록 다이알로그를 만들고 CDialog 클래스를 Dialog template으로 상속받는다. (지금까지는 별다른 것이 없다)
그리고 CDialog::OnInitDialog 함수에 다음의 코드를 넣는다. (단, m_wndToolBar는 CToolBarEx 타입):
BOOL CMyDlg::OnInitDialog()
{
// TODO: Add extra initialization here
CDialog::OnInitDialog();
// 툴바를 삽입한다.
if (!m_wndToolBar.Create( this ) ||
!m_wndToolBar.LoadToolBar(IDR_CORPS_EMIS) )
{
TRACE0("Failed to create toolbar\n");
return -1; // 생성 실패
}
// TODO: 사이즈를 조절 할 수 있고 툴팁이 표시되는 툴바를 원하지 않으면 이 코드를 삭제할 것.
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY );
// 컨트롤 바를 넣기 위해서 다이알로그의 크기를 조절할 필요가 있다.
// 첫째로, 컨트롤 바가 얼마나 큰지 알아보자.
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();
}
// 대화창의 크기와 위치를 조절한다
CRect rcWindow;
GetWindowRect(rcWindow);
rcWindow.right += rcClientStart.Width() - rcClientNow.Width();
rcWindow.bottom += rcClientStart.Height() - rcClientNow.Height();
MoveWindow(rcWindow, FALSE);
// 컨트롤 바를 옮긴다
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
여기서 뻔하지 않은 것들을 볼 수 있다. (나는 이것을 DLGCBR32라는 좀 더 복잡한 예제에서 찾았고, 반드시 삽입되어야 하는 함수들을 찾아내느라 고생했다.)
여기서, 툴바는 다이알로그의 맨 위쪽에만 삽입되게 된다. (위의 코드가 충분히 일반적이기 때문에, 위 대화창에 보여진 요구 사항들이 어떤 툴바에서도 충족하는 것을 알 수 있다). 다이알로그에 여러 개의 툴바를 넣을 수도 있지만, 그 위치는 알아서 조절해야 한다.
이제 툴바에 툴팁을 표시하고 싶다면, TTN_NEEDTEXTA과 TTN_NEEDTEXTW (ANSI와 유니코드 모두를 위함) 메시지를 다음의 방법으로 처리해야 한다.
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. 헤더 파일에 함수를 다음과 같이 정의한다.
// Generated message map functions//{{AFX_MSG(CMyDlg)afx_msg BOOL OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult);//}}AFX_MSG
3. 마지막으로 OnToolTipText 함수를 다음과 같이 코딩한다
(MFC 예제에서 퍼옴) :
BOOL CMyDlg::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult){ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);// 메시지를 처리하기 위해 최상위 계층의 routing frame을 허용 (?)if (GetRoutingFrame() != NULL) return FALSE;// ANSI와 유니코드 메시지를 모두 처리한다.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은 사실 툴바의 핸들이다.nID = ((UINT)(WORD)::GetDlgCtrlID((HWND)nID));}if (nID != 0) // 구분자에서는 0.
{ AfxLoadString(nID, szFullText);// 이건 button index가 아니라 command ID.
AfxExtractSubString(cstTipText, szFullText, 1, '\n');AfxExtractSubString(cstStatusText, szFullText, 0, '\n'); }// 툴팁 윈도우에는 ANSI만 표시 가능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; // 메시지가 처리됨
}
이제 툴바에 툴팁이 삽입된 것을 볼 수 있을 것이다. (툴팁에 보여지는 텍스트는 리소스 에디터를 쓰는 고전적인 방법으로 정의할 수 있다. 휴~ ;-) )
만약 메인프레임의 상태바에 텍스트를 보여주고 싶다면 단 한 줄의 코드를 위의 함수에 리턴하기 바로 전에 넣으면 된다: (단, m_wndStatusBar가 public인 경우에만)
// 메인프레임의 상태바에 텍스트를 표시한다 (단, 도움말 표시부가 인덱스 0에 있을 경우)
((CMainFrame*)GetParent())->m_wndStatusBar.SetPaneText(0, cstStatusText);
이것이 오늘 할 말의 끝이다.
아, 한 가지만 더 말하도록 하겠다. 만약 다이알로그 메뉴가 메인프레임의 상태바에 텍스트를 표시하게 하고 싶다면 (다른 상태바라도 됨) WM_MENUSELECT (window-type 핸들러)의 핸들러 함수에 다음의 코드를 넣으면 된다:
void CMyDlg::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu) {CDialog::OnMenuSelect(nItemID, nFlags, hSysMenu);TCHAR szFullText[256];CString cstStatusText;// TODO: Add your message handler code here// 메인프레임의 상태바 창에 출력.if (nItemID != 0)// 구분자에서는 반드시 0.{AfxLoadString(nItemID, szFullText);// 이것은 button index가 아닌 command id.AfxExtractSubString(cstStatusText, szFullText, 0, '\n');
((CMainFrame*)GetParent())->m_wndStatusBar.SetPaneText(0,cstStatusText);
}}
다이알로그가 IDLE 상태일 때에만 새로 고쳐지기 때문에 이 툴바에는 ON_UPDATE_COMMAND
_UI 메시지를 쓸 수 없다는 것에 유의하자. 만약 그것을 하고 싶다면, CToolBar (또는 CStatus
Bar) 클래스를 상속받아서 - CMyToolBar는 예를 든 것이다 - WM_IDLEUPDATECMDUI 핸들러를
추가하고, 다음의 코드를 그 함수에 넣자:
////////////////////////////////////////////////////////////////////////
// CMyToolBar::OnIdleUpdateCmdUI
// OnIdleUpdateCmdUI는 WM_IDLEUPDATECMDUI 메시지를 처리하는데, 그것은 MFC 프레임
// 워크 안에서 UI 요소의 상태를 업데이트하는 데 쓰인다. (?)
// 여기서 나는 약간의 트릭을 썼다: CToolBar::OnUpdateCmdUI는 첫 번째 인수로 CFrame
// Wnd 포인터를 요구한다. 하지만, 그 함수는 아무것도 하지 않고 그 인수를 CCmdTarget
// 포인터를 요구하는 다른 함수에 넘긴다. 우리는 CFrameWnd가 아닌 CCmdTarget인 CWnd
// 포인터를 부모 윈도우에서 받을 수 있다.
// 그렇다면, CToolBar::OnUpdateCmdUI를 기쁘게 만들기 위해서 여기서 CWnd 포인터를
// 잠깐 CFrameWnd로 바꾸기로 하자.
LRESULT CMyToolBar::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM)
{
if (IsWindowVisible())
{
CFrameWnd *pParent = (CFrameWnd *)GetParent();
if (pParent)
OnUpdateCmdUI(pParent, (BOOL)wParam);
}
return 0L;
}
AfxLoadString 때문에, "Afxpriv.h"를 CPP 파일 맨 위에 포함(Include)시켜야 컴파일 할
수 있다.