<방패 모양 윈도우 만들기>

 윈도우의 모양을 개성있게 만들기 위해 노턴 크래시 가드의 모양처럼 방패 모양의 윈도우를 만들려고 합니다. SetWindowRgn() 함수를 사용하라고 하던데 이 함수는 CreateEllipsRgn() 등의 함수를 이용해 영역을 만들어야 하더군요. 하지만 이런 영역을 만드는 함수가 만들 수 있는 영역의 모양은 타원, 사각형, 둥그런 사각형, 다각형, 다중다각형 등의 모양 밖에 안됩니다. 이런 정도의 함수로 자유로운 모양의 윈도우를 제작할 수는 없나요?

 가끔 이상한 모양의 윈도우를 보게 될 것입니다. 대표적으로 자주 보게 되는 것이 MP3 플레이어로 각광받고 있는 WinAmp를 예를 들 수 있죠. 이런 윈도우들을 보면 어떻게 구현했는지 참 궁굼합니다. 이런 종류의 윈도우들은 대부분 윈도우에 비트맵(Bitmap)을 덮어 씌워 구현합니다. 일단 이런 종류의 윈도우를 구현하려면 좌표 영역을 계산해야 되기 때문에 상당히 잔손이 많이 가게 됩니다. 구현원리는 이렇습니다.먼저 몇가지 NC(Non-Client 영역)에 대한 메시지들을 처리해야 합니다.

윈도우의 캡션바를 변경하려면 우선 윈도우가 활성화되는 경우 모두 세 가지가 있는데 이럴 때 캡션 부분을 WindowDC로 그려주면 됩니다(WindowDC에 대한 설명은 MSDN을 참조). PreCreate 메시지 핸들러에서 윈도우 스타일을 WS_VISIBLE, WS_CAPTION, WS_THICKFRAME 이 세가지 스타일만 있게 정의하고, 다음과 같이 메인 프레임 메시지 핸들러에 정의하면 첫째 파라미터 nStart는 세 가지의 파라미터가 넘어옵니다.

afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);

WA_ACTIVE : 클릭 이외 다른 윈도우가 인액티브 돼 자신이 z-order의 맨 앞으로 왔을 때 발생
WA_CLICKACTIVE : 클릭에 의해 액티브될 때 발생
WA_INACTIVE : 인액티브 상태

 이 파라미터 값을 체크하고 모든 경우에 대해 알맞은 그림을 그려주면 됩니다. 그림을 그릴 때 약간의 테크닉이 필요합니다. 비트맵을 제어해 본 독자는 알겠지만 많은 양의 비트맵을 그릴 경우 화면의 깜박임 현상을 볼 수 있는데, 이런 깜박임 현상을 없애려면 memDC를 사용하면 됩니다. memDC를 사용하는 것은 우선 메모리 상에 가상의 비트맵을 만들고 거기에 모든 그림을 그린 다음 화면에 한 번에 뿌리면 화면 깜박임이 없어지게 됩니다.

CRect rect;
GetWindowRect(&rect);
CBitmap bmpCaption;     // 캡션 그림을 그리는 비트맵
bmpCaption.LoadBitmap(IDB_CAPTION);
CWindowDC capDC(this);     // 창에 그릴 DC를 선택
CDC capMemDC;
CDC bufDC;
capMemDC.CreateCompatibleDC(&capDC);
CBitmap bufBitmap;     // 메모리 상의 가상 비트맵
// 윈도우 크기만큼의 메모리 비트맵을 만든다.
bufBitmap.CreateCompatibleBitmap(&capDC, rect.Width(), 48);

CBitmap* pOldBitmap1 = (CBitmap*)bufDC.SelectObject(&bufBitmap);
CBitmap* pOldBitmap2 = (CBitmap*)capMemDC.SelectObject(&bmpCaption);

bufDC.BitBlt(0, 0, 120, 48, &capMemDC, 0, 0, SRCCOPY);

for(int i = 120; i<rect.Width()-120; ) {
     bufDC.BitBlt(i, 0, 10, 48, &capMemDC, 100, 0, SRCCOPY);
     i += 10;     // 크기가  10 픽셀인 비트맵을 캡션에 그린다.
}

capDC.BitBlt(0, 0, rect.Width(), 48, &bufDC, 0, 0, SRCCOPY);

 

 이런 식으로 비트맵 파일을 핸들링하고 WM_NCPAINT 메시지에서 적절히 처리해주면 됩니다.

afx_msg OnNcCalcsize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp);

위의 함수는 WM_NCCALCSIZE 라는 메시지 핸들러인데, 이 함수의 두 번째 함수 인자는 CRect이 세 개로 구성된 struct이고 이 중에 첫 번째가 클라이언트 영역을 표시하는 부분입니다. 이 인자의 값을 조정해주면 원하는 클라이언트 영역으로 조정할 수 있게 됩니다.

afx_msg UINT OnNcHitTest(CPoint point);
afx_msg UINT OnHitTest(. . . . .);

 이런 식으로 윈도우의 모양을 자유롭게 만들 수 있습니다. 윈도우에 비트맵을 씌워 윈도우 모양을 바꾸면 프레임웍에서 더 이상 체크를 못합니다. 예를 들어 일정한 영역에 마우스가 들어왔는지, 클릭이 됐는지 등등이지요. 그렇다면 이것도 다  구현해야 합니다. 이럴 때 위의 메시지 핸들러를 사용하면 됩니다. 우리가 비트맵을 씌워 만든 윈도우에 어느 영역에 마우스, 키보드 등의 이벤트가 발생했는지를 체크하면 됩니다. 앞서 서두에서 말한 지루한 작업이 바로 이것입니다. 이런 식으로 캡션 부분(위쪽), 상태바 부분(아래쪽), 클라이언트 부분(뷰), 테두리 부분 등 어느 부분이나 모양을 자유로이 변경할 수 있습니다.

<투명한 윈도우를 만들려면>

 프로그램을 실행하다 보면 윈도우 화면이 밑에 깔리고 그 위에서 여러 가지 일들이 벌어지지 않습니까? 그런데 그 윈도우 화면을 안보이고 그냥 컴포넌트들만 보이게 하려면 어떤 설정을 해야 하나요? 즉 윈도우 화면을 투명하게 처리할 수 있는 방법이 있는지요.

 투명한 윈도우는 많이 사람들이 한 번쯤 해보려고 하는 것인데, 답부터 말하자면 가능하기도 하고 그렇지 않기도 합니다. 투명의 의미가 배경이 그냥 보인다는 것으로 보면 배경을 칠할 때 아무것도 안 그리면 뒷 배경이 그냥 보이지요. 그런데 이렇게 하면 그 뒷 배경이 계속 보관돼서 마치 다운된 윈도우를 보는 것 같아서 어색합니다.

 다른 방법으로는 SetWindowRgn을 사용해 사각형이 아닌 윈도우를 만드는 것입니다. 이렇게 하면 투명한 윈도우가 아니라 구멍뚫린 윈도우를 만들 수 있습니다. 그럭저럭 투명한 효과를 볼 수 있습니다만, 여기에는 치명적인 단점이 있습니다. 자식 윈도우는 부모 윈도우가 보이는 곳에만 보일 수 있다는 것이죠. 구멍 뚫린 곳에는 자식 윈도우가 있어도 그것이 화면에 나타나지 않습니다. 이럴 경우 좀 복잡해지지만 방법이 없진 않습니다. 자식 윈도우가 있는 곳에 부모 윈도우가 보이게 만들면 됩니다. 예를 들어 자식 윈도우가 2개가 있고 각각

RECT = { 100, 100, 200, 200 }, { 100, 300, 200, 400 }

에 있다면 부모 윈도우를 다음과 같이 설정합니다.

HRGN hRgn1 = CreateRectRgn( 100, 100, 200, 200 );
HRGN hRgn2 = CreateRectRgn( 100, 300, 200, 400 );
CombineRgn( hRgn1, hRgn1, hRgn2, RGN_OR );
SetWindowRgn( hParentWnd, hRgn1, TRUE );

 이제 부모 윈도우의 다른 부분은 모두 구멍이 뚫리고 단지 두 개의 자식 윈도우 바로 밑에만 막혀있는 그런 윈도우가 됩니다. 실제로 생긴 모습을 보자면 네모칸 두 개가 있으니까 윈도우 2개가 아닌가 하겠지만 그렇지 않습니다. 여기에 자식 윈도우 2개를 만들면 보기에는 자식 윈도우 2개가 공중에 떠 있는 것 같이 보이게 됩니다.

 

<텍스트 파일에서 한 줄씩 읽어와 출력하려면>

 비주얼 C++에서 타자연습 프로그램을 만들려 합니다. 텍스트 파일에 있는 문장을 한 줄씩 가져와 출력하려 하는데,

까마귀 날자 배 떨어진다<Enter키>
수염이 석자라도 먹어야.....<Enter키>

위와 같은 텍스트 파일이 있을 때 한 줄씩 읽어와 출력하려면 어떻게 해야 하는지 알고 싶습니다(Enter키 앞의 문장까지만 읽어오고 다음 줄도 Enter키 앞의 문장까지만 읽어오는 방법).

다음을 참고하세요.

#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <afxtempl.h>

CFile m_File;
CFileStatus status;
char *cBuff, *token, seps[] = "\r\n";
CString data, msg;
CStringArray m_sLineStringArray;
if(m_File.Open(m_sDataFile, CFile::modeRead, NULL) == 0 ) {
     msg.Format("Can't Open %s File!", m_sDataFile);
     AfxMessageBox(msg, MB_ICONEXCLAMATION);
     return FALSE;
}

m_File.GetStatus( status );

// 데이터 파일을 읽음
cBuff = (char *)calloc(status.m_size + 1, sizeof(char));
m_File.Read(cBuff, status.m_size);
data = cBuff;
free(cBuff);

token = strtok((LPTSTR)(LPCTSTR)data, seps);
m_sLineStringArray.RemoveAll();
while(token != NULL) {
     m_sLineStringArray.Add(token);
     token = strtok(NULL, seps);
}

// 파일 닫음
m_File.Close();

 

<MFC에서 메뉴 제거>

MFC에서 메뉴를 제거하려면 어떻게 해야 하나요?

간단합니다. MFC에서 메뉴를 없애려면 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)에서 cs의 cs.hMenu = NULL 로 해주시면 메뉴가 뜨지 않습니다.

 

<디스크 섹터로의 쓰기>

도스 시절에는 인터럽트를 통해 원하는 디스크 섹터로의 읽기 쓰기가 쉽게 해결됐었는데 윈도우 95상에서는 이것을 할 수 있는 방법을 모르겠습니다. 해당 인터럽트도 윈도우 95에서는 막아놓은 것 같고 노턴 유티리티조차 디스크 에디터가 도스용으로만 버전업되고 윈도우 용으로는 나오지 않는 것 같던데, 윈도우 95에서는 섹터로의 쓰기 방법이 없는지 궁금합니다. 이전 프로그램이 긴 파일이름 구조를 파괴하지 못하도록 이전의 디스크 직접 엑세스 동작을 막아놓았다면 당연히 다른 길을 열어놓았을 것 같은데요?

MS 도스는 리얼모드(Real-Mode)에서 동작하며 윈도우 95이상은 프로텍티드 모드에서 작동합니다. 보호 모드에서 말하신 것과 같은 작업을 하려면 특별한 권한(ring0)이 필요합니다. 일반적으로 윈도우 애플리케이션은 ring3에서 동작하므로 말하신 것과 같은 작업을 하려면 프로텍션 에러(Protection Error)가 발생할 것입니다.

 그와 같은 기능을 구현하려면 드라이버를 작성해야합니다. VXD 혹은 WDM을 작성해야 한다는 말이죠. 자세한 내용은 인텔 아키텍처 소프트웨어 디벨로퍼 매뉴얼(www.intel.com)과 시스템 프로그래밍 for 윈도우 95/98 DDK 문서, NT DDK 문서 등을 참고하기 바랍니다.

 참고로 인텔 x86 계열의 프로세서는 세 단계의 특권 레벨(Privilege Level)을 가지고 있으며, ring0, ring3 처럼 불리웁니다. ring0 가 특권 레벨이 가장 높으며, 디바이스 드라이버 등이 ring0 에서 동작합니다.

 

< <Ctrl-Alt-Del>로 프로그램을 종료하지 못하게>

 <Ctrl-Alt-Del>로 프로그램 종료하지 못하게 만들 수 있는 방법을 알고 싶습니다. WM_KEYDOWN을 이용해 입력된 키를 Tab 키로 변환시켜도 실행되던데, 어떻게 해야 하나요?

다음과 같이 해보세요.

SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, NULL, NULL);

그리고  해제할 때는 다음과 같이 합니다.

SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, NULL, NULL);

 여기서 주의할 점은 이건 마이크로소프트에서는 전혀 권장한 적이 없는 변칙적인 방법이라는 것입니다. 이를 제대로 구현하려면 메시지 후킹을 공부해야 합니다.

 

<밀리초를 구현하는 방법>

 현재 시간을 읽어 화면에 표시하는 것을 하고 있는데, 연월일시분초 등을 나타내고 1000분의 1초(milliseconds) 단위도 표현하고 싶은데 어떻게 해야 하는지 방법을 알려주세요.

다음과 같이 하면 됩니다. 볼랜드 C++을 이용한다면 아래의 _timeb 구조체와 _ftime 함수 대신에 timeb 구조체와 ftime 함수를 사용해야 합니다. MS의 컴파일러(예를 들어 Visual C++) 에서는 아래의 코드대로 문제없이 돌아갈겁니다.

/* FTIME.C : This program uses _ftime to obtain the current
 * time and then stores this time in timebuffer.
*/

#include <stdio.h>
#include <sys\timeb.h>
#include <time.h>

void main(void)
{
     struct _timeb timebuffer;
     char *timeline;

     _ftime( &timebuffer );
     timeline = ctime( &(timebuffer.time) );

     printf("The time is %.19s.%hu %s", timeline, timebuffer.millitm,
               &timeline[20] );
}

=== output ===

The time is Tue Mar 21 15:26:41.341 1995

 

<뷰 클래스 변수 제어>

도큐먼트 클래스에서 뷰 클래스의 변수에 값을 바꿔주려고 합니다. 어떻게 해야 하나요.

뷰(View)는 도큐먼트(Doc)의 포인터를 가지고 있어 GetDocument로 도큐먼트의 인자들을 사용할 수 있지만 도큐먼트에서 뷰를 사용하려면 우선 SDI인 경우에는 메인 프레임의 포인터를 얻고 거기서 뷰 클래스의 포인터를 얻어야 합니다.

CView *pView;
pView = (CView*)((CMainFrame*)(AfxGetApp()->m_pMainWnd))->GetActiveView();

MDI인 경우에는

CMDIFrameWnd *pFrame =
     (CMDIFrameWnd*)AfxGetApp()->m_pMainWnd;

CMDIChildWnd *pChild =
     (CMDIChildWnd*)pFrame->GetActiveFrame();

CView *pView = (CView*)pChild->GetActiveView();

위와 같은 방식으로 얻어오면 됩니다. 그러면 서로 참조가 가능합니다.

 

- the end of this article -