유용한 정보

[MFC] 팁

DevReff 2024. 12. 27. 07:44




728x90
반응형

리스트박스 컨트롤에서의 여러 항목 선택

MFC에서 리스트박스 컨트롤을 사용하여 여러 항목을 동시에 선택 가능하도록 하고 싶다. , Shift 키나 Ctrl 키를 사용하여 다중 선택하게 하고 싶은 데 어떻게 해야 하는가 ? 이는 별도의 메소드 등을 사용하여 해결되는 문제가 아니고 리스트박스 컨트롤의 스타일로 Extended를 선택해야 한다.

 

프로그램을 하나만 실행하기

내가 만드는 프로그램을 항상 하나만 실행하도록 하려면 어떻게 해야 하나 ? 그리고 두 번째 실행시에는 먼저 실행되어 있던 프로그램을 앞으로 띄우고 싶다.

만일 프로그램의 캡션이 고정되어 있다든가 사용되는 윈도우의 클래스 이름을 알고 있다면 FindWindow 혹은 FindWindowEx를 사용하면 아주 손쉽게 문제를 해결할 수 있다. 하지만 윈도우의 캡션이 계속적으로 변한다든지 윈도우 클래스 이름을 모르는 경우에는 이것으로 해결책이 되지 못한다. MFC를 사용할 경우가 대표적인데 윈도우 클래스의 이름을 MFC 내부에서 만들어 사용하기 때문에 윈도우 클래스를 직접 등록하여 사용하는 고난도의 기술을 사용하기 이전에는 윈도우 클래스의 이름을 알 수가 없다. 해결책에는 여러 가지 방법이 있겠지만 파일 맵핑 객체를 이용하도록 하겠다. 원래 파일 맵핑 객체의 목적은 여러 프로세스간에 데이터를 주고 받기 위해 사용되며 사용 방식 자체는 기존의 동적 메모리 할당 방식과 흡사하다.
간단히 절차를 설명하면 다음과 같다. 처음 뜨는 프로그램이 정해진 이름의 파일 맵핑 객체(여기서는 이름은 My TestMap)를 만들어 놓는다. 그리고 나서 자신의 메인 윈도우의 핸들을 그 객체에 기록해 놓는다. 이 때 다시 그 프로그램을 실행시키면 이 이름의 파일 맵핑 객체가 있는지 살펴보고 있으면 실행을 중지한다. 그리고 파일 맵핑 객체에 기록되어 있는 윈도우의 핸들값을 읽어서 앞으로 띄워 버린다. 이것이 가능한 이유는 파일 맵핑 객체는 시스템 전역 객체이기 때문이다. 즉 한 프로세스에서 만들어도 다른 프로세스에서 접근할 수 있다. 먼저 항상 하나만 떠 있게 하는 방법을 알아보고 다음으로 기존의 프로그램을 앞으로 띄우는 코드를 알아보도록 하겠다. 사실 아래의 코드를 이해하려면 파일 맵핑 객체에 대해 알아야 하는데 그것 자체에 대한 설명은 생략하기로 하겠다.

항상 하나만 떠 있게 하는 방법
다음 코드를 애플리케이션 클래스의 InitInstance의 선두에 넣는다. 파일 맵핑 객체의 크기를 4바이트로 잡은 이유는 윈도우 핸들이 UINT이고 그 크기가 4바이트이기 때문이다. CreateFileMapping API는 파일 맵핑 객체를 생성할 때 사용되는데, 이름과 크기를 줄 수 있다. 이미 존재하는 이름으로 파일 맵핑 객체를 생성하려고 하면 GetLast Error라는 API를 호출했을 때 ERROR_ALREADY_ EXISTS라는 에러 코드가 리턴된다. 이것으로 먼저 실행된 프로그램이 있는지 알아낼 수 있다.

HANDLE hMapping;

// MyTestMap이란 이름으로 4바이트의 영역을 잡는다
hMapping = CreateFileMapping( (HANDLE) 0xffffffff, NULL,
PAGE_READWRITE, 0, 4, “MyTestMap” );
if (hMapping)
{
if (GetLastError() == ERROR_ALREADY_EXISTS)
//
같은 이름의 파일 맵핑 객체가 존재
{
//
프로그램이 이미 실행중임을 알리는 메시지를 띄운다.
MessageBox( NULL, “
이미 실행 중입니다.”, “경고
”, MB_OK );
return FALSE;
}
}
위의 코드는 하나의 실행파일을 실행할 수 있는 방법이지만 기존에 실행되어 있는 프로그램을 앞으로 띄우는 일까지 할 수는 없다. 그렇게 하고 싶다면 InitInstance의 코드의 끝 부분에 메인 윈도우의 핸들을 파일 맵핑 객체에 저장하는 코드를 추가한다. 아래의 예는 SDI(Single Document Interface) 혹은 MDI(Multiple Document Interface)형식의 프로젝트인 경우의 예이다. 만일 다이얼로그 기반의 프로젝트라면 아래의 코드는 메인 다이얼로그 클래스의 WM_CREATE 메시지나 OnInitDialog 함수에 추가되어야 한다. CreateFileMapping으로 생성된 파일 맵핑 객체는 MapViewOfFile이란 API를 통해 메모리 포인터로 변환할 수 있다. 즉 할당된 파일 맵핑 객체를 접근할 수 있는 메모리 포인터를 사용해 내용을 읽고 쓰고 할 수 있다.

LPDWORD lpDword;

// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// ---------------------------------
//
이번에는 파일 맵핑 객체를 쓰기 모드로 오픈한다.
lpDword=(LPDWORD)MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, 4);
//
메인 윈도우의 핸들을 저장한다
.
*lpDword = (DWORD)m_pMainWnd->m_hWnd;
UnmapViewOfFile(lpDword);
// ---------------------------------
m_pMainWnd->ShowWindow(SW_SHOW);
그러면 앞서 살펴보았던 두 번째로 뜨는 프로그램인지 체크하는 코드 부분을 보강해 보자. 두 번째 뜨는 프로그램일 경우에 원래 먼저 떴던 프로그램의 메인 윈도우를 앞으로 내세우는 코드를 추가해 보자.

hMapping = CreateFileMapping( (HANDLE) 0xffffffff, NULL,
PAGE_READWRITE, 0, 32, “MyTestMap” );
if (hMapping)
{
if (GetLastError() == ERROR_ALREADY_EXISTS)
//
같은 이름의 파일 맵핑 객체가 존재
{
//
프로그램이 이미 실행 중임을 알리는 메시지를 띄운다.
MessageBox( NULL, “
이미 실행 중입니다.”, “경고”, MB_OK );

// 기존 파일 맵핑 객체를 연다. 기존 파일 맵핑 객체의 선두에 이전 프로그램
//
의 메인 윈도우 핸들이 들어있다. 뒷부분의 코드를 보면 알 것이다.
LPDWORD lpDword;
HWND hWnd;
//
파일 맵핑 객체를 읽기 모드로 오픈한다
.
hMapping = OpenFileMapping(FILE_MAP_READ, FALSE,
“MyTestMap”);
//
파일 맵핑 객체 핸들로부터 포인터를 얻어낸다
.
lpDword = (LPDWORD)MapViewOfFile(hMapping,
FILE_MAP_READ, 0, 0, 4);
//
저장되어 있던 윈도우 핸들을 얻어낸다
.
hWnd = (HWND)*lpDword;

// 윈도우를 전면으로 내세우기 전에 먼저 아이콘 상태에 있을지도 모르는
//
윈도우를 원래 크기로 되돌린다. IsIconic 함수를 호출하여 아이콘 상태

//
여부를 확인해도 될 것이다
.
ShowWindow(hWnd, SW_SHOW);
//
윈도우를 전면으로 내세운다
.
SetForegroundWindow(hWnd);
//
파일 맵핑 객체를 닫는다
.
UnmapViewOfFile(lpDword);
return FALSE;
}
위의 코드에서 윈도우를 전면으로 내세우는데 SetFore groundWindow라는 함수를 사용한다는 것을 기억해 두기 바란다. 이 함수의 인자로는 반드시 메인 윈도우의 핸들을 지정해야 한다. 지금까지 살펴보았던 코드를 다시 전체적으로 정리하면 다음과 같다.

HANDLE hMapping;

hMapping = CreateFileMapping( (HANDLE) 0xffffffff, NULL,
PAGE_READWRITE, 0, 32, “MyTestMap” );
if (hMapping)
{
if (GetLastError() == ERROR_ALREADY_EXISTS)
//
같은 이름의 파일 맵핑 객체가 존재
{
//
프로그램이 이미 실행 중임을 알리는 메시지를 띄운다.
MessageBox( NULL, “
이미 실행 중입니다.”, “경고”, MB_OK );

// 기존 파일 맵핑 객체를 연다. 기존 파일 맵핑 객체의 선두에 이전 프로그램의
//
메인 윈도우 핸들이 들어있다. 뒷부분의 코드를 보면 알 것이다.
LPDWORD lpDword;
HWND hWnd;
//
파일 맵핑 객체를 읽기 모드로 오픈한다
.
hMapping = OpenFileMapping(FILE_MAP_READ, FALSE,
“MyTestMap”);
//
파일 맵핑 객체 핸들로부터 포인터를 얻어낸다
.
lpDword = (LPDWORD)MapViewOfFile(hMapping,
FILE_MAP_READ, 0, 0, 4);
//
저장되어 있던 윈도우 핸들을 얻어낸다
.
hWnd = (HWND)*lpDword;

// 윈도우를 전면으로 내세우기 전에 먼저 아이콘 상태에 있을지도 모르는
//
윈도우를 원래 크기로 되돌린다. IsIconic 함수를 호출하여 아이콘

//
상태 여부를 확인해도 될 것이다
.
ShowWindow(hWnd, SW_SHOW);
//
윈도우를 전면으로 내세운다
.
SetForegroundWindow(hWnd);
//
파일 맵핑 객체를 닫는다
.
UnmapViewOfFile(lpDword);

return FALSE;
}
//
이 뒤부터 ------ 까지는 원래 InitInstance 코드
.....
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// --------------
//
다음을 추가한다.
LPDWORD lpDword;

lpDword = (LPDWORD)MapViewOfFile(hMapping, FILE_MAP_WRITE,
0, 0, 4);
*lpDword = (DWORD)m_pMainWnd->m_hWnd;
UnmapViewOfFile(lpDword);
//
이 뒤로는 다시 원래 InitInstance의 코드
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();

 

하나의 세션으로 여러 개의 레코드셋을 사용하기

ODBC 클래스를 사용하면서 하나의 DSN(Data Source Name)에 대해 하나의 세션만을 사용해 여러 개의 레코드셋을 사용하고 싶다.

레코드셋 객체를 만들 때 같은 CDatabase 객체를 사용하느냐에 따라 같은 세션이 사용되는지 아니면 다른 세션이 생성되어 사용되는지가 결정된다. 레코드셋 객체의 생성자로 기존의 CDatabase 객체를 주면 이를 이용해 레코드셋을 오픈하기 때문에 기존의 세션이 그대로 이용된다. , 세션이 별도로 열리지 않는 것이다. 레코드셋 객체의 생성자로 NULL을 주면 레코드셋에서 알아서 CDatabase 객체를 하나 만들어 사용한다. , 세션이 별도로 열리는 것이다. 세션을 하나만 사용하느냐 여러 개를 사용하느냐가 뭐가 중요하냐고 반문할 사람이 있을지도 모르겠다. 하지만 세션이 하나 생성되면 그만큼 메모리를 쓰게된다.
또한 데이터베이스가 다른 시스템에 존재한다면 네트웍 트래픽도 증가한다. 또 대부분의 상용 데이터베이스는 동시 세션 수에 따라 판매 가격이 결정된다. 따라서 세션의 수를 최소화하는 것이 중요하다
.
그런데 MFC 프로그램의 경우 모든 레코드셋을 디폴트로 후자의 방법으로 만들어 놓기 때문에 레코드셋마다 세션이 별개로 열리게 된다. 예를 들어 하나의 레코드셋을 만들어 이를 다른 클래스의 멤버 변수 형태로 사용하면 다음과 같은 형식이 된다.

public:
CTifTestSet m_tifTestSet;
이 경우 레코드셋의 생성자로 아무 것도 넘어가지 않기 때문에 레코드셋에서 별도의 CDatabase 객체를 만들게 되고 따라서 세션이 하나 더 생성된다. 이를 해결하려면 먼저 전역 CDatabase 객체를 하나 멤버 변수로 선언해 두고 각 레코드셋 변수도 그냥 변수가 아닌 포인터 변수로 선언해야 한다.
public:
CDatabase m_globalDB;
CTifTestSet *m_pTifTestSet;
이렇게 한 다음 레코드셋을 오픈할 필요가 있을 때 다음과 같은 절차를 거친다.

m_pTifTestSet = new CTifTestSet(&m_globalDB);

다른 레코드셋을 오픈할 때도 위와 같은 CDatabase 객체를 사용하면 세션을 하나만 사용할 수 있다. 물론 동적으로 할당하기 때문에 나중에 delete를 호출하여 제거하는 것을 잊으면 안 된다.

 

다이얼로그 상의 특정 컨트롤의 색상 변경

다이얼로그 박스상의 특정 컨트롤의 색상을 변경하고 싶다.

이는 비주얼 베이직이나 델파이에서는 아주 쉽게 할 수 있는 일이지만 MFC 다이얼로그 박스 클래스에서 좀 복잡하다. 이런 용도로 존재하는 메시지가 바로 WM_ CTLCOLOR 메시지이다. 이 메시지는 어떤 윈도우가 자식 윈도우로 컨트롤들을 갖고 있을 경우에 발생한다. 이 메시지의 목적은 컨트롤과 컨트롤의 부모 윈도우간에 색상을 맞추기 위함이다. 예를 들어 부모 윈도우에서 글씨를 파란색으로 출력하고 배경색으로는 노란색을 사용하는데 자식으로 존재하는 컨트롤은 검정 글씨에 흰 바탕을 사용한다면 모양이 기괴할 것이다. 이런 것을 통일하기 위해 모든 컨트롤들은 자신의 부모 윈도우에게 글씨색과 배경색으로 어떤 색을 사용할 것인지 반드시 물어본다. 그 용도로 사용되는 메시지가 WM_CTLCOLOR이다. 이 메시지를 별도로 처리하지 않으면 디폴트 색상이 그대로 사용된다.
일단 다이얼로그 클래스의 WM_CTLCOLOR 메시지에 대한 처리 함수를 정의한다. 그 안의 코드를 다음과 같이 변경한다. 참고로 다이얼로그 클래스의 이름은 CTifTest Dlg라고 하고 대상이 되는 컨트롤의 ID IDC_TEXT OUT라고 하자
.
이 컨트롤의 글자색은 파란색으로 하고 배경색으로 회색으로 지정한다.

HBRUSH CTifTestDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT
nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
if (pWnd->GetDlgCtrlID() == IDC_TEXTOUT)
//
어떤 컨트롤이 보냈는가 ?
{
//
글자색을 파란색으로 지정한다
.
pDC->SetTextColor(RGB(0, 0, 255));
//
배경색을 회색으로 지정한다
.
pDC->SetTextColor(RGB(128, 128, 128));
}
return hbr;
}

 

다이얼로그 상에 비트맵 올리기

다이얼로그 위에 비트맵을 올리려고 한다. 다이얼로그에서 비트맵 버튼의 크기를 줄 때 비트맵의 크기와 동일하게 주었는데 크기가 맞지 않는다. 이유를 알고 싶다.

일단 이 문제는 다이얼로그에서 위치나 크기를 줄 때 사용하는 단위가 일반 픽셀 단위가 아니기 때문에 발생한 것이다. 다이얼로그 박스 템플릿에서 사용되는 단위는 다이얼로그 박스 유닛(Dialog box unit)이란 것이다.
비트맵을 올리는데 사용한 컨트롤이 비트맵의 크기와 맞게 자동으로 조절하는 어떤 메소드나 프로퍼티를 갖고 있다면 그걸 사용해 쉽게 문제를 해결할 수 있지만 아니라면 다음과 같은 방식으로 맞춰주어야 한다
.
먼저 다이얼로그 박스위에 어떤 컨트롤이든지 비트맵을 올릴 수 있는 컨트롤을 올렸고 IDC_BMPCTL은 바로 그 컨트롤의 ID라고 가정하겠다. 그리고 나서 다이얼로그 박스의 WM_INITDIALOG 메시지 처리부에서 비트맵이 올라간 컨트롤의 크기를 다음과 같이 변경한다.

XXXX::OnInitDialog()
{
.....
//
로드하고자 하는 비트맵의 폭과 높이를 각각 bmWidth, bmHeight라고 하자.
RECT rc;

GetDlgItem(IDC_BMPCTL)->GetWindowRect(&rc);
ScreenToClient(&rc);
GetDlgItem(IDC_BMPCTL)->MoveWindow(rc.left, rc.right,
bmWidth, bmHeight, TRUE);
return TRUE;
}
어떤 윈도우의 위치와 크기를 변경하는데 사용되는 메소드가 바로 CWnd 클래스의 MoveWindow이다. 대상 윈도우가 메인 윈도우라면 전체 화면 좌표계에서 위치와 크기를 변경하는데 사용되는 것이고 자식 윈도우라면 부모 윈도우의 좌표계 내에서의 상대적인 이동이나 크기 변경을 하는데 사용되는 것이다. 주어진 비트맵의 크기에 맞게 컨트롤의 크기를 변경하자는 것이지 시작 위치까지 변경하자는 것은 아니므로 일단 현재 위치를 알아야 한다. 그래서 사용되는 것이 바로 GetWindowRect이다. 그런데 문제는 여기서 나오는 좌표는 전체 화면에서 보았을 때의 좌표라는 점이다. 이를 다이얼로그내에서의 좌표로 변경해야만 Move Window를 사용할 수 있다. 그래서 ScreenTo Clie nt를 사용한다. 이름에 나타나듯이 전체 화면 좌표를 특정 윈도우의 좌측 상단 중심의 좌표로 변환해 주는 역할을 한다. 반대 변환은 ClientToScreen이라는 메소드를 사용하면 된다. GetWindowRect, ScreenToClient, ClientToS creen은 모두 픽셀 단위의 좌표계를 사용한다. MapDialog Rect라는 함수도 알아두기 바란다. 이 함수는 다이얼로그 박스 유닛으로 주어진 값을 픽셀 단위로 변환해 주는 역할을 한다. 이 함수의 원형은 다음과 같다.

BOOL MapDialogRect(HWND hDlg, LPRECT lpRect);

첫 번째 인자는 변환 대상이 되는 다이얼로그 박스의 핸들이고 두 번째 인자는 변환 대상이 되는 좌표가 들어있는 RECT 타입 인자에 대한 포인터이다. 즉 두 번째 인자에는 좌표가 다이얼로그 박스 유닛 단위로 들어있어야 한다. 그러면 제대로 변환이 되었을 경우 두 번째 인자에 픽셀 단위의 좌표가 변환되어 나온다.

프린트 다이얼로그를 안 띄우고 인쇄하기

프린트 다이얼로그를 안 띄우고 기본 프린터로 인쇄하고 싶다. 어떻게 해야 하는가 ?

기본적으로 인쇄는 인쇄 다이얼로그를 띄우는 절차를 필요로 한다. 그런데 경우에 따라서는 다이얼로그 없이 바로 기본 프린터로 출력하면 좋은 경우도 존재할 것이다. 여기서는 그 절차에 대해 알아보도록 하자. MFC의 인쇄 다이얼로그 클래스는 CPrintDialog인데 이 클래스를 생성할 때 인자를 잘 주면 다이얼로그가 뜨지 않고 기본 프린터에 대한 정보만 얻어오게 된다. 다음 코드는 인쇄 다이얼로그를 띄우지 않고 프린터에 출력하는 예제 코드이다.
주석을 잘 보기 바란다
.
// CPrintDialog
를 이용해 프린터의 디폴트 설정을 읽어온다
.
CPrintDialog dlg(TRUE, PD_RETURNDEFAULT);
// DoModal
을 실행하면 원래는 다이얼로그가 떠야 되지만 인자로
PD_RETURNDEFAULT
//
를 주었기 때문에 정보만 얻어오고 바로 리턴한다. 즉 다이얼로그가 뜨지 않고 바로

//
리턴한다
.
dlg.DoModal();
//
종이를 가로 방향으로 찍고 싶다든지 여러 매를 찍고 싶다면 아래의 코드를 수정하여

//
사용한다. DEVMODE 구조체를 공부해 보기 바란다.
//
다음 예는 가로 방향과 3매씩 찍도록 설정하는 예이다
.
DEVMODE*pDevMode=(DEVMODE *)::GlobalLock(dlg.m_pd.hDevMode);
if (pDevMode)
{
//
프린터의 인쇄 방향 설정을 변경한다
.
pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
// 3
매를 찍도록 한다
.
pDevMode->dmCopies = 3;
::GlobalUnlock(dlg.m_pd.hDevMode);
}

CDC dc;
//
프린터 설정을 dc 변수에 반영한다.
if (dc.Attach(dlg.CreatePrinterDC()))
{
DOCINFO di;

// 새로 생성되는 프린트 잡(job)의 이름을 준다.
memset(&di, 0x00, sizeof(DOCINFO));
di.cbSize = sizeof(DOCINFO);
di.lpszDocName = “InstPhoto Output”;

// 프린트 잡을 하나 연다.
dc.StartDoc(&di);
dc.StartPage();
// dc
에 원하는 출력을 수행한다
.
....

// 출력이 종료되었으면 마무리 작업을 한다.
dc.EndPage();
dc.EndDoc();
dc.DeleteDC();
}

 

CStatic 클래스를 이용한 비트맵 출력

MFC 프로그래밍을 하는데 CStatic 클래스를 이용해 비트맵을 출력하고 있다. 동적으로 상황에 따라 비트맵의 내용을 변경하고 싶은데 CStatic 클래스의 SetBitmap 함수로 잘 안된다.

CStatic 클래스에서 비트맵을 출력하는데 사용되는 것이 CStatic의 멤버 함수인 SetBitmap 함수이다. 예를 들어 CStatic 클래스의 객체 이름이 pic이고 변경하고자 하는 새로운 모양의 비트맵이 들어있는 CBitmap 타입의 변수를 bm이라고 가정하면 pic.SetBitmap(bm)을 쓰면 비트맵의 내용이 변경되어야 할 것 같은데 그렇게 되지 않는다. 다음과 같은 좀더 복잡한 과정을 거쳐야 한다.
pic.SetBitmap(NULL);
pic.SetBitmap(bm); // bm
CBitmap 클래스의 객체나 HBITMAP 핸들값

pic.ShowWindow(SW_SHOW);
pic.UpdateWindow();

 

응용프로그램의 정보를 레지스트리에 보관하기

응용프로그램에서 사용중인 정보(예를 들면 메인 윈도우의 위치 등)를 레지스트리에 보관했다가 다음 실행할 때 읽어들이고자 한다. 레지스트리 관련 API를 사용하지 않고 좀 더 쉽게 처리할 수 있는 방법은 없는가 ?원래 MFC에는 INI 파일 I/O를 위해 다음과 같은 종류의 함수들을 CWinApp 클래스의 멤버 함수로 제공해 준다.

GetProfileInt, GetProfileString, WriteProfileInt, WriteProfileString

이 함수들은 원래는 INI 파일하고만 관련된 것이지만 CWinApp SetRegistryKey 함수를 사용하면 이 함수들을 레지스트리 관련 I/O에 사용할 수도 있다. SetRegi stryKey를 호출할 때회사이름/프로그램이름의 형식을 갖는 문자열을 지정해 주는 것이 좋다. AppWizard에서는 디폴트로 SetRegistryKey(“Local AppWizard-Gene rated Applications”)를 호출하는 코드를 추가해 준다. 아무튼 SetRegistryKey에 지정한 값이 HKEY_ CURR ENT_USER\Software\에 붙어 사용된다. 예를 들어 다음과 같은 코드를 생각해 보자.

// 응용프로그램 클래스에서
SetRegistryKey(“Samsung\\Hangong”);
....
WriteProfileString(“Main Window”,”Type”, “Normal”);
위의 코드를 실행하면 HKEY_CURRENT_USER \Software\ 밑에 Samsung\Hangong이라는 서브 키가 생기고 이 밑에 또 \Main Window라는 서브키가 생성된다. 그리고 그것의 이름/(Name/Value) 중의 하나로 Type/Normal이 생성된다.

ActiveX 컨트롤 띄우기

직접 만든 ActiveX 컨트롤에서 다이얼로그 박스를 하나 만들었다. 그리고 그 위에 이미 등록되어 있는 문제없는 ActiveX 컨트롤(그래프 컨트롤)을 하나 올리면 아무 것도 보이지 않는다. 그런데, 기본적으로 제공되는 컨트롤들(예로 에디터 박스나 버튼 등)만 사용하면 괜찮은데, 어떤 것이든 ActiveX 컨트롤 중 하나만 삽입하면 아무 것도 보이지 않는다.
현재 만들고 있는 ActiveX 컨트롤의 응용 프로그램 클래스의 InitInstance 함수의 선두에 AfxEna bleControlContainer()를 삽입하면 된다.

팝업 메뉴 띄우기

뷰 윈도우에서 오른쪽 마우스 버튼을 누르면 팝업 메뉴가 뜨게 하고 싶다. CMenu 클래스를 이용했는데 잘 안 된다.다음 코드는 오른쪽 마우스 버튼이 클릭되었을 때 그 위치에 팝업 메뉴를 띄우는 코드이다.

void Cxxx::OnRButtonDown(UINT nFlags, CPoint point)
{
CMenu menu;

// 마우스의 좌표가 전체 화면에서 어떤 좌표를 갖는지 알아낸다.
ClientToScreen(&point);
//
메뉴를 띄운다
.
menu.LoadMenu(IDR_CHATMENU);
CMenu *pRMenu = menu.GetSubMenu(0);
// TrackPopupMenu
의 네 번째 인자로 명령을 받을 윈도우의 포인터를 지정한다

pRMenu->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y,
GetParent());
}
여기서 중요한 것은 IDR_CHATMENU가 가리키는 메뉴 리소스가 다음 그림과 같이 탑 메뉴의 첫 번째 서브 메뉴가 나타나는 팝업 메뉴가 되도록 구성해 놓아야 한다는 점이다.
그러면 이렇게 생성된 팝업 메뉴에서 사용자가 항목을 선택하면 그 항목은 도대체 누가 처리하게 되는걸까 ? 그것은 바로 CMenu 클래스의 TrackPopupMenu라는 함수의 마지막 인자와 관련있다. 이 인자로 바로 메뉴 메시지를 받을 윈도우의 포인터를 지정하도록 되어있다. 위의 예에서는 GetParent()를 사용했기 때문에 Cxxx 클래스의 부모 윈도우에게 메시지가 가게 된다. TrackPopup Menu로 팝업 메뉴를 띄울 때는 위치를 전체 화면에서의 좌표로 지정해야 한다. 그런데 마우스 메시지로 넘어오는 좌표는 윈도우 내에서의 상대 좌표이기 때문에 이를 전체 화면에서의 좌표로 변환하기 위해 ClientToScreen 함수를 사용하고 있다.


에디트 컨트롤 뒤에 내용 추가하기

현재 내용의 변경없이 에디트 컨트롤 뒤에 새로운 내용을 추가하려면 어떻게 해야하는가 ?m_editCtrl CEdit 타입의 객체라고 하고 lpMsg가 추가되어야 할 문자열이라면 다음과 같은 코드를 수행하면 된다.int len = m_editCtrl.GetWindowTextLength();
m_editCtrl.SetSel(len, len);
m_editCtrl.ReplaceSel(lpMsg);
현재 에디트 컨트롤의 마지막 빈 글자를 선택(SetSel)하게 하고 그걸 추가하고자 하는 문자열과 바꿔 버리게 하는 것(ReplaceSel)이다.

파일 경로에서 드라이브, 디렉토리, 파일 이름,
확장자 분리하기

어떤 파일의 절대 파일 경로명이 주어졌을 때 여기서 드라이브, 디렉토리, 파일 이름, 확장자를 분리하고자 한다. 쉽게 할 수 있는 방법이 있는가 ?사실 이건 MFC와는 관계없이 _splitpath라는 C 함수를 이용하면 된다. 다음 코드를 참고하기 바란다.

#include <stdlib.h>
#include <io.h>
#include <direct.h>
......
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];

//lpFileName이 바로 분리하고자 하는 파일의 절대 파일 경로를 가리키는 포인터이다.
_splitpath(lpFileName, drive, dir, fname, ext);

위의 코드를 실행하면 drive, dir, fname, ext에 드라이브, 디렉토리, 파일 이름, 확장자가 차례로 들어온다. 만일 드라이브나 디렉토리에는 관심이 없다면 _splitpath함수를 호출할 때 두 번째 인자와 세 번째 인자로는 NULL을 지정하면 된다.
참고로 확장자가 존재할 경우에는 .까지 확장자로 포함된다는 것을 알아두기 바란다.

 

GetParent API의 리턴값

다이얼로그 박스 클래스에서 GetParent 함수를 호출했는데 리턴되는 값이 부모 윈도우의 값이 아닌 것 같다.필자도 이 문제 때문에 상당히 고생한 적이 있는데 GetParent 함수의 리턴값은 보통의 윈도우에서는 윈도우 생성시 지정한 부모/자식 윈도우 간의 관계에 따라 리턴값이 정확히 나온다. 하지만 다이얼로그 박스의 경우에는 다르다. 다이얼로그 박스는 GetParent를 호출하면 무조건 그 것이 속한 응용프로그램의 메인 윈도우 핸들이 리턴된다.

 

특정 프로그램을 실행하고 종료를 기다리기

특정 프로그램을 실행한 다음에 그 프로그램이 종료될 때까지 대기하고 싶다. 어떻게 해야하는가 ?사실 이 코드는 많이 소개되었고 MFC와도 전혀 관계없지만 모르실 분들을 위해 소개하고자 한다. 다음은 실행하고자 하는 파일의 경로를 지정해 주면 Create Process를 이용해서 특정 프로그램의 실행이 끝나기를 기다리는 함수이다.

// buffer에 실행하고자 하는 파일 이름이 들어온다.
void DoCreate(HWND hWnd, LPSTR buffer)
{ STARTUPINFO sui;
PROCESS_INFORMATION pi;
DWORD ret;

memset(&sui, 0x00, sizeof(STARTUPINFO));
sui.cb = sizeof(STARTUPINFO);
ret = CreateProcess(buffer, NULL, NULL, NULL, FALSE,
0, NULL, NULL,&sui, &pi);
if (ret == TRUE) //
제대로 실행되었으면
{ hProcess = pi.hProcess;
//
실행이 끝나기를 대기한다.
WaitForSingleObject(hProcess, 0xffffffff);
CloseHandle(hProcess);
}
}


데이터 파일의 실행

탐색기에서 실행 파일이 아닌 데이터 파일을 더블 클릭하면 그 데이터 파일과 연결된 실행 파일이 자동 실행된다. 이를 구현하는 방법은 ?

ShellExecuteEx라는 API를 사용하면 된다. 다음 함수는 인자로 데이터 파일 혹은 실행 파일의 경로를 받아서 실행해 준다. 데이터 파일의 경우에는 연결된 실행 파일을 찾아서 것을 실행해 준다.
BOOL Execute(LPSTR lpPath)
{
char FilePath[255];
SHELLEXECUTEINFO ExecInfo;

// lpPath
를 나누어 본다
.
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];

_splitpath(lpPath, drive, dir, fname, ext);
//
디렉토리 경로를 얻는다.
strcpy(FilePath, drive);
strcat(FilePath, dir);
//
파일 이름을 얻는다
.
strcat(fname, ext);

SHELLEXECUTEINFO ExecInfo;
ExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ExecInfo.hwnd = hWnd;
ExecInfo.lpVerb = “open”;
ExecInfo.lpFile = fname;
ExecInfo.lpParameters = NULL;
ExecInfo.lpDirectory = FilePath;
ExecInfo.nShow = SW_SHOW;
ExecInfo.hInstApp = g_hInstance;
// g_hInstance
는 프로그램의 인스턴스 핸들

return ShellExecuteEx(&ExecInfo);
}

예를 들어 C:\temp\intro.hwp라는 파일을 실행하고 싶다면 다음과 같이 Execute 함수를 실행하면 된다.

Execute(“C:\\TEMP\\INTRO.HWP”);

물론 그 시스템에 HWP 파일을 실행할 수 있는 프로그램이 설치되어 있어야 할 것이다.

ActiveX 컨트롤에서 브라우저 크기 변경하기인터넷 익스플로러 상에서 MFC로 만든 ActiveX 컨트롤을 사용하고 있다. 그런데 실행 중에 인터넷 익스플로러의 크기를 변경하고 싶다.

핵심은 바로 인터넷 익스플로러의 IWebBro wser2 인터페이스를 얻어내는데 있다. 이 인터페이스는 인터넷 익스플로러를 제어하는데 사용할 수 있는 아주 유용한 인터페이스이다. 이 인터페이스를 얻어내서 put_Width, put_ Height 메소드를 호출하면 크기를 변경할 수 있다. 여기서 지정되는 폭과 높이는 인터넷 익스플로러 전체의 폭과 높이라는 점을 명심하기 바란다.
그럼 IWebBrowser2 인터페이스를 얻어내는 방법을 알아보도록 하자. COleControl 클래스에 보면 GetClient Site라는 메소드가 있다. 이는 현재 컨트롤의 컨테이너에 대한 IOleClientSite라는 인터페이스를 리턴해 준다. 이 인터페이스의 QueryInterface를 통해 IServiceProvider 인터페이스를 얻어내고 거기서 다시 IWebBrowser2 인터페이스를 얻어내면 된다. 예제 코드는 다음과 같다.

IOleClientSite *pClientSite;
IServiceProvider *spSrvProv;
IWebBrowser2 *pIWeb;

// IOleClientSite에 대한 포인터를 얻어낸다.
pClientSite = GetClientSite();
if (pClientSite)
{
// IOleClientSite
에서 IServiceProvider 인터페이스에 대한 포인터를

//
얻어낸다
.
if (pClientSite->QueryInterface(IID_IServiceProvider,
(void**)&spSrvProv)!= S_OK)
return;
if (spSrvProv->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, (LPVOID*)&pIWeb) != S_OK)
{
spSrvProv->Release();
return;
}
//
웹 브라우저의 크기를 조절한다
.
pIWeb->put_Width(lWidth); //
폭을 lWidth로 지정한다
.
pIWeb->put_Height(lHeight); //
높이를 lHeight로 지정한다

pClientSite->Release();
}

 

MFC의 메시지 맵 항목과 처리 함수 정리

MFC 프로그래밍을 하다보면 클래스 위저드가 아닌 손으로 메시지 맵을 조작해야하는 경우가 간혹 생긴다. 그럴 경우 메시지 맵 항목에 해당하는 처리 함수의 프로토 타입이 무엇인지 몰라서 헤매는 경우가 있다. 그래서 메시지 맵 항목에 따른 처리 함수의 프로토 타입을 다음과 같이 정리해 보았다.


코드 실행 중에 다른 윈도우 메시지 처리하기

하나의 함수 내에서 시간이 오래 걸리는 작업을 하고 있다. 이 때 작업 취소를 위한 별도의 다이얼로그를 하나 띄워 두었는데 이쪽의 버튼이 눌리지 않는다. 어떻게 해야 하나 ? 시간이 오래 걸리는 작업을 별도의 스레드로 만들어 처리하던지 아니면 시간이 오래 걸리는 작업을 수행하는 함수 안에서 다음 코드를 가끔 호출해 주면 된다. 만일 루프를 돌고 있다면 루프 내에서 다음 코드를 한번씩 호출해 주면 제일 좋다.

MSG msg;
while (PeekMessage(&msg, NULL, NULL, NULL, TRUE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

비주얼 베이직에서는 이런 용도로 DoEvents라는 메소드가 제공된다.
다음 호에는 윈도우의 암호화 관련 API CryptoAPI에 대해 알아보도록 하겠다. 이를 이용하면 대칭키 방식의 암호화나 공용/개인키 방식의 암호화를 손쉽게 수행할 수 있다.

출처: https://use1348.tistory.com/48 [유용한 정보:티스토리]