본문 바로가기

3.구현/VC++

[수정중]윈도우에서 GetWindowRect, GetCllientRect를 통해 크기 얻기

작성자 : ospace114@엠팔.컴

Window의 컨트롤이나 핸들러의 크기나 위치를 관리하기 위한 API를 살펴보겠다. 일단 컨트롤의 위치 값을 얻는 API는 두 가지가 있다. GetWindowRect와 GetClientRect이다.

  • GetWindowRect: 윈도우 화면 좌표 값으로 위치 정보 반환한다.
  • GetClientRect: 윈도우 클라이언트 영역에서의 좌표값 반환한다.

일단 윈도우의 좌표 표시는 일반적으로 사용하는 수학의 좌표와는 조금 틀리다. 수학에서 X, Y축의 증가하는 값는 각각 오른쪽과 위쪽이다. 그러나 윈도우에서는 Y축의 방향이 반대인 아래로 향한다.

그렇기에 Y축의 값을 계산할 때 주의를 요한다.

다음으로는 화면 좌표간의 변환를 사용하는 API를 보겠다. ScreenToClient와 ClientToScreen 함수이다.

  • ScreenToClient: 화면 좌표를 특정 윈도우의 클라이언트 영역으로 좌표 변환한다.
  • ClientToScreen: 클라이언트 영역의 좌표를 화면 좌표로 변환한다.

마지막으로 컨트롤의 위치를 수정하는 API는 MoveWindow와 SetWindowPos 함수가 있다.

  • MoveWindow: 특정 윈도우의 위치를 이동. Top-level 윈도우는 화면좌표로, child 윈도우는 부모의 왼쪽상단을 기준으로 상대적으로 이동한다.
  • SetWindowPos: 이는 윈도우의 위치뿐만 아니라 z-order로 변경한다.

먼저 윈도우의 좌표 값을 얻는 것을 먼저 보고, 다음으로 위치를 이동하는 API를 살펴보겠다.

윈도우 좌표값 획득

GetClientRect를 사용

아래에서 Box 컨트롤의 ID가 IDC_BOX이라고 했을 경우, 이에 대한 GetClientRect를 사용한 예제를 보자.

HWND hwndBox = ::GetDlgItem(this->m_hWnd, IDC_BOX);
RECT rc2;
::GetClientRect(hwndBox, &rc2);

우리가 필요한 값은 rc2의 RECT 구조체로 저장된 값이다. RECT 구조체에는 LONG형으로 값이 저장되어 있으며 아래와 같다.

// windef.h
typedef struct tagRECT
{
  LONG    left;
  LONG    top;
  LONG    right;
  LONG    bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

자신(버튼)이 기준 좌표로 보고 계산된 값이다. 결과를 보면 rc2.left와 rc2.top이 0 값이 된다. 이는 Box의 Client영역이 Box 내부 자체로 한정되기 때문이다. 즉, Box의 왼쪽위단의 좌표가 (0,0)으로 시작한다는 의미이다. 즉,

{rc2.top, rc2.bottom, rc2.left, rc2.right} ==> {0, rc2.bottom, 0, rc2.right}

다음으로 Test 윈도우의 좌표 값을 얻어보자.

RECT rc4;
::GetClientRect(this->m_hWnd, &rc4);

결과는 rc4.left와 rc4.top의 값이 0이된다. 즉 Test 윈도우 자체가 지역변수가 된다.

{rc4.top, rc4.bottom, rc4.left, rc4.right} ==> {0, rc4.bottom, 0, rc5.right}

GetWindowRect를 사용한 예제

이번에는 GetWindowRect를 사용한 예제를 보자. 결과를 말하면 전체 화면 좌표에 대한 좌표값을 돌려준다.

HWND hwndBox = ::GetDlgItem(this->m_hWnd, IDC_BOX);
RECT rc1;
::GetWindowRect(hwndOption, &rc1);

위와 같이 했을 경우 rc1결과는

{rc1.top, rc1.bottom, rc1.left, rc1.right }

으로 된다. 곧, Box의 위치 정보를 지역좌표가 아닌 현재 윈도우에서 전체 좌표를 구한다. 그럼 Test 윈도우의 좌표를 얻어보자.

RECT rc3;
::GetWindowRect(this->m_hWnd, &rc3);

rc3의 결과는

{rc3.top, rc3.bottom, rc3.left, rc3.right}

윈도우 좌표 변환

ScreenToClient를 사용해보기

이건 약간 복잡하지만, 한번 보도록 하자. 먼저 아래 문제에 대해 생각을 해보자.

  • Q: 앞에서 Box의 위치를 자신이 아닌 현재 부모 윈도우에서 구하려면 어떻게 할까?
  • Q: 현재 윈도우 좌표를 GetWindowRect로 구했는데, 이는 전체 윈도우다. 그럼 상하좌우 테두리와 상단 타이틀 크기를 뺀 실제 사용할 수 있는 화면 크기는 어떻게 구할까?

ScreenToClient 함수는 화면 좌표에서 Client 좌표로 변경하는 함수이다. 실제 우리가 원하는 것은 Box의 클라이언트 지역좌표로 하는 값을 구하고 싶다. 그래야 코딩하고 있는 윈도우에서 정확한 버튼의 상대 좌표값을 지정할 수 있다. 이때 GetWindowRect와 함께 추가로 ScreenToClient를 사용한다.

ScreenToClient는 현재 윈도우 좌표 값을 해당 화면 클라이언트 영역의 좌표로 바꿔준다.
먼저 Box의 좌표를 보도록 하자.

아래는 MSDN에 있는 ScreenToClient 형식이다.

BOOL ScreenToClient(
  HWND hWnd,        // handle to window
  LPPOINT lpPoint   // screen coordinates
);

간단한 사용 예를 보자.

POINT ptTopLeft;
ptTopLeft.x = rcOption.left;
ptTopLeft.y = rcOption.top;
ScreenToClient(hwndScreen, &ptTopLeft);

POINT ptBottomRight;
ptBottomRight.x = rcOption.right;
ptBottomRight.y = rcOption.bottom;
ScreenToClient(hwndScreen, &ptBottomRight);

위에서 ptTopLeft와 ptBottomRight의 각각에 x, y에 변경된 지역 좌표가 값이 들어간다.
ScreenToClient가 RECT에 대한 처리가 없기에 이에 대한 간편한 함수를 하나 만들어도 된다.

그림이 좀 지저분하지만 수정하기 귀차니즘으로 인해서 그런거니 이해해주길 바란다. 여기서 중요한 것은 지저분한 것이아니기 때문이다. ㅡ.ㅡㅋ

Box의 GetWindowRect로 구한 좌표가 rc1이다. 이를 ScreenToClient로 변환하면 rc1_c 좌표가 된다. 여기서 ScreenToClient로 변환시 hWnd는 Test 대화상자의 윈도우 핸들러이다. 그럼 Test 대화상자의 클라이언트 영역을 로컬좌표로 보고 여기에 맞게 Box의 좌표가 계산되어 진다.

위그림에서 파란색 박스가 Test대화상자의 클라이언트 영역이다. 이 영역의 좌측 상단의 좌표가 {0,0}이다.

Test 대화상자에 대한 좌표 변환도 해보자.

이때는 주의해서 볼 것이 Test대화상자 크기가 Test 대화상자 클라이언트 영역을 벗어나게 된다. 이 부분을 주의해야한다. Test 대화상자의 화면 좌표인 rc3를 ScreenToClient로 변환하면 rc3_c 좌표가 된다.

위 그림에서 rc3_c.left와 rc3_c.top의 위치가 클라이언트 영역을 벗어나기에 계산된 값은 음수가 된다.

ClientToScreen

ClientToScreen는 ScreenToClient와 정반대의 기능을 하는 함수이다. 클라이언트 영역의 좌표 값을 화면 좌표 값으로 변환한다.

BOOL ClientToScreen(
    HWND hWnd, // handle to window 
    LPPOINT lpPoint // screen coordinates );

hWnd에는 속한 클라이언트 영역이 있는 윈도우 핸들러이다. 앞의 예제에서 보면 Test 대화상자의 윈도우 핸들러이다.

여기서 잠깐 질문! Box에서 GetClientRect로 얻은 rc2값을 ClientToScreen으로 화면 좌표로 변경할 때 사용할 윈도우 핸들러(hWnd)는 무엇일까?

....

답은 Box의 윈도우 핸들러이다. 잘 생각해보면 Box자체가 클라이언트 영역으로 사용된 것이다.

예제는 따로 없다. 앞의 내용을 참조하길 바란다.

윈도우 위치 이동

MoveWindow 사용

윈도우를 원하는 좌표위치로 이동한다. 이때 이동하는 좌표 범위는 자신이 속한 부모 윈도우의 좌표를 따른다.

예를 들면 Test 대화상자 전체 위도우에 속해 있으므로 윈도우의 화면 좌표에 따라 이동한다. 그러나 Box 컨트롤은 Test 대화상자에 속해있기에 Test 대화상자 클라이언트 영역 좌표 내에서 이동한다. 그렇기에 Box 컨트롤의 위치 이동은 주의를 기울여야한다.

이번은 예제 순서를 변경해서 Test 대화상자 이동 예을 보도록 하자. 이유는 예제가 더 쉽기 때문이다.

RECT rc3;
::GetWindowRect(m_hWnd, &rc3);
::MoveWindow(m_hWnd, rc3.left-20, rc3.top-20, rc3.right-rc3.left, rc3.bottom-rc3.top);

추가로 MoveWindow에 의해서 좌표 위치뿐만 아니라 윈도우의 크기도 변경할 수 있다.

다음 예제로 Box 컨트롤을 보자. Box 컨트롤 이동은 중간에 좌표 변환 작업이 필요하다. 순서는

Box 화면 좌표 값을 얻는다.

  1. Box의 화면 좌표 값을 Test 대화상자의 클라이언트 영역 좌표로 변환한다.
  2. 변환된 좌표를 이용해서 Box의 위치를 이동한다.

앞의 Test 대화상자 이동과 비교하면 B의 과정이 추가되었다.

RECT rc1;
HWND hwndBox = ::GetDlgItem(this->m_hWnd, IDC_BOX);
::GetWindowRect(hwndBox, &rc1);

POINT rc1LT = {rc1.left, rc1.top};
POINT rc1RB = {rc1.right, rc1.bottom};

::ScreenToClient(m_hWnd, &rc1LT);
::ScreenToClient(m_hWnd, &rc1RB);

RECT rc1_c = {rc1LT.x, rc1LT.y, rc1RB.x, rc1RB.y};

::MoveWindow(m_box.m_hWnd, rc1_c.left-10, rc1_c.top-10, rc1.right-rc1.left, rc1.bottom-rc1.top, TRUE);

SetWindowPos 사용

MoveWindow 함수는 윈도우 이동과 크기 조절만 가능하지만 SetWindowPos는 윈도우의 z-order를 조절 할 수 있고 윈도우에 대한 제어도 가능하다.

BOOL SetWindowPos(  
    HWND hWnd,     HWND hWndInsertAfter,     int X,     int Y,     int cx,     int cy,     UINT uFlags );

이에 대한 예제는 차후에 다루도록 하겠다.

MapWindowPoints도 사용해보기

윈도우 미러링하는 경우는 MapWindowPoints를 사용하는 것이 좋다. 앞에 ScreenToClient는 기본적으로 입력되는 좌표값은 전체 윈도우로 고정되었다. 어떤 컨트롤을 다른 특정 컨트롤로로 좌표 값을 변경하고 싶다면 MapWindowPoints를 사용하는게 좋다. 더 좋은 것은 이 API는 여러 포인터 값을 동시에 처리할 수 있다.
아래는 MSDN에 있는 MapWindowPoints의 예제이다.

RECT        rc[10];
for(int i =0; i < (sizeof(rc)/sizeof(rc[0])); i++)
{
      MapWindowPoints(hWnd1, hWnd2, (LPPOINT)(&rc[i]), (sizeof(RECT)/sizeof(POINT)) );
}

History

2008.10.13 GetWindowRect와 ScreenToClient부분 내용에 오류가 있어서 수정함.
2009.05.25 전체적인 내용을 수정과 그림의 틀린 부분도 같이 수정함.

반응형