도형 그리기

타원, 사각형, 다각형 및 경로 같은 모양을 그리는 방법을 알아봅니다. Path 클래스는 XAML UI에서 매우 복잡한 벡터 기반 그리기 언어를 시각화하는 방법입니다. 예를 들어, 베지어 곡선을 그릴 수 있습니다.

XAML UI에서 공간 영역을 정의하는 두 가지 클래스 집합은 Shape 클래스와 Geometry 클래스입니다. 이 두 클래스의 주된 차이점은 Shape는 연결된 브러시가 있고 화면에 렌더링할 수 있는데 Geometry는 단순히 공간 영역을 정의하며 다른 UI 속성에 정보를 제공하는 데 도움이 되지 않는 한 렌더링되지 않는다는 것입니다. ShapeGeometry로 경계가 정의된 UIElement로 간주할 수 있습니다. 이 항목에서는 주로 Shape 클래스를 다룹니다.

Shape 클래스는 Line, Ellipse, Rectangle, Polygon, Polyline, Path입니다. Path는 임의의 기하 도형을 정의할 수 있으므로 흥미롭고 Geometry 클래스는 Path의 부분을 정의하는 한 방법이기 때문에 여기에 포함됩니다.

UWP 및 WinUI 2

Important

이 문서의 정보 및 예제는 Windows 앱 SDKWinUI 3를 사용하는 앱에 최적화되어 있지만 일반적으로 WinUI 2를 사용하는 UWP 앱에 적용할 수 있습니다. 플랫폼별 정보 및 예제는 UWP API 참조를 확인하세요.

이 섹션에는 UWP 또는 WinUI 2 앱에서 컨트롤을 사용하는 데 필요한 정보가 있습니다.

이러한 모형용 API는 Windows.UI.Xaml.Shapes 네임스페이스에 있습니다.

모양 채우기 및 스트로크

Shape를 앱 캔버스로 렌더링하려면 Brush를 연결해야 합니다. ShapeFill 속성을 원하는 Brush로 설정합니다. 브러시에 대한 자세한 내용은 브러시 사용을 참조하세요.

Shape에는 모양의 경계 주위로 그려지는 선인 Stroke도 있을 수 있습니다. Stroke도 모양을 정의하는 Brush가 필요하며, StrokeThickness에 대해 0이 아닌 값이 있어야 합니다. StrokeThickness는 모양의 테두리를 두르는 경계의 두께를 정의하는 속성입니다. StrokeBrush 값을 지정하지 않거나 StrokeThickness를 0으로 설정하면 모양 주위에 테두리가 그려지지 않습니다.

타원

타원은 곡선 경계가 있는 모양입니다. 기본 Ellipse를 만들려면 채우기에 해당하는 너비, 높이, 브러시를 지정합니다.

다음 예제에서 만드는 타원너비가 200이고, 높이가 200이며, SteelBlue 색상의 SolidColorBrush를 그 채우기로 사용합니다.

<Ellipse Fill="SteelBlue" Height="200" Width="200" />
var ellipse1 = new Ellipse();
ellipse1.Fill = new SolidColorBrush(Colors.SteelBlue);
ellipse1.Width = 200;
ellipse1.Height = 200;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(ellipse1);

다음은 렌더링된 타원입니다.

A rendered Ellipse.

이 경우의 타원은 대부분의 사람이 원을 고려하지만 XAML에서 원 모양을 선언하는 방법입니다. 너비높이가 같은 타원을 사용합니다.

타원이 UI 레이아웃으로 배치되는 경우 그 크기는 너비높이를 갖는 사각형과 동일한 것으로 간주됩니다. 경계 밖의 영역에는 렌더링이 없지만 여전히 그 레이아웃 슬롯 크기의 일부입니다.

타원 요소 6개 집합은 ProgressRing 컨트롤용 컨트롤 템플릿의 일부이며, 동심 타원 요소 2개는 RadioButton의 일부입니다.

사각형

사각형은 반대쪽이 같은 4면 모양입니다. 기본 사각형을 만들려면 너비, 높이, 채우기를 지정합니다.

사각형의 모서리를 둥글게 할 수 있습니다. 둥근 모서리를 만들려면 RadiusXRadiusY 속성의 값을 지정합니다. 이러한 속성은 모서리의 곡선을 정의하는 타원의 x축과 y축을 지정합니다. RadiusX의 최대 허용 값은 너비를 2로 나눈 값이고 RadiusY의 최대 허용 값은 높이를 2로 나눈 값입니다.

다음 예제에서는 너비가 200이고 높이가 100인 직사각형을 만듭니다. 이것은 그 FillSolidColorBrushBlue 값을 사용하고, 그 StrokeSolidColorBrushBlack 값을 사용합니다. 여기에서는 StrokeThickness를 3으로 설정합니다. RadiusX 속성을 50으로 설정하고 RadiusY 속성을 10으로 설정하면 사각형 모서리가 둥글게 됩니다.

<Rectangle Fill="Blue"
           Width="200"
           Height="100"
           Stroke="Black"
           StrokeThickness="3"
           RadiusX="50"
           RadiusY="10" />
var rectangle1 = new Rectangle();
rectangle1.Fill = new SolidColorBrush(Colors.Blue);
rectangle1.Width = 200;
rectangle1.Height = 100;
rectangle1.Stroke = new SolidColorBrush(Colors.Black);
rectangle1.StrokeThickness = 3;
rectangle1.RadiusX = 50;
rectangle1.RadiusY = 10;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(rectangle1);

다음은 렌더링된 사각형입니다.

A rendered Rectangle.

Rectangle을 사용하는 대신 Border가 더 적합할 수 있는 UI 정의 시나리오도 있습니다. 다른 콘텐츠 주위에 사각형을 만들려면 Rectangle처럼 고정된 높이와 너비 치수를 사용하는 대신 Border를 사용하는 것이 좋은데 그 이유는 자식 콘텐츠가 있을 수 있어 해당 콘텐츠 주위로 크기가 자동으로 조정되기 때문입니다. Border에는 CornerRadius 속성을 설정할 경우 둥근 모서리를 만드는 옵션도 있습니다.

반면에 Rectangle은 컨트롤 컴퍼지션에 사용하는 것이 더 좋을 것입니다. Rectangle은 포커스 가능한 컨트롤의 "FocusVisual" 부분으로 사용되기 때문에 많은 컨트롤 템플릿에서 볼 수 있습니다. 컨트롤이 "Focused" 시각적 상태일 때마다 이 사각형이 표시되고 다른 상태에서는 숨겨집니다.

Polygon

Polygon은 임의의 점 개수로 경계가 정의되는 모양입니다. 경계는 마지막 점이 첫 번째 점에 연결된 상태에서 한 점에서 다음 점으로 선을 연결하여 만듭니다. Points 속성은 경계를 구성하는 점의 컬렉션을 정의합니다. XAML에서는 쉼표로 구분된 목록을 사용하여 점을 정의합니다. 코드 숨김에서는 PointCollection을 사용하여 점을 정의하고 각 개별 점을 Point 값으로 컬렉션에 추가합니다.

시작점과 끝점이 모두 동일한 Point 값으로 지정되도록 점을 명시적으로 선언할 필요가 없습니다. Polygon의 렌더링 논리는 닫힌 모양을 정의하고 끝점을 시작점에 암시적으로 연결한다고 가정합니다.

다음 예제에서는 4개의 점이 (10,200), (60,140), (130,140), (180,200)로 설정된 Polygon을 만듭니다. SolidColorBrushLightBlue 값을 그 Fill에 사용하며, Stroke 값이 없어 경계 윤곽선이 없습니다.

<Polygon Fill="LightBlue"
         Points="10,200,60,140,130,140,180,200" />
var polygon1 = new Polygon();
polygon1.Fill = new SolidColorBrush(Colors.LightBlue);

var points = new PointCollection();
points.Add(new Windows.Foundation.Point(10, 200));
points.Add(new Windows.Foundation.Point(60, 140));
points.Add(new Windows.Foundation.Point(130, 140));
points.Add(new Windows.Foundation.Point(180, 200));
polygon1.Points = points;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(polygon1);

다음은 렌더링된 Polygon입니다.

A rendered Polygon.

Point 값은 모양의 꼭짓점 선언이 아닌 다른 시나리오에 대한 XAML에서 한 형식으로 자주 사용됩니다. 예를 들어, Point는 터치 이벤트에 대한 이벤트 데이터의 일부이므로 터치 동작이 발생한 좌표 공간의 위치를 정확하게 알 수 있습니다. Point와 이를 XAML 또는 코드에서 사용하는 방법에 관한 자세한 내용은 Point용 API 참조 항목을 참조하세요.

Line 은 좌표 공간의 두 점 사이에 그려지는 선일 뿐입니다. Line은 내부 공간이 없으므로 Fill에 제공되는 값을 무시합니다. LineStrokeStrokeThickness 속성의 값을 지정해야 하는데 안 그러면 Line이 렌더링되지 않기 때문입니다.

Point 값을 사용하여 Line 모양을 지정하지 말고 X1, Y1, X2Y2에 불연속 Double 값을 사용합니다. 이렇게 하면 가로 또는 세로 선에 최소 태그를 사용할 수 있습니다. 예를 들어, <Line Stroke="Red" X2="400"/>은 길이가 400픽셀인 가로줄을 정의합니다. 나머지 X,Y 속성은 기본적으로 0이므로 점의 측면에서 이 XAML은 (0,0)에서 (400,0)까지 선을 그립니다. 그런 다음 (0,0) 이외의 시작점에서 시작하려면 TranslateTransform을 사용하여 전체 Line을 옮깁니다.

<Line Stroke="Red" X2="400"/>
var line1 = new Line();
line1.Stroke = new SolidColorBrush(Colors.Red);
line1.X2 = 400;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(line1);

폴리라인

PolylinePolyline의 마지막 점이 첫 번째 점에 연결되지 않는다는 점을 제외하고 모양 경계가 점 집합에 의해 정의된다는 점에서 Polygon과 유사합니다.

참고 항목

Polyline에 대해 설정된 Points에 명시적으로 동일한 시작점과 끝점이 있을 수도 있지만, 이 경우 Polygon을 대신 사용하는 것이 좋습니다.

PolylineFill을 지정하면 Polyline에 설정된 Points의 시작점과 끝점이 교차하지 않아도 Fill이 모양의 내부 공간을 채웁니다. Fill을 지정하지 않으면 연속된 선의 시작점과 끝점이 교차하는 몇 개의 개별 Line 요소를 지정한 경우에 렌더링된 것과 Polyline이 유사합니다.

Polygon처럼 Points 속성도 경계를 구성하는 점의 컬렉션을 정의합니다. XAML에서는 쉼표로 구분된 목록을 사용하여 점을 정의합니다. 코드 숨김에서는 PointCollection을 사용하여 점을 정의하고 각 개별 점을 Point 구조로 컬렉션에 추가합니다.

이 예제에서는 4개의 점이 (10,200), (60,140), (130,140), (180,200)에 설정된 Polyline을 만듭니다. Stroke가 정의되지만 Fill은 정의되지 않습니다.

<Polyline Stroke="Black"
          StrokeThickness="4"
          Points="10,200,60,140,130,140,180,200" />
var polyline1 = new Polyline();
polyline1.Stroke = new SolidColorBrush(Colors.Black);
polyline1.StrokeThickness = 4;

var points = new PointCollection();
points.Add(new Windows.Foundation.Point(10, 200));
points.Add(new Windows.Foundation.Point(60, 140));
points.Add(new Windows.Foundation.Point(130, 140));
points.Add(new Windows.Foundation.Point(180, 200));
polyline1.Points = points;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(polyline1);

다음은 렌더링된 Polyline입니다. 첫 번째 점과 마지막 점이 다각형 Stroke 경계선으로 연결되지 않는데 Polygon에 있기 때문입니다.

A rendered Polyline.

Path

Path는 임의의 기하 도형을 정의하는 데 사용할 수 있으므로 활용도가 가장 큰 Shape입니다. 그러나 이 활용성에는 복잡성이 수반됩니다. 이제 XAML에서 기본 Path를 만드는 방법을 살펴보겠습니다.

Data 속성을 사용하여 경로의 기하 도형을 정의합니다. Data를 설정하는 방법은 두 가지가 있습니다.

  • XAML에서 Data 문자열 값을 설정할 수 있습니다. 이 형식에서 Path.Data 값은 그래픽에 serialization 형식을 사용합니다. 일반적으로 문자열 형식이 처음 설정된 후에는 이 값을 텍스트로 편집하지 않습니다. 대신, 디자인에서 작업하거나 표면에 메타포를 그리는 데 사용할 수 있는 디자인 도구를 사용합니다. 그런 다음 출력을 저장하거나 내보내면 Path.Data 정보가 포함된 XAML 파일 또는 XAML 문자열 조각이 표시됩니다.
  • Data 속성을 단일 Geometry 개체로 설정할 수 있습니다. 이 작업은 코드 또는 XAML에서 수행할 수 있습니다. 이 단일 Geometry는 일반적으로 GeometryGroup인데 이는 개체 모델을 위해 여러 기하 도형 정의를 단일 개체로 합성할 수 있는 컨테이너 역할을 합니다. 이 작업을 수행하는 가장 일반적인 이유는 PathFigureSegments 값으로 정의할 수 있는 하나 이상의 곡선과 복잡한 모양을 사용하려고 하기 때문입니다(예: BezierSegment).

이 예제는 를 사용하여 몇 개의 벡터 셰이프만 생성한 다음 결과를 XAML로 저장한 결과일 수 있는 Path를 보여줍니다. 전체 Path는 베지어 곡선 세그먼트와 선 세그먼트로 구성됩니다. 이 예제는 Path.Data serialization 형식에 있는 요소와 숫자가 나타내는 요소에 대한 몇 가지 예제를 제공하기 위한 것입니다.

Data는 경로에 대한 절대 시작점을 설정하는 "M"으로 표시된 이동 명령으로 시작합니다.

첫 번째 세그먼트는 (100,200)에서 시작하여 (400,175)에서 끝나는 입방형 베지어 곡선인데, 이것은 두 개의 제어점 (100,25)(400,350)를 사용하여 그립니다. 이 세그먼트는 Data 특성 문자열에서 "C" 명령으로 표시됩니다.

두 번째 세그먼트는 절대 가로선 명령 "H"로 시작하는데, 이것은 이전 하위 경로의 엔드포인트(400,175)에서 새 엔드포인트(280,175)까지 그리는 선을 지정합니다. 이 명령은 가로 선 명령이기 때문에 지정된 값은 x 좌표입니다.

<Path Stroke="DarkGoldenRod" 
      StrokeThickness="3"
      Data="M 100,200 C 100,25 400,350 400,175 H 280" />

다음은 렌더링된 Path입니다.

Screenshot of a simple rendered path.

다음 예제는 앞에서 설명한 다른 기법, 즉 PathGeometry가 있는 GeometryGroup을 사용하는 방법을 보여줍니다. 이 예제에서는 PathGeometry의 일부로 사용할 수 있는 기여 기하 도형 형식 중 일부(예: PathFigure)와 PathFigure.Segments에서 세그먼트가 될 수 있는 다양한 요소를 연습합니다.

<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
    <Path.Data>
        <GeometryGroup>
            <RectangleGeometry Rect="50,5 100,10" />
            <RectangleGeometry Rect="5,5 95,180" />
            <EllipseGeometry Center="100, 100" RadiusX="20" RadiusY="30"/>
            <RectangleGeometry Rect="50,175 100,10" />
            <PathGeometry>
                <PathGeometry.Figures>
                    <PathFigureCollection>
                        <PathFigure IsClosed="true" StartPoint="50,50">
                            <PathFigure.Segments>
                                <PathSegmentCollection>
                                    <BezierSegment Point1="75,300" Point2="125,100" Point3="150,50"/>
                                    <BezierSegment Point1="125,300" Point2="75,100"  Point3="50,50"/>
                                </PathSegmentCollection>
                            </PathFigure.Segments>
                        </PathFigure>
                    </PathFigureCollection>
                </PathGeometry.Figures>
            </PathGeometry>
        </GeometryGroup>
    </Path.Data>
</Path>
var path1 = new Microsoft.UI.Xaml.Shapes.Path();
path1.Fill = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 204, 204, 255));
path1.Stroke = new SolidColorBrush(Colors.Black);
path1.StrokeThickness = 1;

var geometryGroup1 = new GeometryGroup();
var rectangleGeometry1 = new RectangleGeometry();
rectangleGeometry1.Rect = new Rect(50, 5, 100, 10);
var rectangleGeometry2 = new RectangleGeometry();
rectangleGeometry2.Rect = new Rect(5, 5, 95, 180);
geometryGroup1.Children.Add(rectangleGeometry1);
geometryGroup1.Children.Add(rectangleGeometry2);

var ellipseGeometry1 = new EllipseGeometry();
ellipseGeometry1.Center = new Point(100, 100);
ellipseGeometry1.RadiusX = 20;
ellipseGeometry1.RadiusY = 30;
geometryGroup1.Children.Add(ellipseGeometry1);

var pathGeometry1 = new PathGeometry();
var pathFigureCollection1 = new PathFigureCollection();
var pathFigure1 = new PathFigure();
pathFigure1.IsClosed = true;
pathFigure1.StartPoint = new Windows.Foundation.Point(50, 50);
pathFigureCollection1.Add(pathFigure1);
pathGeometry1.Figures = pathFigureCollection1;

var pathSegmentCollection1 = new PathSegmentCollection();
var pathSegment1 = new BezierSegment();
pathSegment1.Point1 = new Point(75, 300);
pathSegment1.Point2 = new Point(125, 100);
pathSegment1.Point3 = new Point(150, 50);
pathSegmentCollection1.Add(pathSegment1);

var pathSegment2 = new BezierSegment();
pathSegment2.Point1 = new Point(125, 300);
pathSegment2.Point2 = new Point(75, 100);
pathSegment2.Point3 = new Point(50, 50);
pathSegmentCollection1.Add(pathSegment2);
pathFigure1.Segments = pathSegmentCollection1;

geometryGroup1.Children.Add(pathGeometry1);
path1.Data = geometryGroup1;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot">
layoutRoot.Children.Add(path1);

다음은 렌더링된 Path입니다.

Screenshot of a complex rendered path.

PathGeometry를 사용하는 것이 Path.Data 문자열을 채우는 것보다 읽기가 더 좋을 수 있습니다. 반면에 Path.Data는 SVG(확장 가능한 벡터 그래픽) 이미지 경로 정의와 호환되는 구문을 사용하므로 SVG에서 그래픽을 포팅하거나 Blend와 같은 도구에서 출력하는 데 유용할 수 있습니다.