[Win32] 6. GDI 시스템 이해(2)

마우스 클릭한 위치에 사각형 그리기

클릭으로 네모만들기

마우스를 좌클릭을 이용해서 네모를 찍어낼거다.
좌클릭을 하려면 마우스클릭에 관한 메시지를 프로시져가 받아온다.

  • 발생하는 메시지를 처리한다.
  • 그림을 그리기위한 DC핸들러를 받아온다. DC 핸들러는 사용하면 무조건 제거해줘야한다.
  • 네모를 한번 시범적으로 그려본다.

클릭한 위치에 네모만들기

좌표를 얻으려면 WM_LBUTTONDOWN 메시지가 전달될때,
메세지 내에 lParam 에는 마우스 클릭 좌표가 같이 전달된다.

  • lParam(32비트) : |Y좌표(16비트)|X좌표(16비트)|
int x = lParam & 0x0000FFFF;
int y = (lParam>>16) & 0x0000FFFF;
  • AND 비트연산 과 쉬프트연산을 이용하여 쉽게 값을 얻을 수 있다.
int x = LOWORD(lParam);
int y = HIWORD(lParam);
  • 매크로함수를 이용하여 하위비트, 상위비트를 받아와도 된다.
#include <windowsx.h>
int x = GET_X_LPARAM(lParam);
int y = GET_Y_LPARAM(lParam);
  • 이런 특수한 매크로함수를 사용해도된다.

코드구현

마우스커서에서 네모가 그려지지만, 커서를 중심으로 네모를 그리고싶다면 네모가 그려지는 함수를 이렇게 한번 만들어보자

Rectangle(h_dc, cursor_x-50, cursor_y-50, cursor_x+50, cursor_y+50);

WM_PAINT 메시지에 대하여

윈도우프로그램의 창을 옮기다보면 안보일 화면밖을 나가서 안보일수도 있다.

이렇게 안보일때 WM_PAINT라는 메시지를 메시지큐에 전달하여 Window에 그림을 다시그려야함.

Window의 클라이언트 영역


저 주황색으로 슥삭 한 곳이 클라이언트 영역이다.

무효화 영역과 다시그리기

GetDC로 DC핸들러를 가져다 그림을 열심히 그려도, 특정 상황에 빠지면 그림은 지워지고 복구도 안된다.

무효화 영역

  • 모니터 밖.
  • 그림이 모니터밖으로 나가면 지워진다.

복구는 어떻게 하냐?

  • WM_PAINT 메시지를 받아서 처리한다.
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM, LPARAM lParam)
{
	if(uMsg == WM_PAINT){
    	// 윈도우의 일부 또는 전부의 그림을 그려야하는 경우.
    } else if(uMsg == WM_DESTROY) PostQuitMessage(0);
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
    
}
  • 다시 DC를 얻어서 그림을 그려야한다.
  • 일부 그림만 그려야하는 상황이 대다수이기 때문에 BeginPaint()함수를 사용한다.

PAINTSTRUCT 구조체

// WinUser.h
typedef struct tagPAINTSTRUCT{
	HDC hdc;
    BOOL fErase;		// BeginPaint 함수로 얻은 DC핸들 값
    RECT rcPaint;		// 윈도우의 기본 배경을 다시 그릴지 여부
    BOOL fRestore;		// 다시 그려야할 영역 정보
    BOOL fInUpdate;			// OS에 의해 내부적으로 사용됨
    BOOL fIncUpdate;		// OS에 의해 내부적으로 사용됨
    BYTE rgReserved[32];	// OS에 의해 내부적으로 사용됨
} PAINTSTUCT
  • fErase 값이 0 이 아니면 윈도우 배경을 다시 그려야 한다는 뜻
    • 이땐 WM_ERASEBKGND 메시지 처리를 받아 그려야함.

BeginPaint 함수를 사용하여 DC 얻기

PAINTSTRUCT ps;
Hdc h_dc = BeginPaint(hWnd, &ps);
  • DC의 핸들값을 얻을 수 있음.

EndPaint 함수를 사용하여 DC 반환

EndPaint(hWnd, &ps);
  • DC 사용이 끝나면 꼭 반환해야함.

이제 WndProc함수에 이렇게 추가하면 됨

  • 근데 WM_PAINT 처리작업이 중복되지 않기 때문에 WM_PAINT메시지를 직접 처리했다면 DefWindowProc함수가 호출되지 않도록 return 0으로 WndProc함수를 종료해야함.
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if(uMsg == WM_PAINT)// 윈도우의 일부 혹은 전부 그림을 그려야할 경우.
    {
    	PAINTSTRUCT ps;
        HDC H_DC = BeginPaint(hWnd, &ps);
        // 그리는 작업!!!
        EndPaint(hWnd, &ps);
        
        // WM_PAINT 메시지를 직접 처리했지 때문에 DefWindProc 함수가
        // 호출되지 않게 이 시점에 WndProc 함수를 종료한다.
        return 0;
     } else if(uMsg = WM_DESTORY) PostQuitMessage(0);
     return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

WM_PAIT 메시지에서 사각형 그리는 코드를 추가한 경우

PAINTSTRUCT ps;
HDC h_dc = BeginPaint(hWnd, &ps);

// h_dc를 이용하여 사각형을 그린다.
Rectangle(h_dc, 10, 10, 100, 100);

EndPaint(hWnd, &ps);

처리작업과 보여주는 작업

실제 프로그래밍 시에는 처리작업순위를 보여주는작업보다 중요하게 본다.

윈도우 운영체제의 메시지 처리 방식

  • 윈도우에서는 위의 우선순위 원칙을 이미 따르고있음.
  • WM_PAINT와 같은 낮은 우선순위들을 Flag메시지 아닌, 메시지 테이블로 관리함.

WM_PAINT메시지에서 GetDC를 사용한다면?

  • CPU 터진다. 쓰지말라!
  • 절대 WM_PAINT 메시지루틴에서는 GetDC를 사용하지 말라!!!!


Window가 생성되었음을 알려주는 메시지

CreateWindow() 를 사용하여 윈도우 생성을 할 수 있음.
근데 이 윈도우는 OS가 관리하기 때문에 접근이 어려움.
개발자가 추가적인 접근을 할 수 있도록 받을 수 있는 메시지가 WM_CREATE메시지 이다. 이 메시지를 받으면 작업하면됨.

WM_CREATE

  • 이 메시지를 받을땐 윈도우 생성이 마친상태가 아니라, 만들어졌지만 보여지기 직전임.
  • 그래서 이 메시지를 받고 우리 개발자들이 윈도우를 만져줘야함.
  • WndProc 조건문을 추가해서 메시지를 사용하면 됨
    • return 이 -1 이면 문제가 있어 종료하는 표시
    • return 이 0 이면 잘 만들어 졌다는 뜻으로 해석함
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if(uMsg == WM_CREATE){
    	// 새로운 윈도우가 만들어졌고 출력직전 상태.
        return 0;// 정상적으로 작업이 완료됨.
    } else if(uMsg == WM_DESTROY) PostQuitMessage(0);
    return DefWindowProc(hWnd, uMsgm wParam, lParam);
}

WM_CREATE메시지와 함께 전달되는 wParam은 별내용없고, lParam에는 윈도우 생성시 사용된 설정정보의 시작주소가 저장됨

  • 새로운 윈도우의 생성정보를 얻으려면 CREATESTRUCT 구조체로 형변환 하여 사용하면 됨
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if(uMsg == WM_CREATE){
    	// 새로운 윈도우가 만들어졌고 출력직전 상태.
       	CREATESTRUCT *p_create_type = (CREATESTRUCT *)lParam;
        // p_create_type을 사용하여 윈도우 생성정보를 사용하면됨.
        return 0;// 정상적으로 작업이 완료됨.
    } else if(uMsg == WM_DESTROY) PostQuitMessage(0);
    return DefWindowProc(hWnd, uMsgm wParam, lParam);
}

WM_CREATE 용도

윈도우가 최초 생성될 때 한번만 전달하는 메시지이다.

프로그램의 기본적인 초기화 작업 용도로 사용됨.


BitBlt함수

우리가 많이 사용하는 비트맵은 DDB 이다.
비트패턴에 형식이 조금만 틀려도 큰일나는 민감한 친구들을 다룬다는 말임.

  • BitBlt() : Windows OS에서 제공하는 비트맵객체의 비트패턴을 다른 비트맵에 복사하는 API 함수

BitBlt()

DC 핸들러를 사용해서 비트맵의 그림을 복사해보자.

#include <wingdi.h>
#include <gdi32.lib>

BOOL BitBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, 
			HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop);

BitBlt() 매개변수

  • hrdSrc (Source) : 원본DC
  • hrdDest (Destination) : 사본DC
  • nXSrc, nYSrc : 원본그림에서 복사를 시작할 좌표
  • nXDest, nYDest : 사본 비트맵에 원본 그림이 복사될 시작좌표
  • nWidth, nHeight : 사본그림에 복사될 원본그림의 폭,높이
  • dwRop : 그림을 복사하는 방법을 설정하는 기능.
    • SRCCOPY : 그냥 복사
    • SRCNVERT, 기타등등 : 여러특수기능

BitBlt() 함수는 하드웨어 지원을 받아서 사용해서 성능도 굉장히 좋다.

BitBlt 함수를 이용한 예제

HDC h_screen_dc = ::GetDC(NULL);	// 모니터 전체 화면용 DC를 얻음
HDC h_dc = ::GetDC(m_hWnd);			// 현재 윈도우용 DC를 얻는다.

BitBlt(h_dc, 10, 10, 500, 300, h_screen_dc, 0,0,SRCOPY);

::ReleaseDC(m_hWnd, h_dc);		// 사용하던 DC를 해제한다.
::ReleaseDC(NULL, h_screen_dc);	// 사용하던 DC를 해제한다.

CreateCompatibleDC()

DC를 Window에 만들어진 DC만 사용가능한게 아님. CreateDC를 이용해서 DC를 직접만들어 사용가능함. 그 DC를 좀더 쉽게 만들기 위해 사용하는 함수.

DC를 만드는 또 다른 방법

HDC h_dc = ::GedDC(m_hWnd); // m_hWnd 윈도우에 그림을 그리는 DC
HDC h_screen_dc = ::GetDC(NULL); // 화면 전체에 그리는 DC

여태 이렇게 GetDC로 DC를 얻어와서 그려왔음.

CreateCompatibleDC() : DC의 특성 대부분을 가지고있지만, 출력장치와 연결안된 DC를 생성하는 함수

CreateCompatibleDC함수에 대해

HDC CreateCompatibleDC(HDC hdc);

  • 어떤 DC와 호환(Compatible)되는 DC를 만드는 방식
  • 이거로 생성한 DC는 DeleteDC()로 제거해야함
HDC h_c = ::GetDC(m_hWnd);	// 윈도우에 그림을 그리는 DC를 얻는다.
HDC h_mem_dc = CreateCompatibleDC(h_dc);// h_dc와 호환되는 DC를 생성

// h_mem_dc와 연결된 비트맵에 사각형을 그린다. 이그림은 출력되지 않음
Rectangle(h_mem_dc, 10, 10, 100, 100);

DeleteDC(h_mem_dc);
::ReleaseDC(m_hWnd, h_dc); 

반드시 비트맵 객체를 연결하자

CreateCompatibleDC()를 이용해서 얻은 DC는비트맵 객체가 연결은 되어있는데 제대로된 객체가 아님.

  • CreateCompatibleDC()로 생성한 DC를 사용하려면 비트맵 객체를 만들어서 연결해야함
HDC h_dc = ::GetDC(m_hWnd);
HDC h_mem_dc = CreateCompatibleDC(h_dc);

// h_dc 와 호환되는 비트맵을 폭 400, 높이 300의 크기로 만듦
HBITMAP h_mem_bmp = CreateCompatibleBitmap(h_dc, 400, 300);
// h_mem_dc에 h_mem_bmp을 연결함.
SelectObject(h_mem_dc, h_mem_bmp);

// h_mem_dc와 연결된 h_mem_bmp에 사각형을 그린다. 이그림은 출력안됨
Rectangle(h_mem_dc, 10, 10, 100, 100);

DeleteObject(h_mem_bmp);
DeleteDC(h_mem_dc);
::ReleaseDC(m_hWnd, h_dc);

결론

  • 화면 출력용 DC는 복잡한 그림을 그리기위해서 화면이 깜빡이는 현상이 발생함
  • 그걸 줄이기 위해 위에서 만든 보이지않는 Memory DC를 생성해서 그림을 출력하면 깜빡임현상이 발생하지않음
  • 더블 버퍼링(Duble uffering)
    • BitBlt()는 Memory DC와 화면 출력 DC를 사용하여 비트맵에 그려진 그림을 복사하여 화면에 출력가능함
    • 이방법으로 깜빡임을 줄이는 곳에 많이 사용함.

좋은 웹페이지 즐겨찾기