[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를 사용하여 비트맵에 그려진 그림을 복사하여 화면에 출력가능함
- 이방법으로 깜빡임을 줄이는 곳에 많이 사용함.
Author And Source
이 문제에 관하여([Win32] 6. GDI 시스템 이해(2)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@psh4204/Win32-6.-GDI-시스템-이해2저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)