.모니터 정보 조사

응용 프로그램이 다중 모니터 환경을 직접 다루거나 다중 모니터로 인한 문제점을 해결하려면 먼저 시스템에 장착된 모니터에 대한 정보를 조사해야 한다. 다음 함수는 시스템의 모든 모니터 또는 특정 DC와 관련된 모니터를 열거한다.

 

BOOL EnumDisplayMonitors(HDC hdc, LPCRECT lprcClip, MONITORENUMPROC lpfnEnum, LPARAM dwData);

 

hdc lprcClip 인수에 대해서는 잠시 후 따로 정리해 보기로 하되 이 값이 둘 다 NULL이면 모든 모니터가 열거된다. lpfnEnum 인수는 모니터가 발견될 때마다 호출될 콜백 함수이며 dwData는 이 함수로 전달될 사용자 정의값이다. 콜백 함수는 다음과 같은 원형을 가진다.

 

BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData);

 

첫번째 인수 hMonitor는 발견된 모니터의 핸들인데 이 핸들로부터 모니터의 상세한 정보를 조사할 수 있다. hdcMonitor는 이 모니터 영역에 그리기를 할 때 사용할 DC 핸들이며 lprcMonitor는 모니터의 좌표값인데 잠시 후 따로 정리하도록 하자. 열거를 계속하려면 TRUE를 리턴하고 열거를 중지하려면 FALSE를 리턴하면 된다. 원하는 모니터를 찾았을 때 FALSE를 리턴하면 모니터 열거가 끝난다. 모니터의 핸들로부터 모니터의 정보를 조사할 때는 다음 함수를 사용한다.

 

BOOL GetMonitorInfo(HMONITOR hMonitor, LPMONITORINFO lpmi);

 

MONITORINFO(EX) 구조체를 선언한 후 이 구조체의 포인터를 전달하면 모니터 관련 정보를 구조체에 채워준다. 이 구조체는 다음과 같이 선언되어 있다.

 

typedef struct tagMONITORINFOEX { 

  DWORD  cbSize;

  RECT   rcMonitor;

  RECT   rcWork;

  DWORD  dwFlags;

  TCHAR  szDevice[CCHDEVICENAME];

} MONITORINFOEX, *LPMONITORINFOEX;


멤버의 의미는 다음과 같다.

 

멤버

설명

cbSize

이 구조체의 크기이며 구조체 버전 확인에 사용된다. GetMonitorInfo 함수를 호출하기 전에 이 멤버에 구조체의 크기를 반드시 대입해 주어야 한다.

rcMonitor

가상 화면상의 모니터 좌표이다. 왼쪽이나 위쪽에 있는 모니터는 음수 좌표를 가질 것이다.

rcWork

가상 화면상의 워크 에리어 좌표이다. rcMonitor 영역에서 앱바 영역을 제외한 영역이다.

dwFlags

주 모니터일 경우 MONITORINFOF_PRIMARY가 전달된다.

szDevice

모니터의 이름이다.

 

다음 예제는 시스템에 장착된 모든 모니터의 정보를 조사해 준다.

 

#define WINVER 0x0500

#include <windows.h>

....

int y;

BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor,HDC hdcMonitor,LPRECT lprcMonitor,LPARAM dwData)

{

   TCHAR sInfo[256];

   MONITORINFOEX mi;

 

   mi.cbSize=sizeof(MONITORINFOEX);

   GetMonitorInfo(hMonitor,&mi);

   wsprintf(sInfo,"모니터 핸들 = %x, 좌표=(%d,%d)-(%d,%d) %s",hMonitor,

      lprcMonitor->left,lprcMonitor->top,lprcMonitor->right,lprcMonitor->bottom,

      (mi.dwFlags & MONITORINFOF_PRIMARY)==0 ? "":" 모니터");

   TextOut((HDC)dwData,10,y,sInfo,lstrlen(sInfo));

   y+=20;

   return TRUE;

}

 

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{

   HDC hdc;

   PAINTSTRUCT ps;

   TCHAR Mes[256];

   int cMon;

   RECT rcVirt;

 

   switch(iMessage) {

   case WM_LBUTTONDOWN:

   case WM_DISPLAYCHANGE:

      InvalidateRect(hWnd,NULL,TRUE);

      UpdateWindow(hWnd);

      return 0;

   case WM_PAINT:

      hdc=BeginPaint(hWnd, &ps);

      cMon=GetSystemMetrics(SM_CMONITORS);

      if (cMon == 0) {

          lstrcpy(Mes,"다중 모니터를 지원하지 않는 시스템입니다");

          TextOut(hdc,10,10,Mes,lstrlen(Mes));

      } else {

          wsprintf(Mes,"모니터 개수 = %d, 색상 포맷=%s",cMon,

             GetSystemMetrics(SM_SAMEDISPLAYFORMAT) ? "모두 동일":"다름");

          TextOut(hdc,10,10,Mes,lstrlen(Mes));

 

          rcVirt.left=GetSystemMetrics(SM_XVIRTUALSCREEN);

          rcVirt.top=GetSystemMetrics(SM_YVIRTUALSCREEN);

          rcVirt.right=rcVirt.left+GetSystemMetrics(SM_CXVIRTUALSCREEN);

          rcVirt.bottom=rcVirt.top+GetSystemMetrics(SM_CYVIRTUALSCREEN);

          wsprintf(Mes,"가상 화면=(%d,%d)-(%d,%d)",rcVirt.left,rcVirt.top,

             rcVirt.right,rcVirt.bottom);

          TextOut(hdc,10,30,Mes,lstrlen(Mes));

 

          y=50;

          EnumDisplayMonitors(NULL,NULL,MonitorEnumProc,(LPARAM)hdc);

      }

      EndPaint(hWnd, &ps);

      return 0;

   case WM_DESTROY:

      PostQuitMessage(0);

      return 0;

   }

   return(DefWindowProc(hWnd,iMessage,wParam,lParam));

}

 

다중 모니터 시스템은 98이후 2000이후에서만 지원된다. 즉 윈도우즈 95 NT는 다중 모니터 환경을 지원하지 않으며 비주얼 C++ 6.0의 기본 컴파일 환경이 윈도우즈 95로 맞추어져 있기 때문에 다중 모니터 관련 함수를 호출하려면 windows.h를 인클루드하기 전에 WINVER 매크로를 0x500 이상으로 정의해 주어야 한다. 두 개의 모니터가 장착된 시스템에서 이 예제를 실행한 결과는 다음과 같다.

모니터의 개수는 SM_CMONITORS 메트릭스로 간단하게 조사할 수 있는데 만약 이 값이 0이라면 아예 다중 모니터를 지원하지 않는 95/NT 시스템이다. 이 값이 1이라면 주 모니터 하나만 있는 상태이고 2이상이면 다중 모니터 환경이라고 할 수 있다. 95/NT 환경에서는 다중 모니터 관련 함수를 호출해서는 안되며 SM_CMONITORS 0을 리턴한다고 해서 장착된 모니터가 없다고 판단해서도 안된다.

모니터들의 색상 포맷 동일성 여부나 가상 화면의 범위는 메트릭스값으로 간단히 조사할 수 있다. 각각의 모니터에 대한 정보를 조사하려면 EnumDisplayMonitor 함수로 모니터 열거를 한 후 콜백 함수에서 조사되는 모니터 핸들에 대해 GetMonitorInfo 함수를 호출해 보면 된다. 예제의 실행 결과를 보면 주 모니터가 왼쪽에 있고 추가 모니터가 오른쪽에 있음을 알 수 있다.

이런 식으로 모니터를 열거해 보면 시스템에 장착된 모니터의 목록과 각 모니터의 특성을 조사할 수 있으므로 이 정보를 바탕으로 자신의 위치나 좌표의 유효성을 점검하면 된다. 다음은 잠시 설명을 보류해 두었던 EnumDisplayMonitors 함수의 두 인수에 대해 정리해 보자. hdc 인수가 전달되면 이 DC 영역이 걸쳐 있는 모니터에 대해서만 조사되며 콜백 함수의 첫번째 인수로 해당 모니터에 출력할 수 있는 DC 핸들이 전달된다. DC 핸들로 그리기를 하면 해당 모니터의 특성에 맞는 커스텀 그리기를 할 수 있다.

EnumDisplayMonitors 함수의 두번째 인수 lprcClip은 클리핑 영역을 지정하는데 이 영역이 걸쳐 있는 모니터에 대해서만 조사된다. 작업 영역의 일부만 그리기를 할 때 클리핑 영역을 지정하면 콜백 함수의 lprcMonitor 인수로 클리핑 영역과 겹쳐지는 모니터 영역이 전달되므로 이 영역에 대해서만 그리기를 하면 된다.

이 두 인수가 모두 NULL이면 장착된 모든 모니터에 대한 정보가 조사되며 이 두 인수중 하나 또는 전부가 전달될 경우 조사되는 정보가 달라진다. 다음 도표는 EnumDisplayMonitors 함수의 두 인수값의 조합과 조사되는 정보들을 정리한 것이다.

 

hdc

lprcClip

정보

NULL

NULL

모든 모니터의 정보를 조사하며 콜백 함수로는 NULL DC가 전달된다.

NULL

NULL

클리핑 영역과 교차되는 모니터 영역이 조사된다. 이때 클리핑 영역과 콜백 함수가 전달받는 좌표값은 모두 가상 화면 좌표이다.

NULL

NULL

DC와 교차되는 모니터 영역이 조사된다. 콜백 함수는 각 모니터 영역에 해당하는 DC값을 받는다.

NULL

NULL

DC의 클리핑 영역과 교차되는 모니터 영역이 조사된다. 이때 클리핑 영역과 콜백 함수가 전달받는 좌표값은 모두 윈도우 좌표이다. 콜백 함수는 각 모니터 영역에 해당하는 DC값을 받는다.

 

 MonitorInfo 예제에서 이 두 인수값을 변경해 가며 테스트해 보면 어떤 정보가 조사되는지 알 수 있을 것이다. 이 정보들을 사용하는 예제는 바로 다음 항에서 작성해 볼 것이다.

다중 모니터 환경은 시스템을 부팅할 때 결정되는 것이 아니라 사용자에 의해 언제든지 변경될 수 있다. 이때마다 시스템은 모든 탑 레벨 윈도우에게 WM_DISPLAYCHANGE 메시지를 보내 주는데 모니터 구성에 따라 동작이 달라져야 한다면 이 메시지를 처리해야 한다. 시스템은 모니터 구성이 바뀔 때 주 모니터에 있지 않은 윈도우를 주 모니터 안으로 이동시켜 주므로 이 메시지를 꼭 처리해야 할 필요는 거의 없다.