관련 글
출처 http://blog.naver.com/dolicom/10071034119
■ MFC 더블버퍼링으로 그림 그리기 - 깜박임 방지 & 윈도우 바탕화면 그리기 & 그림 버튼-double-buffer
■ MFC 그림으로 버튼, 스크롤, 리스트 만들기 - 더블버퍼링 & UML 문서화 기법 - 실제 프로그램 예
■ Thumbnail (썸네일) Windows MFC에서 그림 그리기 - GDI+ Graphics & Image 클래스 사용하기
■ 모달리스로 시작하는 인트로 만들기 - 초기에 modaless dialog을 표시하기 & 더블버퍼링 doube-buffer & PeekMessage
윈도우의 그림 처리 시, 여러가지 그림을 겹칠 때 깜박임 현상이 나타난다. 이것을 방지하기 위해서는 메모리를 사용하여 일단 한번 메모리에 그리고 최종적으로 디스플레이 버퍼 그림을 복사 한다.
개발 툴 : VC++ 6.0 & 포토샾
첨부파일 : 소스 및 bmp
bmp파일은 프로젝트\res\bmp에 복사하면 됨.
프로그램에서 사용된 그림은 다음 즐의 첨부파일 참조 - pic 디렉토리에 넣는다.
▒
우주사진 1
▒ 우주사진 2
▒ 우주사진 3
▒ 우주사진 4
▒ 우주사진 5
그림을 일단 포토샾으로 그리고 BMP 파일로 저장 한다. 그리고 이를 RES에 통록하여 그림을 화면에 표시할 수 있는 준비를 한다.
이 프로그램 예제에서 깜박임은 바로 그림들을 차례대로 화면에 뿌리면서 나타난다. 그림을 차례대로 복사할 때 각각의 스텝의 시간 차와 특히 배경을 그리고 개발자가 원하는 화면 구성할 때 많이 나타난다.
깜박임이 발생하는 원인
더블버퍼링을 사용하지 않으면 다음과 같은 현상을 볼 수 있다. 실제하고 BitBlt하는 순서에 따라 약간은 다르지만 이해를 위해 0.1초 간격으로 이미지를 표시 했다면 다음과 같다.
이것을 천천히 돌려 본다.
- 위 GIF에서 처음에 흰색은 개발 소스의 코드없이도 윈도우에서 설정된 배경화면으로 색깔로 client 공간을 전부 채운다. 따라서 내가 그리려는 색깔은 검정색 계통이기 때문에 깜박임이 심하다. 만약 흰색을 내가 원하는 이미지나 색깔로 채우려면 BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC)에서 원하는 함수를 호출하면 된다. 다음에 언급 참고.
- 각각의 그림을 그리면서 시간차에 의해 깜박임이 발생한다.
기본적으로 전부 지우고 다시 그리기 때문에 여기서 깜박임이 발생한 것이다.
더블버퍼링 개념 이해
우선 개념적인 이중버퍼링 개념을 생각하기 전에 일반적인 더블버퍼링을 사용하지 않는 경우는
그리는 순서를 보면
0. 그리려는 그림을 로드 한다.
CDC memDC; ---> CBitmap 처리 CDC을 잡는다.
CBitmap *oldBitmap;
CBitmap m_background;
CBitmap m_bmp1;
CBitmap m_bmp2;
m_background.LoadBitmap(IDB_BACKGRD); // 배경 그림을 가져온다.
BITMAP bmp;
int res = m_background.GetObject(sizeof(BITMAP), (LPVOID) &bmp);
int cx = (int)bmp.bmWidth; // 그림의 폭
int cy = (int)bmp.bmHeight; // 그림의 넓이
m_bmp1.LoadBitmap(IDB_BITMAP1); // 필요한 그림을 가져온다. 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.
m_bmp2.LoadBitmap(IDB_BITMAP2);
0.1 그림을 그리기 위한 pDC와 연결 한다.
memDC.CreateCompatibleDC(pDC);
1. 배경 그림을 선택하고 screen 버퍼에 그림을 복사 한다.
oldBitmap = memDC.SelectObject(&m_background);
pDC->BitBlt(0,0, cx, cy, &memDC, 0, 0, SRCCOPY);
이렇게 되면 이전에 그림 중에서 다른 bitmap1과 bitmap2가 화면에서 사라지고 배경만 나타난다.
디버깅 모드에서 break-point을 잡고 여기 까지 실행하면 쉽게 볼수 있다. VC++와 실행 프로그램을 윈도우 상에 겹치게 하면 WM_PAINT가 계속 호출되기 때문에 디버깅 할 때는 받듯이 VC++툴과 프로그램을 별도로 분리 해야 한다. 이래서 이중모니터를 사용하면 쉽게 디버깅이 가능한데, 개인이 모니터를 두개를 사용하는 것은 드문일... 그러므로 화면을 분할하여 VC++와 실행 프로그램을 실행하자마자 분리 한다. 처음에 break-point을 제거해야 프로그램을 VC++과 분리할 수 있다. 분리를 하고 다시 OnDraw에서 break-point을 다시 잡는 센스...
2. 이번에는 bitmap1을 선택하고 복사
memDC.SelectObject(&m_bmp1);
pDC->BitBlt(100, 300, 80, 50, &memDC, 0, 0, SRCCOPY);
3. 이번에는 bitmap2을 선택하고 복사
memDC.SelectObject(&m_bmp2);
pDC->BitBlt(220,300, 80, 50, &memDC, 0, 0, SRCCOPY);
4. 그림 뿐만 아니라 기타 함수를 사용하여 원하는 표시를 한다.
CString msg;
msg.Format("Hello");
pDC->TextOut(10,10, msg);
pDC->MoveTo(50.50);
pDC->LineTo(100.50);
pDC->LineTo(100.100);
pDC->LineTo(50, 100);
pDC->LineTo(50, 50);
5. 이제 마무리 하면
memDC.SelectObject(oldBitmap);
memDC.DeleteDC();
이렇게 하면 그림을 그릴 수 있다. 그러나 이렇게 되면 배경을 그리면 다른 그림이 사라지면서 다시 나타나는 깜박임이 나타난다.
만약 배경 그림이 없다면 원하는 배경색으로
FillRect() 함수 등으로 화면을 원하는 색으로 칠 할 수 있다. 이것 역시 마찬가지로 지워지는 마찬가지 이다.
CBrush brush,*pOldBrush;
brush.CreateSolidBrush(RGB(255, 255, 255));
pOldBrush = pDC->SelectObject(&brush);
CRect rect(10,10,200,100);
pDC->FillRect(&rect, &brush);
pDC->SelectObject(pOldBrush);
brush.DeleteObject();
또는
CBrush *pbrush = CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH));
CBrush *pOldBrush = pDC->SelectObject(pbrush);
CRect rect(10,10,200,100);
pDC->FillRect(&rect, pbrush);
pDC->SelectObject(pOldBrush);
pbrush->DeleteTempMap();
이렇게 되면 흰색으로 배경을 칠할 수 있다.
더블버퍼링 개념
그리서 다음과 같은 더블버퍼링을 사용한다. 개념부터 보자.
0. 우선 메모리 버퍼에 배경그림을 로드 한다.
CDC memDC; ...
LoadBitmap()
1. 다른 그림을 bitmap1등의 그림을 메모리 버퍼 memDC에 복사 한다.
2. 계속 원하는 그림 memDC에 그린다.
3. 그림과 글씨등 여러가지 함수를 사용하여 그림 뿐만 아니라 도형등을 memDC에 그린다.
TextOut, FillRect, LineTo, ....
4. 이제 memDC에 그림이 완성되면, 최종적으로 표시 버퍼인 pDC에 그림을 복사한다. BitBlt()
5. 마무리로 생성한 그림 핸들러 등을 닫는다.
DeleteObject, SelectObject, DeleteDC( DC을 Create 했을 때)
실제로 프로그램에 적용된 그림은 다음과 같다.
- IDB_BACKGRD : 바탕그림 - 우선적으로 화면의 밑바탕이다.
- IDB_LBTN, IDB_LBTN_SEL, IDB_LBTN_ACT : 왼쪽 방향 ( List에서 UP으로 사용)
- IDB_RBTN, IDB_RBTN_SEL, IDB_RBTN_ACT : 오른쪽 방향 ( List에서 DOWN으로 사용)
- IDB_LISTBAR : 리스트의 선택을 표시하는 그림이다. 이 위에 글쓰를 쓴다.
- IDB_SCRL_BAR : 스크롤의 바로 사용
위의 그림을 적절히 배치, 표현 함으로써 버튼, 리스트, 스크롤의 동작을 그림으로만으로도 구현 할 수 있다.
이제 깜박임을 생각해 본다.
그림을 그릴 때 BitBlt 함수를 사용하는데, 그림이 한장이면 깜박임 현상이 없다.
CBitmap m_background;
CDC dmemDC;
CBitmap *oldBitmap;
m_background.LoadBitmap(IDB_BACKGRD);
memDC.CreateCompatibleDC(pDC);
oldBitmap = memDC.SelectObject(&m_background);
pDC->BitBlt(stPos_x, stPos_x, width, height, &dmemDC, 0, 0, SRCCOPY);
memDC.SelectObject(oldBitmap);
memDC.DeleteDC();
가장 일반적인 방법이다. 그러나 그림이 여러장이라고 생각하자.
CBitmap m_background;
CBitmap m_btnLeft;
m_background.LoadBitmap(IDB_BACKGRD);
m_btnLeft.LoadBitmap(IDB_LBTN);
. . .
// Step1 : 배경그림 그리기
oldBitmap = memDC.SelectObject(&m_background);
pDC->BitBlt(stPos_x, stPos_x, width, height, &dmemDC, 0, 0, SRCCOPY);
// Step 2: 다음 그림 그리기
oldBitmap = memDC.SelectObject(&m_btnLeft);
pDC->BitBlt(stPos_x, stPos_x, width, height, &dmemDC, 0, 0, SRCCOPY);
. . .
이렇게 2장을 겹치면 첫번째를 그리면 순간적은 step2가 실행되기전에 두번째 그림이 삭제되어 화면에 표시되면서 눈이 이를 감지 한다.
그림이 2장이고 두번째가 작다면 깜박임 효과가 덜 하겠지만 그림이 많다거나 그림이 크다면 확실하게 눈이 감지 한다.
이를 방지 하기 위해 그림을 버퍼를 하나 더 두고, 메모리에 일단 모든 그림을 그리고 마지막에 이를 표시 메모리 버퍼로 한번에 BitBlt 한다. 마치 DirectX의 그림 그리는 방식과 같다.
함수로 작성하면
class CXxxDlg : public CDialog
{
// ...
public:
CBitmap m_background;
BITMAP m_Bitmap;
int m_stateLoadBitmap;
CBitmap m_btnLeft;
CBitmap m_btnRight;
CBitmap m_scrlBar;
void DrawPaint(CDC *pDC);
};
BOOL CXxxDlg ::OnInitDialog()
{
...
// TODO: Add extra initialization here
m_btnLeft.LoadBitmap(IDB_LBTN); // 필요한 그림을 가져온다. 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.
m_btnRight.LoadBitmap(IDB_RBTN);
m_scrlBar.LoadBitmap(IDB_SCRL_BAR);
m_background.LoadBitmap(IDB_BACKGRD);
m_background.GetObject(sizeof(BITMAP), (LPVOID) &m_Bitmap); // 배경그림의 사양을 얻는다. 크기...
//m_stateLoadBitmap = 1;
return TRUE; // return TRUE unless you set the focus to a control
}
만약 다이얼로그가 아니라 CView라면
class CDrawPicView : public CView
{
/// ...
};
void CDrawPicView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
m_btnLeft.LoadBitmap(IDB_LBTN); // 필요한 그림을 가져온다. 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.
m_btnRight.LoadBitmap(IDB_RBTN);
m_scrlBar.LoadBitmap(IDB_SCRL_BAR);
m_background.LoadBitmap(IDB_BACKGRD);
m_background.GetObject(sizeof(BITMAP), (LPVOID) &m_Bitmap); // 배경그림의 사양을 얻는다. 크기...
//m_stateLoadBitmap = 1;
// ...
}
CASE 1 :
OnInitDialog() 혹은 OnInitialUpdate()
- 배경그림 및 기타 그림을 로드 한다.
- 그림 로드는 OnPaint에서 할 수도 있으나 미리 해 놓으면 빠른 동작에 유리 하다.
OnPaint()
- 전체 그림을 위한 메모리 비트맵 메모리 버퍼를 만든다.
- 그림 그릴 준비를 한다.
- 배경을 우선 메모리 버퍼에 그린다.
- Z-order에 따라 그림과 기타 문자, 도형 등을 그린다. 일반적인 모든 화면 조작을 메모리에 그린다.
- 그림이 완성되면 최종으로 화면 버퍼에 전송에 그림이 나타나게 한다. 그림을 조립하는 과정에서의 깜박임을 방지하기 위한 방법이다.
void CXxxDlg::DrawPaint(CDC *pDC)
{
CDC memDC; // 처리 CDC을 지정 한다.
// 빈공간을 새롭게 만든다.
CDC mdcOffScreen; // 더블버퍼링을 위한 메모리 그림버퍼
CBitmap bmpOffScreen; // 더블버퍼링을 위한 비트맵 객체를 만든다.
CBitmap *oldbitmap;
if (! m_background.m_hObject) // 로드되어 있는지 확인
m_background.LoadBitmap(IDB_BACKGRD); // 만약 그림 배경 그림이 로드되지 않았으면 그림을 가져온다.
// 이미 배경은 OnInitDialog() 혹은 OnInitialUpdate()에서 로드되어 있으므로 다시 할 필요는 없다.
// 초기에서 로드가 실패하지 않았다면 다시할 필요가 없다.
// m_btnLeft.LoadBitmap(IDB_LBTN); // 만약 로드가 되지 않았다면, 필요한 그림을 가져온다.
// m_btnRight.LoadBitmap(IDB_RBTN); // 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.
// m_scrlBar.LoadBitmap(IDB_SCRL_BAR);
memDC.CreateCompatibleDC(pDC);
mdcOffScreen.CreateCompatibleDC(pDC);
// 화면 크기로 빈공간의 버퍼를 생성 한다.
bmpOffScreen.CreateCompatibleBitmap(pDC, m_Bitmap.bmWidth, m_Bitmap.bmHeight);
// 아직 dmemDC의 메모리에는 아무런 그림이 없다.
// 만약 어떤 색깔로 채우고자 한다면 FillRect() 함수등으로 특정색으로 칠할 수 있다.
// 그러나 다음에 배경 그림을 로드하므로 필요없는 일이다.
oldbitmap = mdcOffScreen.SelectObject(&bmpOffScreen);
// 이제 메모리 준비는 끝났다. 지금 부터는 그림을 그린다.
//우선 배경 그림이 맨 밑이므로 배경을 메모리에 복사 한다.
memDC.SelectObject(&m_background); // 배경 그림을 선택하고
mdcOffScreen.BitBlt(0, 0, m_Bitmap.bmWidth, m_Bitmap.bmHeight, &memDC, 0, 0, SRCCOPY);
// ==> 배경을 메모리버퍼에 복사 한다. 아직 화면에는 나타나지 않는다.
// 따라서 그림은 화면에 나타나지 않고, 디버깅이 힘들다.
// 디버깅을 싶게 한다면
//pDC->BitBlt(0, 0, m_Bitmap.bmWidth, m_Bitmap.bmHeight, &dmemDC, 0, 0, SRCCOPY);
// 한줄 더 넣어 화면을 확인하고 디버깅이 끝나면 삭제 한다.
memDC.SelectObject(&m_btnLeft);
mdcOffScreen.BitBlt(m_btnLeftRect.left, m_btnLeftRect.top, m_btnLeftRect.right, m_btnLeftRect.bottom,
&memDC, 0, 0, SRCCOPY);
// ==> 표시 버퍼인 pDC가 아니라 메모리에 복사 한다. 이렇게 되면 배경 그림이 있는 메모리 버퍼 mdcOffScreen에 복사 한다.
// 아직 메모리에 그림이 존재한다.
// 따라서 VC++에서 각 스텝별로 디버깅 할 때 여기까지 진행 했다면 아직 그림이 화면에 나타나지 않는다.
// 디버깅은 좀 불편
memDC.SelectObject(&m_btnRight);
mdcOffScreen.BitBlt(m_btnRightRect.left, m_btnRightRect.top, m_btnRightRect.right, m_btnRightRect.bottom,
&memDC, 0, 0, SRCCOPY);
// ==> 계속 메모리 버퍼 mdcOffScreen에 복사 한다. 배경화면에 계속 다른 그림을 겹친다.
// 따라서 mdcOffScreen의 배경 그림은 변경된다.
memDC.SelectObject(&m_scrlBar);
mdcOffScreen.BitBlt(m_scrlBarRect.left, m_scrlBarRect.top, m_scrlBarRect.right, m_scrlBarRect.bottom,
&memDC, 0, 0, SRCCOPY);
// ==> 계속 메모리 버퍼 mdcOffScreen에 복사 한다.
// 이번에는 BitBlt 말고 다른 윈도우 함수를 역시 메모리에 복사 한다.
mdcOffScreen.SetTextColor( (COLORREF) 0x00FFFFFF );
//==> 이것은 pDC->SetTextColor( (COLORREF) 0x00FFFFFF );와는 별개로 상관없음
mdcOffScreen.SetBkMode( TRANSPARENT ); // 글자의 배경색을 없앤다.
mdcOffScreen.TextOut(150,10, m_Msg ); // 글자를 쓴다. 물론 메모리에
// 여기까지 모든 그림이 완성되어 지만, 아직 표시 버퍼에 출력된 상태가 아니다. 디버깅을 해보면 아직 그림이 표시되지 않는다.
// 최종적으로 표시 화면 메모리에 복사 한다.
pDC->BitBlt(0,0, m_Bitmap.bmWidth, m_Bitmap.bmHeight, &mdcOffScreen, 0, 0, SRCCOPY);
// 이때서야 화면에 그림이 나타난다.
memDC.DeleteDC();
mdcOffScreen.SelectObject(oldbitmap);
mdcOffScreen.DeleteDC();
bmpOffScreen.DeleteObject();
}
CASE 2 :
이번에는 비트맵 빈공간 생성을 하지 않고 배경이미지를 매번 로드해서 처리하는 방식이다.
void CXxxDlg::DrawPaint(CDC *pDC)
{
CDC memDC;
CDC dmemDC;
CBitmap *oldBitmap;
if (m_background.m_hObject)
m_background.DeleteObject();
m_background.LoadBitmap(IDB_BACKGRD); // 배경 그림을 가져온다.
// 이 그림은 memDC와 복사되어 다른 그림을 그리면 배경 그림이 변경되므로 다음에 다시 로드할 필요가 있다.
// 즉, memDC에 전체 화면에 완성 되면, m_background 변수에 있는 그림은 완성된 전체그림이다. 배경만 있는 그림이 아니다.
// 만약, m_background에 이미 그림이 로드(LoadBitmap()) 되어 있는 상태에서는 다시 로드가 안된다. 이때는
// m_background.DeleteObject()로 제거하고 다시 로드하는 센스...
// m_btnLeft.LoadBitmap(IDB_LBTN); // 만약 로드가 되지 않았다면, 필요한 그림을 가져온다.
// m_btnRight.LoadBitmap(IDB_RBTN); // 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.
// m_scrlBar.LoadBitmap(IDB_SCRL_BAR);
memDC.CreateCompatibleDC(pDC);
oldBitmap = memDC.SelectObject(&m_background);
dmemDC.CreateCompatibleDC(&memDC);
dmemDC.SelectObject(&m_btnLeft);
memDC.BitBlt(m_btnLeftRect.left, m_btnLeftRect.top, m_btnLeftRect.right, m_btnLeftRect.bottom,
&dmemDC, 0, 0, SRCCOPY);
// ==> 표시 버퍼인 pDC가 아니라 메모리에 복사 한다. 이렇게 되면 배경 그림이 있는 메모리 버퍼 memDC에 복사 한다.
// 아직 메모리에 그림이 존재한다.
// 따라서 VC++에서 각 스텝별로 디버깅 할 때 여기까지 진행 했다면 아직 그림이 화면에 나타나지 않는다. 디버깅은 좀 불편
dmemDC.SelectObject(&m_btnRight);
memDC.BitBlt(m_btnRightRect.left, m_btnRightRect.top, m_btnRightRect.right, m_btnRightRect.bottom,
&dmemDC, 0, 0, SRCCOPY);
// ==> 계속 메모리 버퍼 memDC에 복사 한다. 배경화면에 계속 다른 그림을 겹친다. 따라서 memDC의 배경 그림은 변경된다.
dmemDC.SelectObject(&m_scrlBar);
memDC.BitBlt(m_scrlBarRect.left, m_scrlBarRect.top, m_scrlBarRect.right, m_scrlBarRect.bottom,
&dmemDC, 0, 0, SRCCOPY);
// ==> 계속 메모리 버퍼 memDC에 복사 한다.
// 이번에는 BitBlt 말고 다른 윈도우 함수를 역시 메모리에 복사 한다.
memDC.SetTextColor( (COLORREF) 0x00FFFFFF );
memDC.SetBkMode( TRANSPARENT );
memDC.TextOut(150,10, m_Msg );
// 여기까지 모든 그림이 완성되어 지만, 아직 표시 버퍼에 출력된 상태가 아니다. 디버깅을 해보면 아직 그림이 표시되지 않는다.
// 최종적으로 표시 메모리에 복사 한다.
pDC->BitBlt(0,0, m_bgRect.Width(), m_bgRect.Height(), &memDC, 0, 0, SRCCOPY);
// 이때서야 화면에 그림이 나타난다.
dmemDC.DeleteDC();
memDC.SelectObject(oldBitmap);
memDC.DeleteDC();
// m_stateLoadBitmap = 0;
// m_background.DeleteObject(); // 지금 완성 된 그림을 다른 함수에서 사용한다면 그림을 없애지 않을 수 있다.
// 이렇게 되면 화면에 나타난 완성된 그림은 메모리에 남게 된다.
// m_background 이것은 클래스의 멤버변수이므로 사라지지 않는다.
// 다시 사용할 수 있다. m_background에 다시 그림을 로드하기 위해 m_background.LoadBitmap(IDB_BACKGRD)가 호출되면
// 에러가 발생 한다.
}
이렇게 이중 버퍼링을 통해 깜박임을 방지 한다.
이렇게 예제 그림이 나온다.
CDC dmemDC;
CDC memDC;
memDC.CreateCompatibleDC(pDC);
dmemDC.CreateCompatibleDC(pDC);
이것도 될것 같고....
pDC는 어디서
1. CDialog에서 상속 받은 다이얼로그라면
void CXxxDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CClientDC dc(this);
DrawDoalog(&dc);
CDialog::OnPaint();
}
}
2. CView로 부터 상속받은 일반적인 윈도우 View Class 라면
void CXxxView::OnDraw(CDC* pDC)
{
CXxxDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
DrawDoalog(pDC);
}
3. WM_PAINT 이벤트가 아니라 다른데서 한다면, 이 때는 View클래스나 Dialog에서 하면 된다.
CDC* pDC = GetDC();
DrawDoalog(pDC);
ReleaseDC(pDC ); ---> GetDC()을 했다면 이 함수를 호출하는 센스...
MFC가 아니거나 CWnd로 상속 받지 않았다면
HDC GetDC(
HWND hWnd // handle to a window
);
int ReleaseDC(
HWND hWnd, // handle to window
HDC hDC // handle to device context
);
이 함수를 사용해야 하는데.
CDC* pDC = ::GetDC(hWnd);
/// 그림을 그리고
::ReleaseDC(hWnd, pDC );
MFC을 사용한 경우인데 CWnd의 상속 클래스가 아닌곳에서 호출하려고 얻으려면
class CXxxDlg : public CDialog
{
public:
CXxxDlg ();
};
CXxxDlg dlg;
HWND hWnd = dlg.m_hWnd;
다이얼로그가 아니라면 View class에서 부터 얻어야 한다. 만약 View라면 GetDC가 CWnd에 있지만, 다른 클래스에서 호출하려면 일단 View 클래스를 얻어야 한다. 프로그램 구조적으로 View 클래스를 포인터 관리하면 쉽지만 이게 아니라면 일단 View 클래스의 포인터를 얻으면 된다.
class CXxxView : public CWnd
{
public:
CXxxView ();
};
CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();
CXxxView * pView = (CXxxView *)pFrame->GetActiveView();
HWND hWnd = pView->m_hWnd;
그렇다면 Multi-View라면 View 클래스를 Document 클래스로 부터 얻을 수 있다.
void CXxxDoc::OnRepaintAllViews()
{
POSITION pos = GetFirstViewPosition(); // 첫번째 뷰가 있는 위치 변수 포인터
while (pos != NULL) {
CView* pView = GetNextView(pos); // 다음 뷰들의 포인터
// 만약 이것이 그리려면 윈도우라면 break;
// pView->UpdateWindow();
}
}
CWnd::m_hWnd
Remarks
The handle of the Windows window attached to this CWnd. The m_hWnd data member is a public variable of type HWND.
CView에서 깜박임
메모리버퍼링에 의한 깜박임의 제거 했다고 모두 없어지는 것은 아니다. 다음 문제가 윈도우의 크기 변경할 때, 윈도우 크기를 변경하고 윈도우 배경색을 칠한다. 이러면 내가 그리려는 배경과 다른 배경이 처음에 한번 칠해지면서 깜박임이 나타난다. 보통 흰색계열의 배경이 칠해지므로 여기서 제시된 검정색 배경과 플립현상으로 깜박임이 나타난다.
따라서 WM_SIZE의 이벤트에서 크기 변경 후, 다시 배경색을 칠하는 부분을 제어 하면 된다.
이때 사용되는 함수가
BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
return CView::OnEraseBkgnd(pDC);
}
이다.
이벤트 발생 순서를 알기 위한 디버깅을 하면
void CDrawPicView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// TODO: Add your message handler code here
TRACE("OnSize(type=%X, %d,%d )\n", nType, cx, cy);
}
BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
TRACE("OnEraseBkgnd()\n");
return CView::OnEraseBkgnd(pDC);
}
이렇게 메시지 출력을 하면
OnSize(type=0, 1268,801 )
OnEraseBkgnd()
OnPaint
출력 된다.
OnEraseBkgnd()에서 전체적으로 흰색계열로 칠하고, OnPaint에서 개발자가 BitBlt 한것이다. 이 때 바로 깜박임이 발생 한 것이다.
배경색 제거를 하려면
BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
TRACE("OnEraseBkgnd()\n");
//return CView::OnEraseBkgnd(pDC);
return FALSE;
}
변경하면 배경을 그리는 것은 없다. 그러면 다시 WM_PAINT가 발생하므로 여기서 화면 구성을 하면 된다.
이렇게 하면 일단 깜박임은 없어진다. 그런데 이 예에서는 WM_PAINT에서는 설정한 크기만을 표시 하였다. 정해진 크기보다 크면 배경색 지우기가 없어졌다.
바깥부분의 배경색이 문제가 발생 했다. 이것은 OnEraseBkgnd()에서 흰색 계통의 배경을 채웠던건데 여기서는 이 함수의 기능을 정지 했기 때문에 어디선가 배경을 칠해야 한다.
이것은 OnEraseBkgnd()에서 하든지, OnDraw()에서 하든지 둘중에 한곳에서 배경을 처리 해야 한다.
여기서는 OnEraseBkgnd()에서 처리하기로 결정 했다. 내가 설정한 영역을 벗어나는 부분은 배경 그림 데이터가 없기 때문에 검정색으로 그냥 채우기로 결정 하였다.
영역 A배경은 OnEraseBkgnd다음에 발생하는 OnPaint에서 칠하가로 하고, 우선 B영역을 검정색으로 칠하고, 다음으로 C영역을 칠 한다. B영역과 C영역을 OnPaint에서 칠해도 되지만 아무래도 OnEraseBkgnd()가 먼저 발생되기 때문에 A영역은 안없어지고 여기서는 B와 C영역을 칠하는 것이 좋은 상황이다.
BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
TRACE("OnEraseBkgnd()\n");
if (m_pBackground->cx < m_bgRect.right) {
RECT rect;
rect.left = m_pBackground->cx;
rect.right = m_bgRect.right;
rect.top = 0;
rect.bottom = m_pBackground->cy;
pDC->FillRect(&rect, &m_bkgBrush); // B영역 칠하기
}
if (m_pBackground->cy < m_bgRect.bottom) {
RECT rect;
rect.left = 0;
rect.right = m_bgRect.right;
rect.top = m_pBackground->cy;
rect.bottom = m_bgRect.bottom;
pDC->FillRect(&rect, &m_bkgBrush); // C영역 칠하기
}
//return CView::OnEraseBkgnd(pDC);
return FALSE;
}
여기서 내가 설정 배경의 범위를 벗어나는 것은 알기 위해 사이즈를 일단 저장한다. 그리고 배경색을 지정하기 위한 CBrush을 하나 잡는다.
class CDrawPicView : public CView
{
CRect m_bgRect; // 화면 전체의 크기 - client 윈도의 전체크기
BitmapList *m_pBackground; // 전체 배경 그림의 포인트
CBrush m_bkgBrush; // 화면 크기를 벗어나면 칠해 준다.
// ...
};
화면 크기는
void CDrawPicView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// TODO: Add your message handler code here
m_bgRect.left = 0;
m_bgRect.top = 0;
m_bgRect.right = cx;
m_bgRect.bottom = cy;
TRACE("OnSize(type=%X, %d,%d )\n", nType, cx, cy);
}
에서 저장하고
void CDrawPicView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
CDrawPicDoc* pDoc = GetDocument();
m_bkgBrush.CreateSolidBrush( 0x00000000 ); // (COLORREF) crColor=0x00bbggrr );
}
이렇게 2가지를 저장과 설정을 하면 OnEraseBkgnd() 준비가 끝나고 이 함수가 호출될 때 바깥 부분을 검정색으로 처리 할 수 있다.
A영역 배경 그림 사이즈 알기
여기서 m_pBackground변수는 A영역의 배경 그림이다.
m_pBackground->cx;
m_pBackground->cy;
는 이미지 크기를 읽어 저장한 위한 변수이다.
typedef struct {
UINT idd;
int st_x;
int st_y;
int end_x;
int end_y;
int cx;
int cy;
CBitmap cbitmap;
char *name;
int state;
} BitmapList; // 각 그림들을 관리하기 위한 struct 이다.
void CDrawPicView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
CDrawPicDoc* pDoc = GetDocument();
int cnt;
// ...
BitmapLoad(lbmp, IDB_BACKGRD, POSLBTN_X, POSRBTN_Y); // 배경 그림을 로드하여 크기와 기타 정보를 저장한다.
m_pBackground = lbmp; lbmp++;
// ...
}
BitmapList *CDrawPicView::BitmapLoad(BitmapList *lbmp, UINT idd,
int sx, int sy)
{
lbmp->idd = idd;
lbmp->name = NULL;
lbmp->cbitmap.LoadBitmap(idd);
BITMAP bmp;
int res = lbmp->cbitmap.GetObject(sizeof(BITMAP), (LPVOID) &bmp);
lbmp->cx = (int)bmp.bmWidth; // 그림의 폭
lbmp->cy = (int)bmp.bmHeight; // 그림의 넓이
lbmp->st_x = sx;
lbmp->st_y = sy;
lbmp->end_x = lbmp->st_x + lbmp->cx;
lbmp->end_y = lbmp->st_y + lbmp->cy;
return lbmp;
}
이것을 정리하면
CBitmap cbitmap;
BITMAP bmp;
cbitmap.LoadBitmap(IDB_BACKGRD);
int res = cbitmap.GetObject(sizeof(BITMAP), (LPVOID) &bmp);
lbmp->cx = (int)bmp.bmWidth; // 그림의 폭
lbmp->cy = (int)bmp.bmHeight; // 그림의 넓이
여기서 사용된 CBrush는 프로그램 끝나면서 제거 했다.
CDrawPicView::~CDrawPicView()
{
m_bkgBrush.DeleteObject();
// ...
}
전체적인 프로그램을 적용하여 프로그램 하면 다음과 같은 결과가 나온다.
이렇게 바깥부분이 검정색으로 배경 그림과 표시가 나지 않도록 처리 하였다.
아니면 윈도우 사이즈 크기를 제한 하는 방법도 있을 것 같은데... 뀌찮음....
크기제한은 다음 메세지를 연구해야 할것 같다.
POINT ptMaxTrackSize= { GetSystemMetrics(SM_CXSCREEN) / 3, GetSystemMetrics(SM_CYSCREEN)};
POINT ptMinTrackSize = { 200,200};
POINT ptMaxSize = { GetSystemMetrics(SM_CXSCREEN) / 3 , GetSystemMetrics(SM_CYSCREEN)};
POINT ptMaxPosition = { GetSystemMetrics(SM_CXSCREEN) * 2 / 3 , 0};
caseWM_GETMINMAXINFO:
{
LPMINMAXINFO pMinMax = (LPMINMAXINFO)lParam;
pMinMax->ptMaxTrackSize = ptMaxTrackSize;
pMinMax->ptMinTrackSize = ptMinTrackSize;
pMinMax->ptMaxSize = ptMaxSize;
pMinMax->ptMaxPosition = ptMaxPosition;
}
return 0;
CDialog에서 다른 요소(에디터박스,...)와의 깜박임
CDialog에서 그림을 처리하는 과정은 배경에 그림을 그리는 형태이다. 이것은 따라서 다른 요소와의 깜박임이 있다. 리스트박스를 넣었다면 크기가 크므로 여기서 사용한 배경색 검정과 윈도우의 다이얼로고의 흰색 계열과 깜박임이 발생 한다. 크기가 클 수록 확실히 보인다.
여기서는 CEdit 클래스의 에디터박스이다. 오른쪽아래의 에디터박스를 흰색으로 변경하면 바탕의 검정과 흰색의 대비가 일어난다. 이것은 그림을 그릴 때나타나는 배경 칠하기와 각 요소를 그리는 것과의 문제이다.
void CDoubleBuffDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
//...
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CClientDC dc(this);
DrawDoalog(&dc); // 개발자가 그리는 배경화면
CDialog::OnPaint(); // 다이얼로그의 다른 요소를 그리는 과정 (에디터박스, 버튼, ...)
}
}
DrawDoalog()이얼로 에서 배경을 그리면 모든 부분에 그림을 그린다. 여기서는 검정색 계통의 배경 그림이므로 이것이 일단 화면에 나타나고 다시 OnPaint에서 내부 요소들을 그린다. 따라서 크기가 큰 에디터 박스는 눈에 확실히 표시가 난다. 이것을 방지하기 위해 다음과 같은 모드로 변경 한다.
CEdit 를 점유하고 있는데 부모 클래스인 전체 다이얼로그의 속성을 Clip children으로 바꾸었다.
// 개발자가 그리는 배경화면
void CDoubleBuffDlg::DrawDoalog(CDC *pDC)
{
CDC memDC;
CBitmap *oldBitmap;
CDC dmemDC;
m_pBackground->cbitmap.DeleteObject();
m_pBackground->cbitmap.LoadBitmap(IDB_BACKGRD); // 전체배경 그림을 그리고
memDC.CreateCompatibleDC(pDC);
oldBitmap = memDC.SelectObject(&m_pBackground->cbitmap);
dmemDC.CreateCompatibleDC(pDC);
/// 메모리에 그림을 그리고
dmemDC.SelectObject(&pbmplist->cbitmap);
memDC.BitBlt(pbmplist->st_x,pbmplist->st_y, pbmplist->cx,pbmplist->cy, &dmemDC, 0, 0, SRCCOPY);
/// ...
// 화면버퍼에 그림을 복사하여 화면에 나타나게
pDC->BitBlt(0,0, m_pBackground->cx, m_pBackground->cy, &memDC, 0, 0, SRCCOPY);
TRACE("OnPaint\n");
dmemDC.DeleteDC();
memDC.SelectObject(oldBitmap);
memDC.DeleteDC();
}