Partilhar via


Desenhar formas

Aprende a desenhar formas, como elipses, retângulos, polígonos e caminhos. A classe Path é a forma de visualizar uma linguagem de desenho baseada em vetores bastante complexa numa interface XAML; por exemplo, podes desenhar curvas de Bézier.

Dois conjuntos de classes definem uma região do espaço na interface XAML: classes de forma e classes de geometria . A principal diferença entre estas classes é que uma Forma tem um pincel associado e pode ser renderizada no ecrã, enquanto uma Geometria simplesmente define uma região de espaço e não é renderizada a menos que ajude a contribuir com informação para outra propriedade da interface. Pode pensar numa Forma como um UIElement com o seu limite definido por uma Geometria. Este tópico abrange principalmente as classes de Forma .

As classes de Forma são Linha, Elipsa, Retângulo, Polígono, Polilinha e Caminho. Path é interessante porque pode definir uma geometria arbitrária, e a classe Geometry está envolvida aqui porque é uma forma de definir as partes de um Path.

Preenchimento e traço para formas

Para que uma Forma seja renderizada na tela da aplicação, é necessário associar um Pincel a ela. Defina a propriedade Fill da Forma com o Pincel que deseja. Para mais informações sobre pincéis, veja Usar pincéis.

Uma Forma pode também ter um traço, que é uma linha traçada em torno do perímetro da forma. Um traço também requer um pincel que defina a sua aparência e deve ter um valor diferente de zero para a espessura do traço. StrokeThickness é uma propriedade que define a espessura do perímetro ao redor da borda da forma. Se não especificares um valor de Pincel para o traço, ou se definires a espessura do traço a 0, então a borda à volta da forma não é 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 exemplo seguinte cria uma Elipse com largura de 200 e altura de 200, usando um SolidColorBrush de cor SteelBlue como 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.

Uma Elipse renderizada.

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

Quando uma Elipse está posicionada num layout de interface, assume-se que o seu tamanho é igual ao de um retângulo com essa Largura e Altura; A área fora do perímetro não tem renderização, mas ainda faz parte do tamanho da sua ranhura de layout.

Um conjunto de 6 elementos Ellipse faz parte do modelo de controlo do ProgressRing, e 2 elementos concêntricos Ellipse fazem parte de um RadioButton.

Retângulo

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

Podes arredondar os cantos de um Retângulo. Para criar cantos arredondados, especifique um valor para as propriedades RadiusX e RadiusY . Estas propriedades especificam o eixo 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 exemplo seguinte cria um retângulo com largura de 200 e altura de 100. Utiliza um valor azul do SolidColorBrush para o Preenchimento e um valor de Preto do SolidColorBrush para o traço. Definimos a espessura do traço para 3. Definimos a propriedade RadiusX para 50 e a propriedade RadiusY para 10, o que dá ao Retângulo cantos arredondados.

<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.

Um Retângulo renderizado.

Existem alguns cenários para definições de UI onde, em vez de usar um Retângulo, uma Borda pode ser mais apropriada. Se a sua intenção é criar uma forma retangular em torno de outro conteúdo, talvez seja melhor usar Border porque pode conter conteúdo filho e dimensiona automaticamente em torno desse conteúdo, em vez de usar dimensões fixas para altura e largura como Rectangle faz. Um Border também tem a opção de ter cantos arredondados se definires a propriedade CornerRadius.

Por outro lado, um Rectangle é provavelmente uma melhor escolha para composição de controlos. Uma forma retangular é vista em muitos modelos de controlo porque é usada como parte "FocusVisual" para controlos focalizáveis. Sempre que o controlo está num estado visual "Focado", este retângulo torna-se visível, noutros estados está oculto.

Polygon

Um polígono é uma forma com uma fronteira definida por um número arbitrário de pontos. A fronteira é criada ligando uma linha de um ponto ao seguinte, com o último ponto ligado ao primeiro ponto. A propriedade Pontos define a coleção de pontos que compõem a fronteira. Em XAML, define-se os pontos com uma lista separada por vírgulas. No code-behind, usas uma PointCollection para definir os pontos e adicionas cada ponto individual como valor de pontos à coleção.

Não precisa de 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 assume que estás a definir uma forma fechada e vai ligar implicitamente o ponto final ao ponto de partida.

O exemplo seguinte cria um polígono com 4 pontos definidos como (10,200), (60,140), (130,140), e (180,200). Utiliza um valor LightBlue do SolidColorBrush para o seu Preenchimento, e não tem valor para o Traço , por isso não tem contorno perimetral.

<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.

Um Polígono renderizado.

Sugestão

Um valor de ponto é frequentemente usado como tipo em XAML para cenários que não sejam declarar os vértices das formas. Por exemplo, um Ponto faz parte dos dados do evento para eventos de toque, por isso pode saber exatamente onde, num espaço de coordenadas, ocorreu a ação de toque. Para mais informações sobre o Point e como o usar em XAML ou código, consulte o tópico de referência da API para o Point.

Linha

Uma linha é simplesmente uma linha traçada entre dois pontos no espaço de coordenadas. Uma linha ignora qualquer valor fornecido para Preenchimento, porque não tem espaço interior. Para uma linha, certifica-te de especificar valores para as propriedades Stroke e StrokeThickness , porque caso contrário a linha não vai renderizar.

Não usas valores de pontos para especificar uma forma de linha , em vez disso usas valores duplos discretos para X1, Y1, X2 e Y2. Isto 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 defeito, por isso, em termos de pontos, este XAML desenharia uma linha de (0,0) para (400,0). Poderia então usar um TranslateTransform para mover toda a linha, se quiser que comece num 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);

Polilinha

Uma polilínea é semelhante a um polígono no sentido em que o limite da forma é definido por um conjunto de pontos, exceto que o último ponto de uma polilinha não está ligado ao primeiro ponto.

Observação

Poderias ter explicitamente um ponto de início e um ponto final idênticos no conjunto de Pontos para a Polyline, mas nesse caso provavelmente poderias ter usado um Polygon em vez disso.

Se especificar um Preenchimento de uma Polilínea, o Preenchimento pinta o espaço interior da forma, mesmo que o ponto inicial e o ponto final do conjunto de Pontos para a Polilinha não se cruzem. Se não especificares um preenchimento, então a Polyline é semelhante ao que teria sido renderizado se tivesses especificado vários elementos individuais de linha onde os pontos de início e de fim de linhas consecutivas se cruzam.

Tal como num Polígono, a propriedade Pontos define a coleção de pontos que compõem a fronteira. Em XAML, define-se os pontos com uma lista separada por vírgulas. No code-behind, usas uma PointCollection para definir os pontos e adicionas cada ponto individual como uma estrutura de pontos à coleção.

Este exemplo cria uma polilinha com quatro pontos definidos para (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á o Polyline renderizado. Note que o primeiro e o último ponto não estão ligados pelo contorno de traço , pois estão num polígono.

Uma polilinha renderizada.

Caminho

Um Path é a forma mais versátil porque podes usá-lo para definir uma geometria arbitrária. Mas com esta versatilidade vem a complexidade. Vamos agora ver como criar um Caminho básico em XAML.

Defines a geometria de um caminho com a propriedade Data . Existem duas técnicas para definir Dados:

  • Podes definir um valor de string para Dados em XAML. Nesta forma, o valor Path.Data consome um formato de serialização para gráficos. Normalmente, não editas este valor em forma de string depois de o estabelecer pela primeira vez. Em vez disso, utiliza-se ferramentas de design que permitem trabalhar uma metáfora de design ou desenho numa superfície. Depois guardas ou exportas a saída, e isso dá-te um ficheiro XAML ou um fragmento de string XAML com informação Path.Data .
  • Podes definir a propriedade Data para um único objeto Geometria . Isto pode ser feito em código ou em XAML. Essa única Geometria é tipicamente um Grupo de Geometria, que atua como um recipiente capaz de compor múltiplas definições de geometria num único objeto para efeitos do modelo do objeto. A razão mais comum para fazer isto é porque se quer usar uma ou mais curvas e formas complexas que podem ser definidas como Segmentos para uma Figura-Caminho, por exemplo BezierSegment.

Este exemplo mostra um Caminho que pode ter sido o resultado de usar o Blend para Visual Studio para produzir poucas formas vetoriais e depois salvar o resultado como XAML. O trajeto total consiste num segmento de curva de Bézier e num segmento de reta. O exemplo destina-se principalmente a dar-lhe alguns exemplos de que elementos existem no formato de serialização Path.Data e o que os números representam.

Este Data começa com o comando de movimento, indicado por "M", que estabelece um ponto absoluto de início para o caminho.

O primeiro segmento é uma curva cúbica de Bézier que começa em (100,200) e termina em (400,175), que é desenhada usando os dois pontos (100,25) de controlo e (400,350). Este segmento é indicado pelo comando "C" na cadeia de atributos Data .

O segundo segmento começa com um comando de linha horizontal absoluta "H", que especifica uma linha traçada do ponto final (400,175) do subcaminho anterior para um novo ponto final (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.

Captura de ecrã de um caminho simples renderizado.

O exemplo seguinte mostra uma utilização da outra técnica que discutimos: um GeometryGroup com um PathGeometry. Este exemplo exerce 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 no 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.

Captura de ecrã de um caminho complexo renderizado.

Usar PathGeometry pode ser mais legível do que preencher uma cadeia Path.Data . Por outro lado, o Path.Data utiliza uma sintaxe compatível com definições de caminho de imagem Scalable Vector Graphics (SVG), pelo que pode ser útil para portar gráficos do SVG ou como saída de uma ferramenta como o Blend.

UWP e WinUI 2

Importante

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

Esta seção contém informações que você precisa para usar o controle em um aplicativo UWP ou WinUI 2.

APIs para estas formas existem no namespace Windows.UI.Xaml.Shapes.