Desenhar formas

Saiba como desenhar formas, como elipses, retângulos, polígonos e caminhos. A classe Path é a maneira de visualizar uma linguagem de desenho baseada em vetor bastante complexa em uma interface do usuário XAML; por exemplo, você pode desenhar curvas de Bezier.

Dois conjuntos de classes definem uma região de espaço na interface do usuário XAML: classes Shape e classes Geometry. A principal diferença entre essas classes é que Shape tem um pincel associado a ela e pode ser renderizada na tela, e Geometry simplesmente define uma região de espaço e não é renderizada, a menos que ajude a contribuir com informações para outra propriedade da interface do usuário. Você pode pensar em uma Forma como um UIElement com seu limite definido por uma Geometria. Este tópico aborda principalmente as classes Shape.

As classes Shape são Line, Ellipse, Rectangle, Polygon, Polyline e Path. Path é interessante porque pode definir uma geometria arbitrária, e a classe Geometry está envolvida aqui porque essa é uma forma de definir as partes de um Path.

UWP e WinUI 2

Importante

As informações e exemplos neste artigo são otimizados para aplicativos que usam o SDK do Aplicativo Windows e o WinUI 3, mas geralmente são aplicáveis a aplicativos UWP que usam o WinUI 2. Consulte a referência de API da UWP para obter informações e exemplos específicos da plataforma.

Esta seção contém informações necessárias para usar o controle em um aplicativo UWP ou WinUI 2.

As APIs para essas formas existem no namespace Windows.UI.Xaml.Shapes.

Preenchimento e traço para formas

Para que uma Forma seja renderizada na tela do aplicativo, você deve associar um Pincel a ela. Defina a propriedade Preenchimento da Forma como o Pincel desejado. Para obter mais informações sobre pincéis, confira Como usar pincéis.

Uma Forma também pode ter um Traço, que é uma linha desenhada ao redor do perímetro da forma. Um traço também requer um Pincel que define sua aparência e deve ter um valor diferente de zero para StrokeThickness. StrokeThickness é uma propriedade que define a espessura do perímetro ao redor da borda da forma. Se você não especificar um valor de Pincel para Traço ou se definir StrokeThickness como 0, a borda ao redor da forma não será desenhada.

Elipse

Uma Elipse é uma forma com um perímetro curvo. Para criar uma Elipse básica, especifique uma Largura, Altura e um Pincel para o Preenchimento.

O próximo exemplo cria uma Elipse com uma Largura de 200 e uma Altura de 200 e usa uma cor SteelBlue para o SolidColorBrush como seu Preenchimento.

<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);

Aqui está a Elipse renderizada.

A rendered Ellipse.

Nesse caso, a Elipse é o que a maioria das pessoas consideraria um círculo, mas é assim que você declara uma forma de círculo em XAML: use uma Elipse com Largura e Altura iguais.

Quando uma Elipse é posicionada em um layout de interface do usuário, é presumido que seu tamanho é igual a um retângulo com essa Largura e Altura; a área fora do perímetro não tem renderização, mas ainda faz parte de seu tamanho de slot de layout.

Um conjunto de seis elementos de Elipse fazem parte do modelo de controle para o controle ProgressRing e dois elementos Ellipse concêntricos fazem parte de um RadioButton.

Retângulo

Um retângulo é uma forma de quatro lados com seus lados opostos sendo iguais. Para criar um Retângulo básico, especifique uma Largura, uma Altura e umPreenchimento.

Você pode arredondar os cantos de um Retângulo. Para criar cantos arredondados, especifique um valor para as propriedades RadiusX e RadiusY. Essas propriedades especificam os eixos x e y de uma elipse que define a curva dos cantos. O valor máximo permitido de RadiusX é a Largura dividida por dois e o valor máximo permitido de RadiusY é a Altura dividida por dois.

O próximo exemplo cria um Retângulo com uma Largura de 200 e uma Altura de 100. Ele usa um valor Azul de SolidColorBrush para Preenchimento e um valor Preto de SolidColorBrush para Traço. Definimos StrokeThickness como 3. Definimos a propriedade RadiusX como 50 e a propriedade RadiusY como 10, o que dá os cantos arredondados do Retângulo.

<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);

Aqui está o Retângulo renderizado.

A rendered Rectangle.

Existem alguns cenários para definições de interface do usuário em que, em vez de usar um Retângulo, uma Borda pode ser mais apropriada. Se sua intenção é criar uma forma de retângulo em torno de outro conteúdo, talvez seja melhor usar a Borda, pois ela pode ter conteúdo filho e será dimensionada automaticamente em torno desse conteúdo, em vez de usar as dimensões fixas para altura e largura como Retângulo faz. Uma Borda também tem a opção de ter cantos arredondados se você definir a propriedade CornerRadius.

Por outro lado, um Retângulo é provavelmente uma melhor escolha para a composição do controle. Uma forma de Retângulo é vista em muitos modelos de controle porque é usada como uma parte "FocusVisual" para controles focados. Sempre que o controle está em um estado visual "Focado", esse retângulo fica visível; em outros estados, ele fica oculto.

Polígono

Um Polígono é uma forma com um limite definido por um número arbitrário de pontos. O marco de delimitação é criado conectando uma linha de um ponto ao próximo, com o último ponto conectado ao primeiro ponto. A propriedade Pontos define a coleção de pontos que compõem o limite. Em XAML, você define os pontos com uma lista separada por vírgula. No code-behind, você usa uma PointCollection para definir os pontos e adiciona cada ponto individual como um valor Ponto à coleção.

Você não precisa declarar explicitamente os pontos, de modo que o ponto inicial e o ponto final sejam especificados como o mesmo valor de Ponto. A lógica de renderização de um Polígono pressupõe que você está definindo uma forma fechada e conectará o ponto final ao ponto inicial implicitamente.

O próximo exemplo cria um Polígono com quatro pontos definidos como (10,200), (60,140), (130,140) e (180,200). Ele usa um valor LightBlue de SolidColorBrush para seu Preenchimento e não tem valor para Traço, portanto, não tem contorno de perímetro.

<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);

Aqui está o Polígono renderizado.

A rendered Polygon.

Dica

Um valor de Ponto costuma ser usado como um tipo em XAML para cenários que não estejam declarando os vértices de formas. Por exemplo, um Ponto faz parte dos dados de evento para eventos de touch, para que você possa saber exatamente onde em um espaço de coordenadas a ação de touch ocorreu. Para obter mais informações sobre o Ponto e como usá-lo em XAML ou código, consulte o tópico de referência de API para Ponto.

Linha

Uma Linha é simplesmente uma linha desenhada entre dois pontos no espaço de coordenadas. Uma Linha ignora qualquer valor previsto para Preenchimento, pois não tem espaço interior. Para uma Linha, especifique valores para as propriedades Stroke e StrokeThickness, caso contrário, a Linha não será renderizada.

Você não usa valores de Ponto para especificar uma forma de Linha, em vez disso, usa valores Duplo discretos para X1, Y1, X2 e Y2. Isso permite uma marcação mínima para linhas horizontais ou verticais. Por exemplo, <Line Stroke="Red" X2="400"/> define uma linha horizontal com 400 pixels de comprimento. As outras propriedades X,Y são 0 por padrão, portanto, em termos de pontos, esse XAML desenharia uma linha de (0,0) a (400,0). Em seguida, você pode usar um TranslateTransform para mover a Linha inteira, se quiser que ela comece em um ponto diferente de (0,0).

<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);

Polyline

Uma Polilinha é semelhante a um Polígono no sentido de que o marco de delimitação da forma é definido por um conjunto de pontos, exceto que o último ponto em uma Polilinha não está conectado ao primeiro ponto.

Observação

Você pode ter pontos inicial e final idênticos de forma explícita nos Pontos definidos para a Polilinha, mas, nesse caso, você provavelmente poderia usar um Polígono em vez disso.

Se você especificar um Preenchimento de uma Polilinha, o Preenchimento pintará o espaço interno da forma, mesmo que o ponto inicial e o ponto final e os Pontos definidos para a Polilinha não se cruzem. Se você não especificar um Preenchimento, a Polilinha será semelhante ao que teria sido renderizado se você tivesse especificado vários elementos de Linha individuais em que os pontos iniciais e finais de linhas consecutivas se cruzam.

Assim como em um Polígono, a propriedade Pontos define a coleção de pontos que compõem o marco de delimitação. Em XAML, você define os pontos com uma lista separada por vírgula. No code-behind, você usa uma PointCollection para definir os pontos e adiciona cada ponto individual como uma estrutura de Pontos à coleção.

Este exemplo cria uma Polilinha com quatro pontos definidos como (10,200), (60,140), (130,140) e (180,200). Um Traço é definido, mas não um preenchimento.

<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);

Aqui está a Polilinha renderizada. Observe que o primeiro e o último pontos não estão conectados pelo contorno do Traço, pois estão em um Polígono.

A rendered Polyline.

Caminho

Um Caminho é a Forma mais versátil porque você pode usá-lo para definir uma geometria arbitrária. Porém, com essa versatilidade vem a complexidade. Vejamos agora como criar um Caminho básico em XAML.

Você define a geometria de um caminho com a propriedade Dados. Há duas técnicas para definir Dados:

  • Você pode definir um valor de sequência de caracteres para Dados em XAML. Nesse formulário, o valor Path.Data está consumindo um formato de serialização para elementos gráficos. Normalmente, você não edita esse valor em forma de sequência de caracteres depois que ele é estabelecido pela primeira vez. Em vez disso, você usa ferramentas de design que permitem trabalhar em uma metáfora de design ou desenho em uma superfície. Em seguida, você salva ou exporta a saída, e isso fornece um arquivo XAML ou fragmento de sequência de caracteres XAML com informações de Path.Data.
  • Você pode definir a propriedade Data como um só objeto Geometria. Isso pode ser feito em código ou em XAML. Essa Geometria única é tipicamente um GeometryGroup, que atua como um contêiner que pode compor várias definições de geometria em um só objeto para fins do modelo de objeto. O motivo mais comum para fazer isso é porque você quer usar uma ou mais das curvas e formas complexas que podem ser definidas como valores de Segmentos para um PathFigure, por exemplo, BezierSegment.

Este exemplo mostra um Path que pode ter resultado do uso do Blend para Visual Studio para produzir apenas algumas formas vetoriais e então salvar o resultado como XAML. O Path total consiste em um segmento de curva de Bezier e um segmento de linha. O exemplo destina-se principalmente a fornecer alguns exemplos de quais elementos existem no formato de serialização Path.Data e o que os números representam.

Esses Dados começam com o comando move, indicado por "M", que estabelece um ponto de partida absoluto para o caminho.

O primeiro segmento é uma curva de Bézier cúbica que começa em (100,200) e termina em (400,175), que é desenhada usando os dois pontos de controle (100,25) e (400,350). Esse segmento é indicado pelo comando "C" na sequência de caracteres do atributo Datas.

O segundo segmento começa com um comando “H” de linha horizontal absoluto, que especifica uma linha desenhada do ponto de extremidade do subcaminho anterior (400,175) até um novo ponto de extremidade (280,175). Como é um comando de linha horizontal, o valor especificado é uma coordenada x.

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

Aqui está o Caminho renderizado.

Screenshot of a simple rendered path.

O próximo exemplo mostra um uso da outra técnica que discutimos: um GeometryGroup com uma PathGeometry. Este exemplo exercita alguns dos tipos de geometria contribuintes que podem ser usados como parte de um PathGeometry: PathFigure e os vários elementos que podem ser um segmento em 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);

Aqui está o Caminho renderizado.

Screenshot of a complex rendered path.

Usar PathGeometry pode ser mais legível do que preencher uma sequência de caracteres Path.Data. Por outro lado, Path.Data usa uma sintaxe compatível com definições de caminho de imagem SVG (Elementos Gráficos Vetoriais Escaláveis), portanto, pode ser útil para portar gráficos do SVG ou como saída de uma ferramenta como o Blend.