<도큐먼트와 멀티 뷰간의 통신>

하나의 도큐먼트와 세 개의 뷰(View)가 있는데, 이  세 개의 뷰(하나의 뷰를 세 개가 나누어 씁니다)가 하나의 도큐먼트를 공유하면서 서로의 액션(action)을 감지하게 하고 싶습니다. 예를 들면 아웃룩 익스프레스 메일(Outlook Express Mail)처럼 메시지의 헤더가 나와있는 뷰(리스트 컨트롤 뷰)에서 하나의 헤더를 선택하면 그 헤더에 해당되는 내용을 다른 뷰(폼 뷰)로 출력하고 싶습니다. 이때 리스트 컨트롤 뷰는 어떤 방식으로 폼 뷰에 자신의 행동을 알려주는지요?

먼저 각자의 역할을 정확히 구분해 보겠습니다. 도큐먼트는 응용 프로그램이 가져야 하는 정보를 저장하는 역할을 하며 자신을 표현하는 뷰의 리스트를 관리합니다. 그리고 뷰는 자기가 보여줄 정보를 가지고 있는 도큐먼트에 대한 포인터를 가지고 있으며, 화면이 갱신될 때 자기가 표시해야할 정보를 그립니다. 보통 스몰토크에서 사용하는 MVC 구조 중 C에 해당하는 컨트롤은 MFC에서는 독립적으로 설계되어 있지 않습니다. 일반적으로 뷰가 컨트롤의 역할을 맡게 되지요.
이런 구조로 프로그램을 짤 경우에는 다음과 같은 원칙을 지켜야 할 것입니다.

① 도큐먼트는 정보를 관리하는 역할만을 해야 하며 절대로 뷰에 나타날 것을 직접 그리면 안됩니다.
② 뷰는 도큐먼트에서 자기가 필요한 정보를 가져다가 화면에 그리는 역할만을 해야 하며 절대로 도큐먼트의 내용을 직접 손봐서는 안됩니다. 또한 정보를 가져다가 그릴 때도 될 수 있으면 각 정보를 표현하는 객체에게 그리기를 요청해야지 직접 객체의 정보를 읽어다가 화면에 그리는 것은 바람직하지 않습니다.
③ 컨트롤의 역할을 하는 뷰는 사용자가 어떤 동작을 했을 경우 도큐먼트에 이 사건에 대한 처리를 요청해야지 직접 그 사건을 처리해서는 안됩니다.

이러한 입장에서 보았을 때, 위 문제에 대한 해결책을 다음과 같이 제시할 수 있습니다.

① 먼저 리스트 뷰에서 사용자가 뭔가를 선택하면 그 정보를 도큐먼트에게 보내면서 이에 대한 처리를 요청합니다(예를 들면 선택한 줄의 위치나 문자열 정보를 넘겨주면서 도큐먼트의 이벤트 핸들러를 부르는 것입니다).
② 그러면 도큐먼트는 이를 처리하고(예를 들면 선택한 줄의 위치에 따라 현재 선택된 아티클을 가리키는 포인터를 변경한다던가 하는 작업) 모든 뷰에게 내 정보가 바뀌었으니 알아서 화면을 고치라고 신호를 보냅니다(이때 보통 UpdateAllViews()를 사용합니다).
③ 각 뷰가 갱신하라는 신호를 받으면(보통 OnUpdate()에서 처리하지요) 파라미터를 보고 자신이 처리해야 하는 정보인지를 판단합니다. 만약 자신이 처리해야 하는 정보라면 도큐먼트에서 관련 정보를 읽어다가 자기가 담당한 화면을 고칩니다.

 

 <멤버 함수에서 다른 함수의 포인터 호출>

C++ 입문자입니다. 멤버 함수에서 다른 함수의 포인터를 어떻게 불러와야 할까요? 다른 함수의 포인터는 굳이 멤버 함수일 필요는 없습니다. 저는 지금 'C'에서 사용했던 signal 함수를 사용하려고 하는데요, 아시겠지만 인자로 함수 포인터를 받지 않습니까? 그런데 그것을 C++에 그대로 적용하려니 잘 되지가 않습니다.

class static으로 함수를 하나 만들어 쓰는 것이 좋을 듯합니다. 다음의 코드를 참조하세요.

class CThread : public CObject {
     ...
     class thr_args {
          public:
          CThread *the_thread;
          CObject *arg;
          void (CThread::*handler)(CObject *);
     };

     static void *
     startup(void *arg) {
          thr_args *t = (thr_args *)arg;

          t->the_thread->result = t->the_thread->run(t->arg);
          delete t;
          return 0;
     }
     ...
};

<API로 파일 오픈 대화상자 띄우기>

MFC를 쓰지 않고 파일 오픈 대화상자를 띄우려면 어떻게 해야 하는지 알고 싶습니다.

다음은 제가 API로 프로그래밍할 때 쓰는 방법입니다. 만약에 .AVI 파일을 로드하는 경우라면 다음과 같이 합니다.

void FileOpen(HWND hWnd, char szFileName[], OPENFILENAME * ofn)
{
     static char szTitleName[_MAX_FNAME + _MAX_EXT];

     ofn->lStructSize = sizeof(OPENFILENAME);
     ofn->hInstance = NULL;

     ofn->lpstrFilter = "AVI Files(*.AVI)\0*.avi\0ALL Files(*.*)\0*.*\0\0";
     ofn->lpstrCustomFilter = NULL;

     ofn->nMaxCustFilter = 0;

     ofn->nMaxFile = _MAX_PATH;
     ofn->nMaxFileTitle = _MAX_FNAME + _MAX_EXT;

     ofn->lpstrInitialDir = NULL;
     ofn->lpstrTitle = NULL;

     ofn->nFileOffset = 0;
     ofn->nFileExtension = 0;

     ofn->lpstrDefExt = "raw";
     ofn->lCustData = 0L;
     ofn->lpfnHook = NULL;
     ofn->lpTemplateName = NULL;

     ofn->hwndOwner = hWnd;
     ofn->lpstrFile = szFileName;
     ofn->lpstrFileTitle = szTitleName;
     ofn->Flags = OFN_CREATEPROMPT;
}

파일명을 선택해줄 때는 여기서 lpstrFilter 부분을 고쳐주면 됩니다.

 

<핸들이란 무엇인가요>

비주얼 C++에서 리소스쪽을 공부하고 있습니다. 리소스 ID를 인수로 받아서 커서의 핸들을 리턴한다고 하는데, 이게 무슨 말인지 잘 모르겠습니다. 일종의 운용 권한 같은 것인가요? 그리고 멤버 함수 선언 시 const로 선언하면 값을 바꿀 수 없다고 하는데, Help 파일을 읽어보면 Read Only라고 되어 있더군요. 이는 형변환을 못하는게 아니고 함수가 한 번 받아들인 값을 바꿀 수 없다는 말인가요?

화면이나 프린터 같은 그래픽 출력 장치에 그리기를 원하는 경우, 맨 먼저 디바이스 컨택스트(DC)에 대한 핸들을 얻어야 합니다. 프로그램에서 이 핸들을 주는 것은 그 장치를 사용할 수 있도록 윈도우가 허락하는 것이지요. 이어서 그리기 원하는 장치를 윈도우에게 알리기 위해 GDI 함수에 인자로 핸들을 포함하는 식입니다. 즉 핸들이란 어떠한 것을 조절, 운용할(핸들링) 수 있는 객체라고 생각하면 됩니다.
그리고 const형이란 그 변수의 값을 변화시킬 수 없는 변수란 뜻입니다. 만일 A라는 함수를 'A(char *szStr)'라는 식으로 만들면 이 함수 안에서 szStr을 마음대로 변화시킬 수가 있습니다. 그러나 'A(const char *szStr)'라고 만들어 버리면 szStr을 가공할 수가 없습니다. 어떤 클래스에서 특정한 맴버 변수를 쓰고 있는데 가상 함수를 만들었다고 상상할 때 이 가상 함수에서는 이 변수를 단지 참조만 해야 한다는 가정을 한다면 당연히 그 변수 타입에 const를 넣어서 선언을 합니다. 만일 이 선언을 하지 않으면 가상 함수에서 그 맴버 변수를 마음대로 조작해 이상한 결과를 초래할지도 모릅니다.

 

<베이스 클래스 바꾸는 방법>

AppWizard에서 Dialog Based로 프로젝트를 생성했는데, 기본 뷰(View)가 CDialog에서 상속받은 것이지 않습니까? 이것을 CPropertySheet로 바꾸고 싶은데 어떻게 해야 할지 모르겠습니다.

그냥 Class Wizard에서 CPropertySheet를 베이스 클래스로 새로운 클래스를 만든 다음 CxxxApp 클래스의 InitInstance() 함수에서

CxxxDlg dlg;
m_pMainWnd &dlg;
int nResponse = dlg.DoModal();
if(nResponse == IDOK){
}
else if(nResponse == IDCANCEL){
}

이 부분을 새로 만든 클래스로 적절히 연결해서 쓰면 됩니다. 원래 있던 CxxxDlg 클래스는 별 필요가 없습니다. 그건 Workspace의 FileView에서 xxxDlg.cpp하고 xxxDlg.h를 지우면(Del 키) ClassView에서도 없어집니다.

 

<프로그램 시작 시 한/영키의 변환>

프로그램을 실행하면서 영문 상태를 한글 상태로 바꾸려고 하는데, 한영 전환은 어떻게 하면 되는지요.

CRichEditView에서 WM_CREATE 메시지를 받을 때 아래와 같은 코드를 추가해 한글 상태로 바꿀 수 있습니다. 참고로 컴파일을 하려면 imm.h와 imm32.lib가 포함되어 있어야 합니다.

HIMC hImc;
DWORD dwConversion, dwSentence;

hImc = ImmGetContext(GetSafeHwnd());
ImmGetConversionStatus(hImc, &dwConversion, &dwSentence);

if (!(dwConversion & IME_CMODE_HANGEUL))
     ImmSetConversionStatus(hImc, dwConversion | IME_CMODE_HANGEUL, dwSentence);

 

<다이얼로그박스를 중앙에 오게 하려면>

다이얼로그박스 함수를 이용해서 리소스에 있는 다이얼로그박스를 하나 띄웠습니다. 이를 부르는 윈도우가 이 다이얼로그박스의 부모 윈도우가 될 줄 알았는데, 해당 다이얼로그 박스의 프로시저에서 GetParent를 해보니까 잘 안됩니다. 다이얼로그박스를 원래 윈도우 위에 한 가운데 띄우려면 어떻게 해야하는지요.

다음과 같은 방법을 통해 다이얼로그박스를 중앙에 오도록 할 수 있습니다.

HWND hwndOwner;
RECT rc, rcDlg, rcOwner;

case WM_INITDIALOG:

if ((hwndOwner = GetParent(hwndDlg)) == NULL)
     hwndOwner = GetDesktopWindow();

GetWindowRect(hwndOwner, &rcOwner);
GetWindowRect(hwndDlg, &rcDlg);
CopyRect(&rc, &rcOwner);

OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
OffsetRect(&rc, -rc.left, -rc.top);
OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);

SetWindowPos(hwndDlg, HWND_TOP, rcOwner.left + (rc.right / 2), rcOwner.top + (rc.bottom / 2),
    0, 0, // 크기인수를인식한다
    SWP_NOSIZE);

if (GetDlgCtrlID((HWND) wParam) != ID_ITEMNAME) {
     SetFocus(GetDlgItem(hwndDlg, ID_ITEMNAME));
     return FALSE;
}
return TRUE;