.모니터 위치 조사

다음 세 함수는 특정 오브젝트가 어떤 모니터위에 있는지 조사해 준다. 한 점에 대해, 사각 영역에 대해, 윈도우에 대해 조사할 수 있으며 이 오브젝트가 있는 모니터의 핸들이 리턴된다.

 

HMONITOR MonitorFromPoint(POINT pt, DWORD dwFlags);

HMONITOR MonitorFromRect(LPCRECT lprc, DWORD dwFlags);

HMONITOR MonitorFromWindow(HWND hwnd, DWORD dwFlags);

 

dwFlags는 오브젝트가 어떤 모니터 위에도 있지 않을 때, 예를 들어 수평으로 배치된 상태에서 모니터의 위쪽 좌표나 대각선으로 배치된 상태에서 오른쪽 좌표에 있을 때 어떤 값을 리턴할 것인가를 지정한다. 다음 셋 중 하나를 선택할 수 있다.

 

플래그

설명

MONITOR_DEFAULTTONEAREST

가장 가까운 모니터

MONITOR_DEFAULTTONULL

NULL

MONITOR_DEFAULTTOPRIMARY

주 모니터

 

점의 경우는 어느 모니터위에 있는지 또는 모니터 밖인지를 명확하게 판단할 수 있지만 사각영역이나 윈도우의 경우는 두 모니터의 경계에 걸칠 수도 있는데 이런 경우는 많이 속해있는 쪽 모니터가 선택된다. 다음 예제는 윈도우가 있는 모니터에 대한 정보를 보여 준다.

 

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

{

   HDC hdc;

   PAINTSTRUCT ps;

   TCHAR Mes[256];

   HMONITOR hMonitor;

   MONITORINFOEX mi;

 

   switch(iMessage) {

   case WM_MOVE:

      InvalidateRect(hWnd,NULL,TRUE);

      UpdateWindow(hWnd);

      return 0;

   case WM_PAINT:

      hdc=BeginPaint(hWnd, &ps);

      hMonitor=MonitorFromWindow(hWnd,MONITOR_DEFAULTTONEAREST);

 

      mi.cbSize=sizeof(MONITORINFOEX);

      GetMonitorInfo(hMonitor,&mi);

      wsprintf(Mes," 윈도우가 있는 모니터:%x%s, 가상 좌표=(%d,%d)-(%d,%d)",

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

          mi.rcMonitor.left,mi.rcMonitor.top,mi.rcMonitor.right,mi.rcMonitor.bottom);

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

      EndPaint(hWnd, &ps);

      return 0;

   case WM_DESTROY:

      PostQuitMessage(0);

      return 0;

   }

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

}

 

모니터의 핸들을 구한 후 이 모니터가 주 모니터인지, 가상 화면상의 좌표는 어디쯤인지를 조사해 작업영역으로 출력했다. 사용자는 윈도우를 원하는 모니터로 자유롭게 이동시킬 수 있고 윈도우의 입장에서 자신이 어떤 모니터위에 있는지는 그다지 중요한 사항이 아니다. 왜냐하면 윈도우는 모니터라는 물리적인 장치보다는 데스크탑이라는 논리적인 영역에 자신이 속해있다고 생각하기 때문이다. 그래서 사실 자신이 위치해 있는 모니터를 조사해야 할 경우는 그리 흔하지 않다.

다음 예제는 다중 모니터 시스템일 경우 윈도우를 배치할 모니터를 선택하는 방법을 보여준다. 그래픽 편집 프로그램의 정보 팔레트, 디버깅중의 와치 정보, 주식 프로그램의 주식 시황같이 사용자의 조작은 필요없고 정보를 보여주기만 하는 윈도우라면 주 모니터보다 보조 모니터에 열리는 것이 더 좋을 것이다. 참고 정보를 보조 모니터에 보여줌으로써 주 모니터의 공간을 더 많이 쓸 수 있도록 해 준다는 점에서 실용적이다.

다음 예제는 메인 윈도우 하나와 시간을 보여 주는 차일드 윈도우를 만드는데 차일드는 보조 모니터에 열리도록 한다. 물론 다중 모니터 시스템일 때만 이렇게 윈도우를 배치하고 모니터가 하나뿐이라면 두 윈도우 모두 주 모니터에 열려야 한다.

 

#define WINVER 0x0500

#include <windows.h>

 

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

LRESULT CALLBACK ChildProc(HWND,UINT,WPARAM,LPARAM);

HINSTANCE g_hInst;

HWND hWndMain;

LPCTSTR lpszClass=TEXT("UseDual");

 

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance

     ,LPSTR lpszCmdParam,int nCmdShow)

{

   HWND hWnd;

   MSG Message;

   WNDCLASS WndClass;

   g_hInst=hInstance;

  

   WndClass.cbClsExtra=0;

   WndClass.cbWndExtra=0;

   WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);

   WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);

   WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);

   WndClass.hInstance=hInstance;

   WndClass.lpfnWndProc=(WNDPROC)WndProc;

   WndClass.lpszClassName=lpszClass;

   WndClass.lpszMenuName=NULL;

   WndClass.style=CS_HREDRAW | CS_VREDRAW;

   RegisterClass(&WndClass);

 

   WndClass.hbrBackground=(HBRUSH)(COLOR_BTNFACE+1);

   WndClass.lpfnWndProc=(WNDPROC)ChildProc;

   WndClass.lpszClassName="ChildWnd";

   RegisterClass(&WndClass);

 

   hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,

      CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,

      NULL,(HMENU)NULL,hInstance,NULL);

   ShowWindow(hWnd,nCmdShow);

   hWndMain=hWnd;

  

   while(GetMessage(&Message,0,0,0)) {

      TranslateMessage(&Message);

      DispatchMessage(&Message);

   }

   return (int)Message.wParam;

}

 

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

{

   HDC hdc;

   PAINTSTRUCT ps;

   TCHAR Mes[]="메인 윈도우, 모니터에 나타납니다.";

 

   switch(iMessage) {

   case WM_CREATE:

      CreateWindow("ChildWnd","ChildWnd",WS_OVERLAPPEDWINDOW,

          CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,

          NULL,(HMENU)NULL,g_hInst,NULL);

      return 0;

   case WM_PAINT:

      hdc=BeginPaint(hWnd, &ps);

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

      EndPaint(hWnd, &ps);

      return 0;

   case WM_DESTROY:

      PostQuitMessage(0);

      return 0;

   }

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

}

 

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

{

   MONITORINFOEX mi;

 

   mi.cbSize=sizeof(MONITORINFOEX);

   GetMonitorInfo(hMonitor,&mi);

   if ((mi.dwFlags & MONITORINFOF_PRIMARY)==0) {

      (*(LPRECT)dwData)=*lprcMonitor;

      return FALSE;

   } else {

      return TRUE;

   }

}

 

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

{

   HDC hdc;

   PAINTSTRUCT ps;

   TCHAR Mes[]="보조 윈도우, 추가 모니터에 나타납니다.";

   TCHAR szTime[32];

   SYSTEMTIME st;

   RECT rt;

 

   switch(iMessage) {

   case WM_CREATE:

      if (GetSystemMetrics(SM_CMONITORS) > 1) {

          EnumDisplayMonitors(NULL,NULL,MonitorEnumProc,(LPARAM)&rt);

          MoveWindow(hWnd,rt.left,rt.top,500,300,FALSE);

      }

      ShowWindow(hWnd,SW_SHOW);

      SetTimer(hWnd,1,1000,NULL);

      return 0;

   case WM_TIMER:

      InvalidateRect(hWnd,NULL,TRUE);

      return 0;

   case WM_PAINT:

      hdc=BeginPaint(hWnd, &ps);

      SetBkMode(hdc,TRANSPARENT);

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

      GetLocalTime(&st);

      wsprintf(szTime,"현재 시간 = %d:%d:%d",st.wHour,st.wMinute,st.wSecond);

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

      EndPaint(hWnd, &ps);

      return 0;

   case WM_DESTROY:

      KillTimer(hWnd,1);

      PostQuitMessage(0);

      return 0;

   }

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

}

 

WinMain에서 차일드를 위한 윈도우 클래스를 추가로 등록하되 배경색상을 회색으로 설정하여 메인 윈도우와 구분이 되도록 했다. 메인 윈도우의 WM_CREATE에서 차일드를 생성하되 특별히 모니터를 지정하거나 하지는 않았다. 여기까지는 일반적인 차일드 생성 과정과 같으며 차일드 스스로가 보조 모니터를 찾아가도록 하였다.

차일드는 WM_CREATE에서 모니터를 열거하되 사용자 정의 데이터로 RECT형 포인터 변수 rt를 넘겨 주었다. 열거 함수에서는 주 모니터가 아닌 보조 모니터가 발견되면 이 모니터의 영역을 사용자 정의 데이터에 복사해 주고 FALSE를 리턴하여 열거를 중지한다. 열거가 끝난 후 차일드는 MoveWindow 함수로 보조 모니터의 좌상단에 폭 500, 높이 300의 크기로 이동하였다. 실행 결과는 다음과 같다.

이런 모니터 배치는 어디까지나 프로그램을 시작할 때의 초기 배치일 뿐이며 프로그램이 실행된 후 사용자는 보조 모니터에 있는 차일드를 주 모니터로 가져오거나 메인 윈도우를 보조 모니터로 보낼 수도 있다. 어디까지나 서비스 차원에서 윈도우의 초기 배치를 사용자가 쓰기 편하도록 맞추어 주었을 뿐이다. 윈도우가 자신이 실행될 모니터를 선택하는 간단한 예를 보였는데 좀 더 코드를 추가하면 훨씬 더 지능적으로 동작하도록 만들 수도 있다.

만약 보조 모니터가 오른쪽이 아닌 왼쪽에 있다면 차일드를 좌상단에 열지 않고 우상단에 열 수도 있으며 보조 모니터가 여러 개일 경우 그 중 한 모니터를 선택할 수도 있다. 이때는 보조 모니터의 가상 좌표를 비교해 보고 주 모니터와 가장 가까운 모니터를 선택하는 것이 합리적일 것이다. 정보창이 많을 경우 어떤 차일드를 어떤 모니터에 열 것인지 사용자가 미리 지정할 수 있도록 옵션을 제공하는 것도 가능하다.

물론 이런 모든 서비스들은 다중 모니터 환경일 때에만 동작해야 한다. 모니터가 하나뿐이면 모니터를 선택할 여지가 없으므로 모든 윈도우는 주 모니터에 열릴 수밖에 없다. 그래서 차일드의 WM_CREATE에서 시스템에 장착된 모니터가 1보다 큰지를 먼저 점검한 후 열거를 시작한다.