프로그래밍 모델의 변경 내용
Windows GDI+ 프로그래밍과 Windows 그래픽 장치 인터페이스(GDI) 프로그래밍의 차이점을 다음 섹션에서 여러 가지 방법으로 설명합니다.
- 디바이스 컨텍스트, 핸들 및 그래픽 개체
- 선을 그리는 두 가지 방법
- 매개 변수로서의 펜, 브러시, 경로, 이미지 및 글꼴
- 메서드 오버로드
- 현재 위치 더 이상 없음
- 그리기 및 채우기를 위한 별도의 메서드
- 지역 생성하기
디바이스 컨텍스트, 핸들 및 그래픽 개체
GDI(이전 버전의 Windows에 포함된 그래픽 디바이스 인터페이스)를 사용하여 프로그램을 작성하였다면, DC(디바이스 컨텍스트)에 대해 잘 알고 있는 것입니다. 디바이스 컨텍스트는 Windows에서 특정 디스플레이 디바이스의 기능에 대한 정보 및 해당 디바이스에서 항목을 그리는 방법을 지정하는 특성을 저장하기 위해 사용하는 구조입니다. 디스플레이의 특정 창과도 비디오 디스플레이에 대한 디바이스 컨텍스트가 연결됩니다. 먼저 HDC(디바이스 컨텍스트)에 대한 핸들을 가져온 뒤, 실제로 그리기를 수행하는 GDI 함수에 해당 핸들을 인수로 전달합니다. 또한 디바이스 컨텍스트의 특성을 가져오거나 설정하는 GDI 함수에 핸들을 인수로 전달합니다.
GDI+를 사용하는 경우, GDI를 사용할 때와 같이 핸들 및 디바이스 컨텍스트에 신경 쓸 필요가 없습니다. 그래픽 개체를 만든 뒤, 해당 메서드를 익숙한 개체 지향 스타일인 myGraphicsObject.DrawLine(매개 변수)에서 호출하기만 하면 됩니다. 디바이스 컨텍스트가 GDI의 핵심인 것처럼, 그래픽 개체는 GDI+의 핵심입니다. 디바이스 컨텍스트와 그래픽 개체는 비슷한 역할을 수행하지만, GDI(디바이스 컨텍스트)와 함께 사용되는 핸들 기반 프로그래밍 모델과 GDI+(그래픽 개체)에 사용되는 개체 지향 모델 간에는 몇 가지 기본적인 차이점이 있습니다.
디바이스 컨텍스트와 같은 그래픽 개체는 화면의 특정 창과 연결되며 항목을 그리는 방법을 지정하는 특성(예시: 다듬기 모드 및 텍스트 렌더링 힌트)을 포함합니다. 하지만 그래픽 개체는 디바이스 컨텍스트처럼 펜, 브러시, 경로, 이미지 또는 글꼴에 연결되지 않습니다. 예를 들어, GDI에서 디바이스 컨텍스트를 사용하여 선을 그리려면 먼저 펜 개체를 디바이스 컨텍스트와 연결하기 위해 SelectObject를 호출해야 합니다. 이를 디바이스 컨텍스트에 대한 펜 선택이라고 부릅니다. 다른 펜을 선택하기 전까지, 디바이스 컨텍스트에 그려진 모든 선은 해당 펜을 사용합니다. GDI+를 사용하면 그래픽 클래스의 DrawLine 메서드에 펜 개체를 인수로 전달합니다. 지정된 펜 개체를 그래픽 개체와 연결하지 않고도, 각 일련의 DrawLine 호출에서 다른 펜 개체를 사용 가능합니다.
선을 그리는 두 가지 방법
다음과 같은 두 예시에서 너비 3의 빨간색 선을 각각 위치(20, 10)에서 위치(200,100)로 그립니다. 첫 번째 예시에서는 GDI를 호출하고, 두 번째 예시에서는 C++ 클래스 인터페이스를 통해 GDI+를 호출합니다.
GDI를 통해 선 그리기
디바이스 컨텍스트와 펜의 두 개체는 GDI를 사용하여 선을 그리기 위해 필요합니다. 디바이스 컨텍스트에 대한 핸들을 가져오기 위해 BeginPaint를 호출하고, 펜에 대한 핸들을 가져오기 위해 CreatePen을 호출합니다. 그 다음, 디바이스 컨텍스트로 펜을 선택하기 위해 SelectObject를 호출합니다. 펜 위치를 (20, 10)로 설정하기 위해 MoveToEx를 호출0한 뒤, 해당 펜 위치에서 (200, 100)로 선을 그리기 위해 LineTo를 호출합니다. MoveToEx와 LineTo는 모두 hdc를 인수로 받습니다.
HDC hdc;
PAINTSTRUCT ps;
HPEN hPen;
HPEN hPenOld;
hdc = BeginPaint(hWnd, &ps);
hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
hPenOld = (HPEN)SelectObject(hdc, hPen);
MoveToEx(hdc, 20, 10, NULL);
LineTo(hdc, 200, 100);
SelectObject(hdc, hPenOld);
DeleteObject(hPen);
EndPaint(hWnd, &ps);
GDI+ 및 C++ 클래스 인터페이스를 통해 선 그리기
그래픽 개체와 펜 개체는 GDI+ 및 C++ 클래스 인터페이스를 사용하여 선을 그리기 위해 필요합니다. 이러한 개체에 대한 핸들을 Windows에 요청하지 않는다는 점에 유의하세요. 대신 생성자를 사용하여 그래픽 클래스의 인스턴스(그래픽 개체) 및 펜 클래스의 인스턴스(펜 개체)를 만듭니다. 그래픽 클래스의 Graphics::DrawLine 메서드를 호출하면 선을 그릴 수 있습니다. Graphics::DrawLine 메서드의 첫 번째 매개 변수는 펜 개체에 대한 포인터입니다. 이는 이전의 GDI 예시처럼 디바이스 컨텍스트로 펜을 선택하는 것보다 더 간단하고 유연한 체계입니다.
HDC hdc;
PAINTSTRUCT ps;
Pen* myPen;
Graphics* myGraphics;
hdc = BeginPaint(hWnd, &ps);
myPen = new Pen(Color(255, 255, 0, 0), 3);
myGraphics = new Graphics(hdc);
myGraphics->DrawLine(myPen, 20, 10, 200, 100);
delete myGraphics;
delete myPen;
EndPaint(hWnd, &ps);
매개 변수로서의 펜, 브러시, 경로, 이미지 및 글꼴
이전의 예시는 그리기 메서드를 제공하는 그래픽 개체와 별도로 펜 개체를 만들고 유지 관리할 수 있다는 것을 보여 줍니다. Brush, GraphicsPath, Image 및 Font 개체를 Graphics 개체와 별도로 만들고 유지 관리할 수도 있습니다. 그래픽 클래스에서 제공하는 대부분의 그리기 메서드는 Brush, GraphicsPath, Image 또는 Font 개체를 인수로 받습니다. 예를 들어, Brush 개체의 주소는 FillRectangle 메서드에 인수로 전달되고, GraphicsPath 개체의 주소는 Graphics::DrawPath 메서드에 인수로 전달됩니다. 마찬가지로 Image 및 Font 개체의 주소는 DrawImage 및 DrawString 메서드에 전달됩니다. 이는 디바이스 컨텍스트에 브러시, 경로, 이미지 또는 글꼴을 선택한 뒤, 드로잉 함수에 인수로 디바이스 컨텍스트에 핸들을 전달하는 GDI와는 대조적입니다.
메서드 오버로드
많은 GDI+ 메서드가 오버로드됩니다. 즉, 동일한 이름을 여러 메서드가 공유하지만 매개 변수 목록은 다릅니다. 예를 들어, 다음과 같은 형식으로 Graphics 클래스의 DrawLine 메서드가 제공됩니다.
Status DrawLine(IN const Pen* pen,
IN REAL x1,
IN REAL y1,
IN REAL x2,
IN REAL y2);
Status DrawLine(IN const Pen* pen,
IN const PointF& pt1,
IN const PointF& pt2);
Status DrawLine(IN const Pen* pen,
IN INT x1,
IN INT y1,
IN INT x2,
IN INT y2);
Status DrawLine(IN const Pen* pen,
IN const Point& pt1,
IN const Point& pt2);
위의 4가지 DrawLine 변형은 모두 펜 개체에 대한 포인터, 시작점의 좌표 및 끝점의 좌표를 받습니다. 처음 두 변형은 부동 소수점 숫자로 좌표를 받으며, 마지막의 두 변형은 좌표를 정수로 받습니다. 첫 번째 및 세 번째 변형은 네 개의 개별 숫자 목록으로 좌표를 수신하며, 두 번째와 네 번째 변형은 좌표를 포인트(또는 PointF) 개체 쌍으로 받습니다.
현재 위치 더 이상 없음
이전에 표시된 DrawLine 메서드에서 줄의 시작점과 끝점 모두 인수로 수신됩니다. 이는 MoveToEx를 호출하여 (x1, y1)에서 시작하여 (x2, y2)에서 끝나는 선을 그리기 위해 현재 펜 위치와 LineTo를 설정하는 GDI 체계에서 벗어난 것입니다. 전체적으로 GDI+는 현재 위치의 개념을 포기했습니다.
그리기 및 채우기를 위한 별도의 메서드
GDI+는 윤곽선을 그리고 사각형 등의 모양의 내부를 채우는 과정에서 GDI보다 더 유연합니다. GDI에는 윤곽선을 그리며 사각형의 내부를 한 단계로 채우는 Rectangle 함수가 있습니다. 현재 선택한 펜으로 윤곽선이 그려지며, 현재 선택한 브러시로 내부가 채워집니다.
hBrush = CreateHatchBrush(HS_CROSS, RGB(0, 0, 255));
hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
SelectObject(hdc, hBrush);
SelectObject(hdc, hPen);
Rectangle(hdc, 100, 50, 200, 80);
GDI+에는 윤곽선을 그리며 사각형의 내부를 채우는 별도의 메서드가 있습니다. Graphics 클래스의 DrawRectangle 메서드는 매개 변수 중 하나로 Pen 개체의 주소를 사용하고, FillRectangle 메서드는 매개 변수 중 하나로 Brush 개체의 주소를 사용합니다.
HatchBrush* myHatchBrush = new HatchBrush(
HatchStyleCross,
Color(255, 0, 255, 0),
Color(255, 0, 0, 255));
Pen* myPen = new Pen(Color(255, 255, 0, 0), 3);
myGraphics.FillRectangle(myHatchBrush, 100, 50, 100, 30);
myGraphics.DrawRectangle(myPen, 100, 50, 100, 30);
사각형의 왼쪽 가장자리, 위쪽, 너비 및 높이를 지정하는 인수를 GDI+의 FillRectangle 및 DrawRectangle 메서드가 받습니다. 이는 사각형의 왼쪽 가장자리, 오른쪽 가장자리, 위쪽 및 아래쪽을 지정하는 인수를 사용하는 GDI Rectangle 함수와는 대조적입니다. 또한 네 개의 매개 변수가 GDI+의 Color 클래스 생성자에 있습니다. 마지막 세 가지 매개 변수는 일반적인 빨강, 녹색 및 파랑 값입니다. 첫 번째 매개 변수는 그리는 색이 배경색과 혼합되는 정도를 지정하는 알파 값입니다.
지역 생성하기
GDI는 지역을 만들기 위한 함수인 CreateRectRgn, CreateEllpticRgn, CreateRoundRectRgn, CreatePolygonRgn 및 CreatePolyPolygonRgn을 제공합니다. GDI+의 Region 클래스가 사각형, 타원, 둥근 사각형, 다각형을 인수로 받는 유사한 생성자를 가질 것이라고 예상할 수 있지만, 실제로는 그렇지 않습니다. GDI+의 Region 클래스는 Rect 개체 참조를 수신하는 생성자와 GraphicsPath 개체의 주소를 받는 다른 생성자를 제공합니다. 타원, 둥근 사각형 또는 다각형을 기반으로 하위 지역을 생성하려는 경우, GraphicsPath 개체(예시: 줄임표 포함)를 만든 뒤, 해당 GraphicsPath 개체의 주소를 Region 생성자에 전달하여 쉽게 생성 가능합니다.
셰이프와 경로를 결합하여 복잡한 영역을 쉽게 형성하기 위해 GDI+를 사용할 수 있습니다. Region 클래스에는 경로 또는 다른 하위 지역으로 기존 하위 지역을 보강하기 위해 사용 가능한 Union 및 Intersect 메서드가 있습니다. GraphicsPath 개체가 Region 생성자에 인수로 전달될 때 제거되지 않는다는 것은 GDI+ 체계의 한 가지 좋은 기능입니다. GDI에서는 경로를 하위 지역으로 변환하기 위해 PathToRegion 함수를 사용할 수 있지만, 프로세스에서 경로가 제거됩니다. 또한 주소가 Union 또는 Intersect 메서드에 인수로 전달되는 경우, GraphicsPath 개체는 제거되지 않으므로 지정된 경로를 여러 개별 하위 지역에 대한 구성 요소로 사용할 수 있습니다. 이 방법은 다음 예제에서 확인할 수 있습니다. onePath가 이미 초기화된 GraphicsPath 개체(단순 또는 복합)에 대한 포인터라고 가정합니다.
Region region1(rect1);
Region region2(rect2);
region1.Union(onePath);
region2.Intersect(onePath);