- 화면 그리기의 구조
- CWnd::RedrawWindow()
- WM_PAINT 메시지를 발생시켜 윈도우를 다시 그리도록 한다. - 윈도우의 크기를 변경하여 빠른 속도로 윈도우가 다시 그려지도록 하면 화면이 깜빡이거나 현상 발생
- WM_ERASEBKGND 메시지
- WM_PAINT 메시지와 더불어 윈도우를 다시 그릴 때 발생
- WM_ERASEBKGND 메시지 핸들러 함수는 윈도우의 배경을 그리는 코드 수행
- 클라이언트 영역을 다시 그릴 때 WM_PAINT 메시지에 앞서 WM_ERASEBKGND 메시지 발생
(비클라이언트 영역을 다시 그리는 메시지는 WM_NCPAINT)
- 윈도우가 깜빡이는 주요 원인 중 하나
- WM_ERASEBKGND 메시지 핸들러 함수를 등록하여 상위 클래스의 멤버를 호출하지 않도록 하면 깜박임이 줄어듬 - 눈속임의 미학
- 클라이언트 뷰의 자식 윈도우로 있는 윈도우가 배경을 그릴 때(WM_ERASEBKGND 메시지가 발생했을 때) 자신의 부모 윈도우의 OnEraseBkgnd() 메시지 핸들러 함수를 명시적으로 호출하여 배경 이미지 획득 후 이미지 출력하여 투명해 보이는 효과 얻음
- 클라이언트 뷰의 OnEraseBkgnd() 함수
- 전달받은 DC에 이미지를 로드하여 출력 - 클라이언트 뷰의 자식 윈도우의 OnEraseBkgnd() 함수
- 메모리 DC에 부모 윈도우의 배경을 획득 후 자식 윈도우에 출력 - 더블 버퍼링(Double Buffering)
- 윈도우의 깜빡임을 제거하는 최선의 방법
- 메모리 DC(버퍼 DC)에 모든 그리기 작업을 수행한 후, 메모리 DC의 내용을 화면 DC로 복사하는 기법
- 그리는 과정이 화면에 출력되지 않으므로 깜빡임이 사라짐
- CBufferDC 클래스
- BufferDC.h - WM_ERASEBKGND 메시지 핸들러 함수를 등록하여 아무런 처리도 하지 않도록 코드를 수정하고 OnPaint() 함수에서 CBufferDC 클래스를 활용하여 더블 버퍼링을 구현한다면 화면 깜빡임을 완벽히 해결할 수 있음
- 1.(3) 참조
BOOL CRedrawDemoView::OnEraseBkgnd(CDC* pDC) { return TRUE; // return CView::OnEraseBkgnd(pDC); }
- 위 기법과 더블 버퍼링을 함께 사용하면 깜빡임이 전혀 없는 인터페이스를 구현 가능
BOOL CTransparentDemoView::OnEraseBkgnd(CDC* pDC) { CRect Rect; GetClientRect(&Rect); pDC->FillSolidRect(&Rect, RGB(255, 255, 255)); CImage ImageBackground; ImageBackground.LoadFromResource(AfxGetInstanceHandle(), IDB_Background); ImageBackground.BitBlt(pDC->m_hDC, 0, 0); return TRUE; // return CView::OnEraseBkgnd(pDC); }
BOOL CTransparentWnd::OnEraseBkgnd(CDC* pDC) { // 자식 윈도우의 왼쪽 위가 부모 윈도우 기준으로 어딘지 좌표 계산 CRect Rect, ParentRect; GetClientRect(&Rect); GetParent()->GetClientRect(&ParentRect); CPoint ptLeftTop = CPoint(0, 0); ClientToScreen(&ptLeftTop); GetParent()->ScreenToClient(&ptLeftTop); // 메모리 DC에 적절한 CBitmap 클래스 객체 생성하여 선택 CDC MemDC; CBitmap Bmp; MemDC.CreateCompatibleDC(NULL); Bmp.CreateBitmap(ParentRect.Width(), ParentRect.Height(), MemDC.GetDeviceCaps(PLANES), MemDC.GetDeviceCaps(BITSPIXEL), NULL); CBitmap* pOldBmp = MemDC.SelectObject(&Bmp); // 메모리 DC의 핸들을 메시지 파라미터로 부모 윈도우에 WM_ERASEBKGND 메시지 송신 GetParent()->SendMessage(WM_ERASEBKGND, (WPARAM)MemDC.m_hDC); pDC->BitBlt(0, 0, Rect.Width(), Rect.Height(), &MemDC, ptLeftTop.x, ptLeftTop.y, SRCCOPY); MemDC.SelectObject(pOldBmp); return TRUE; // return CWnd::OnEraseBkgnd(pDC); }
class CBufferDC : public CDC { private: CBufferDC() { } CBufferDC(const CBufferDC &src) { } CBufferDC& operator=(const CBufferDC &src) { } protected: BOOL Attach(HDC hDC); HDC Detach(); private: CWnd* m_pParent; //대상 윈도우에 대한 포인터 CDC* m_pTarget; //대상 윈도우 DC에 대한 포인터 PAINTSTRUCT m_PaintStruct; CRect m_RcClient, m_RcWindow; //대상 윈도우의 크기 정보 CDC m_MemoryDC; //버퍼 DC CBitmap m_MemoryBmp, *m_pOldMemoryBmp; //버퍼링을 위한 비트맵 public: CBufferDC(CWnd *pParent); ~CBufferDC(); public: inline CRect ClientRect() const { return m_RcClient; } inline CRect WindowRect() const { return m_RcWindow; } inline CRect UpdateRect() const { return m_PaintStruct.rcPaint; } operator HDC() const { return m_MemoryDC.m_hDC; } // DC handle for API functions };
- BufferDC.cpp
////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CBufferDC::CBufferDC(CWnd *pParent) : m_pParent(pParent) { ASSERT(pParent); //대상 윈도우에 대한 정보를 수집한다. m_pTarget = m_pParent->BeginPaint(&m_PaintStruct); m_pParent->GetClientRect(&m_RcClient); m_pParent->GetWindowRect(&m_RcWindow); //대상 윈도우에 대한 DC를 생성한다. m_MemoryDC.CreateCompatibleDC(m_pTarget); //대상 DC에 대한 메모리 비트맵을 생성하여 Select 한다. m_MemoryBmp.CreateBitmap(m_RcClient.Width(), m_RcClient.Height(), m_MemoryDC.GetDeviceCaps(PLANES), m_MemoryDC.GetDeviceCaps(BITSPIXEL), 0); m_pOldMemoryBmp = m_MemoryDC.SelectObject(&m_MemoryBmp); //메모리 버퍼에 Attach한다. Attach(m_MemoryDC); } ////////////////////////////////////////////////////////////////////// CBufferDC::~CBufferDC() { //메모리 DC의 내용을 대상 윈도우에 출력한다. //내부적으로 비트맵에 출력한 것이므로 해당 비트맵을 1:1로 복사한다. m_pTarget->BitBlt( m_PaintStruct.rcPaint.left, m_PaintStruct.rcPaint.top, m_PaintStruct.rcPaint.right - m_PaintStruct.rcPaint.left, m_PaintStruct.rcPaint.bottom - m_PaintStruct.rcPaint.top, &m_MemoryDC, m_PaintStruct.rcPaint.left, m_PaintStruct.rcPaint.top, SRCCOPY); m_MemoryDC.SelectObject(m_pOldMemoryBmp); m_pParent->EndPaint(&m_PaintStruct); Detach(); } ////////////////////////////////////////////////////////////////////// BOOL CBufferDC::Attach(HDC hDC) { return CDC::Attach(hDC); } ////////////////////////////////////////////////////////////////////// HDC CBufferDC::Detach() { return CDC::Detach(); }