GDI+는 일반 Windows 프로그램에서 사용하던 GDI의 불편한 부분을 수정하여 만든 그래픽 라이브러리입니다. 이것은 GDI 보다 훨씬 많은 기능을 제공하고 있으나 여기서는 기본적으로 앞에서 만든 메뉴를 이용하여 선, 사각형, 원, 삼각형을 그리는 루틴을 만들어 보겠습니다. 나머지는 도움말을 보고 쉽게 사용할 수 있을 것입니다.
필자는 앞에서 작성한 MenuToolbar 프로젝트를 GDIPlus로 변경하여 작성했습니다. 그러나 가능하면 익숙해 질때까지 프로젝트를 새로 생성하여 작성하기 바랍니다. 참고로 이미 자료를 전혀 보지 않고 앞의 단계를 입력할 수 있는 분들을 위해 변경 방법을 설명해 보겠습니다.
GDIPlus라는 디렉토리를 만들어 MenuToolbar의 파일(하위 디렉토리 제외)을 모두 복사하여 붙여 넣습니다. MenuToolbar 라는 이름의 파일들을 GDIPlus으로 변경합니다. GDIPlus.csproj 파일과 GDIPlus.sln 파일을 텍스트 편집기로 열어 MenuToolbar를 GDIPlus로 모두 바꾼다. 이제 GDIPlus.sln 파일을 열면 변경된 프로젝트가 열립니다.
C++ 프로그램을 해본 사람은 WM_PAINT 메시지나 OnDraw함수에 대해 알 것입니다. Windows가 화면상에서 새로 그려져야할 때 발생하는 메시지 이며 그 메시지가 발생했을 때 호출되는 함수입니다.
C#에서 이 기능을 하는 이벤트가 Paint입니다. C++에서 마찬가지로 모든 그리는 루틴은 이벤트 Paint가 발생하면 실행되는 함수를 만들어 그려주고 메뉴 선택이나 마우스 클릭 등에 의해 화면을 강제로 그려야 하는 경우는 Invalidate()를 호출하여 Paint함수가 호출되게 만들면 됩니다.
Form의 속성창에서 Paint를 더블클릭하여 생성된 함수에서 다음과 같이 입력합니다.
Graphics g = e.Graphics;
Rectangle rect = new Rectangle(base.ClientSize.Width/4, base.ClientSize.Height/4,
base.ClientSize.Width * 2/4, base.ClientSize.Height *2/4);
Pen pen = new Pen(Color.Black, 1);
Brush brush = new SolidBrush(Color.Red);
switch(this.drawKind)
{
case DrawKind.Line:
g.DrawLine(pen, rect.Left, rect.Top, rect.Right, rect.Bottom);
break;
case DrawKind.Rectangle:
g.FillRectangle(brush, rect);
g.DrawRectangle(pen, rect);
break;
case DrawKind.Ellipse:
g.FillEllipse(brush, rect);
g.DrawEllipse(pen, rect);
break;
case DrawKind.Triangle:
Point[] ptPos = new Point[3];
ptPos[0].X = rect.X + rect.Width / 2;
ptPos[0].Y = rect.Y;
ptPos[1].X = rect.X;
ptPos[1].Y = rect.Y + rect.Height;
ptPos[2].X = rect.X + rect.Width;
ptPos[2].Y = rect.Y + rect.Height;
g.FillPolygon(brush, ptPos);
g.DrawPolygon(pen, ptPos);
break;
}
루틴은 다만 선택한 그리기 종류에 따라 선, 사각형, 원, 삼각형을 그리 도록한 것입니다. 삼각형 그리기는 약간 복잡해 보이나 단지 3점의 위치를 메모리에 넣고 Polygon을 호출한 것 뿐입니다. 또한 rect는 화면의 가운데 그림을 그리기 위해서 클라이언트 영역으로부터 가운데 영역을 구한것입니다.
앞에서부터 루틴을 보면 pen과 brush를 생성하여 그리는 함수 인자에 좌표와 함께 넘겨 주면 그림이 그려 집니다. 물론 pen과 brush 및 rect는 가비지 컬렉터가 알아서 삭제 합니다.
예전 GDI를 생각해 보겠습니다. pen과 brush를 만들어 SelectObject(이전 pen, brush 보관)한 후 그려주고, 이전 pen과 brush를 이용하여 SelectObject를 호출하여 돌려놓고 pen과 brush를 삭제하였습니다.
물론 조금밖에 복잡하지 않지만 이전 pen과 brush로 돌려 놓지 않거나 pen과 brush를 삭제 하지 않아 리소스를 잡아먹는 프로그램을 만드는 실수를 종종하게 되었는데, 이 실수를 GDI+는 사전에 방지하도록 구성되어 있습니다.
C#에서는 도형을 그릴때 외부의 선과 내부 면을 한꺼번에 그리는 루틴이 없습니다. 따라서 그런 그림을 그리고자 한다면 위에서와 같이 Fill로 시작하는 Method(내부 면 채우기)와 Draw로 시작하는 Method(외부 선 그리기)를 모두 호출해 주어야만 합니다.
이제 실행하여 창크기를 바꾸어 보겠습니다. 줄였다 늘리면 화면상에 그림이 삭제 되는 것을 알 수 있습니다. 앞에서 이야기한 대로라면 Paint이벤트를 사용하여 그리므로 당연히 제대로 그려져야 합니다. C#에서는 크기가 변경될 때 속도를 빨리하기 위해 일반적으로는 변경된 부위만 새로 그리도록 합니다.
이것은 고정된 크기의 그림을 그리는 일반적인 경우에는 문제가 없으나 화면 크기가 변경될 때 그림 전체 크기가 변경되는 이 프로그램 같은 경우에는 문제가 됩니다. 따라서 Form은 크기가 변경될 때 전체 화면을 모두 그리도록 설정할 수 있는 속성(Attribute)이 있는데 이것이 ResizeRedraw입니다.
ResizeRedraw는 속성창에는 존재하지 않고 수동으로 입력해야만 합니다. MainForm의 생성자에 디자이너에서 생성한 코드를 호출하기 위한 InitializeComponent(); 아래줄에 다음과 같이 입력합니다.
base.ResizeRedraw = true;
수동으로 속성을 변경하거나 초기화를 시킬일이 있으면 이곳에 입력해야 한다는 것을 알아두면 됩니다.
이제 다시 실행하여 사각형을 선택한 후 창크기를 바꾸어 보겠습니다. 이제 정상적으로 그려지나, 빠른속도로 바꾸면 그려진 사각형이 깜박인다는 것을 알수 있습니다. 이 문제는 C++ 강좌에서 복잡한 방식으로 해결을 했다. 그러나 C#에서는 MainForm 생성자에 다음과 같이 세줄을 입력하면 깜박이지 않게 됩니다.
base.SetStyle(ControlStyles.DoubleBuffer, true);
base.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
base.SetStyle(ControlStyles.ResizeRedraw, true);
실제 C#내부적으로는 C++에서와 마찬가지로 메모리(Bitmap)을 생성하여 그 메모리에 먼저 그리고 한꺼번에 화면(더블버퍼링)으로 옮깁니다. 따라서 복잡한 그림일 경우 더블버퍼링을 이용하는 것이 그냥 화면에 뿌려 주는 것보다 더 빠릅니다.
마지막 줄 base.SetStyle(ControlStyles.ResizeRedraw, true)는 위에서 설정한 base.ResizeRedraw = true와 같은 기능을 수행합니다. 따라서 base.ResizeRedraw = true는 삭제해도 됩니다.
예제 프로그램 다운로드
'C#' 카테고리의 다른 글
C#08. 메모리, 마우스 및 스크롤 (0) | 2022.03.08 |
---|---|
C#07. 대화상자 (0) | 2022.03.08 |
C#05. 메뉴 및 툴바 지원 (0) | 2022.03.08 |
C#04. 실제 프로그램 제작 (0) | 2022.03.08 |
C#03. 기본 프로그램 제작 2 (0) | 2022.03.08 |