2010년 1월 6일 수요일

[.NET Compact Framework-C#] Control Double Buffering

컨트롤 커스터마이징을 하다 보면 요구사항에 따라 Flicker와 같이 부드럽게 동적으로 움직이는 컨트롤이 필요할 때가 있다. 컨트롤 클래스를 상속받아 열심히 오너 드로잉을 해 보면 알겠지만 Invalidate를 이용하여 다시 그리기를 시도하면 컨트롤이 아주 작지 않는 이상 깜박 거림을 볼 수 있을 것이다. 이를 방지하기 위해서 타이머에서 그래픽스 객체를 직접 얻어다가 그려주는 방식을 사용할 수도 있겠지만 보편적인 Invalidate를 사용하는 방법을 소개해보고자 한다.
결론 부터 말하자면 수동적인(?) 더블 버퍼링을 사용하는 방법인데 다음과 같이 정리할 수 있다.

1. 컨트롤 크기와 같은 이미지를 생성한다.
2. 이미지의 그래픽스 객체를 얻어 OnPaint 나 OnPaintBackground 내의 그리기 루틴을 이미지에 그린다.
3. 컨트롤 자체의 그래픽스 객체에 이미지를 뿌려준다.

자세한 내용은 아래 소스를 참고하면 되지만 포인트 별 부연설명을 하자면 다음과 같다.


1. 컨트롤 사이즈 변경(OnResize)에서 컨트롤 크기와 같은 이미지를 생성한다. 이 때 screenBuffer, graphics, paintEvent는 클래스 멤버이다.

private Bitmap screenBuffer;
private Graphics graphics;
private PaintEventArgs paintEvent;

protected override void OnResize(EventArgs e)
{
base.OnResize(e);

if (graphics != null)
{
graphics.Dispose();
}

if (screenBuffer != null)
{
screenBuffer.Dispose();
}

screenBuffer = new Bitmap(Width, Height);
graphics = Graphics.FromImage(screenBuffer);
paintEvent = new PaintEventArgs(graphics, ClientRectangle);
}


2. OnPaint, OnPaintBackground 를 오버라이딩 하여 그리기 중복 방지를 위하여 하위클래스에서 상속 받을 수 없게 한다.

protected override sealed void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
}

protected override sealed void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
}


3. OnDraw 메소드를 가상함수로 정의하고 컨트롤 그리기의 공통행위(?)를 구현한다. 예제 소스에서는 BackColor로 화면을 클리어 하는 루틴을 구현해 넣었다.

protected virtual void OnDraw(PaintEventArgs e)
{
e.Graphics.Clear(BackColor);
}


4. OnPaint, OnPaintBackground 에서 base 호출을 주석처리하고 둘 중 한군데에 screenBuffer를 뿌리게 한다. 예제 소스에서는 OnPaintBackground에서 뿌리게 하였다.

protected override sealed void OnPaint(PaintEventArgs e)
{
//base.OnPaint(e);
}

protected override sealed void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e);
if (this.graphics != null && screenBuffer != null)
{
OnDraw(paintEvent);
e.Graphics.DrawImage(screenBuffer, 0, 0);
}
}


5. 컨트롤을 더 이상 사용하지 않을 경우를 생각해서 다음과 같이 Dispose 함수를 오버라이딩 하여 구현한다.

protected override void Dispose(bool disposing)
{
if (disposing)
{
if (graphics != null)
{
graphics.Dispose();
}

if (screenBuffer != null)
{
screenBuffer.Dispose();
}
}
base.Dispose(disposing);
}

이 방법을 사용하면 디자이너에서도 사용할 수 있으므로 디자이너에서 컨트롤 배치를 하는 개발자라면 유용하게 쓰일 듯 하다.

댓글 없음:

댓글 쓰기